webpack.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. #!/usr/bin/env node
  2. process.exitCode = 0;
  3. /**
  4. * @param {string} command process to run
  5. * @param {string[]} args commandline arguments
  6. * @returns {Promise<void>} promise
  7. */
  8. const runCommand = (command, args) => {
  9. const cp = require("child_process");
  10. return new Promise((resolve, reject) => {
  11. const executedCommand = cp.spawn(command, args, {
  12. stdio: "inherit",
  13. shell: true
  14. });
  15. executedCommand.on("error", error => {
  16. reject(error);
  17. });
  18. executedCommand.on("exit", code => {
  19. if (code === 0) {
  20. resolve();
  21. } else {
  22. reject();
  23. }
  24. });
  25. });
  26. };
  27. /**
  28. * @param {string} packageName name of the package
  29. * @returns {boolean} is the package installed?
  30. */
  31. const isInstalled = packageName => {
  32. try {
  33. require.resolve(packageName);
  34. return true;
  35. } catch (err) {
  36. return false;
  37. }
  38. };
  39. /**
  40. * @typedef {Object} CliOption
  41. * @property {string} name display name
  42. * @property {string} package npm package name
  43. * @property {string} binName name of the executable file
  44. * @property {string} alias shortcut for choice
  45. * @property {boolean} installed currently installed?
  46. * @property {string} url homepage
  47. * @property {string} description description
  48. */
  49. /** @type {CliOption[]} */
  50. const CLIs = [
  51. {
  52. name: "webpack-cli",
  53. package: "webpack-cli",
  54. binName: "webpack-cli",
  55. alias: "cli",
  56. installed: isInstalled("webpack-cli"),
  57. url: "https://github.com/webpack/webpack-cli",
  58. description: "The original webpack full-featured CLI."
  59. },
  60. {
  61. name: "webpack-command",
  62. package: "webpack-command",
  63. binName: "webpack-command",
  64. alias: "command",
  65. installed: isInstalled("webpack-command"),
  66. url: "https://github.com/webpack-contrib/webpack-command",
  67. description: "A lightweight, opinionated webpack CLI."
  68. }
  69. ];
  70. const installedClis = CLIs.filter(cli => cli.installed);
  71. if (installedClis.length === 0) {
  72. const path = require("path");
  73. const fs = require("fs");
  74. const readLine = require("readline");
  75. let notify =
  76. "One CLI for webpack must be installed. These are recommended choices, delivered as separate packages:";
  77. for (const item of CLIs) {
  78. notify += `\n - ${item.name} (${item.url})\n ${item.description}`;
  79. }
  80. console.error(notify);
  81. const isYarn = fs.existsSync(path.resolve(process.cwd(), "yarn.lock"));
  82. const packageManager = isYarn ? "yarn" : "npm";
  83. const installOptions = [isYarn ? "add" : "install", "-D"];
  84. console.error(
  85. `We will use "${packageManager}" to install the CLI via "${packageManager} ${installOptions.join(
  86. " "
  87. )}".`
  88. );
  89. let question = `Which one do you like to install (${CLIs.map(
  90. item => item.name
  91. ).join("/")}):\n`;
  92. const questionInterface = readLine.createInterface({
  93. input: process.stdin,
  94. output: process.stderr
  95. });
  96. questionInterface.question(question, answer => {
  97. questionInterface.close();
  98. const normalizedAnswer = answer.toLowerCase();
  99. const selectedPackage = CLIs.find(item => {
  100. return item.name === normalizedAnswer || item.alias === normalizedAnswer;
  101. });
  102. if (!normalizedAnswer) {
  103. console.error(
  104. "One CLI needs to be installed alongside webpack to use the CLI."
  105. );
  106. process.exitCode = 1;
  107. return;
  108. } else if (!selectedPackage) {
  109. console.error(
  110. "No matching choice.\n" +
  111. "One CLI needs to be installed alongside webpack to use the CLI.\n" +
  112. "Try to installing your CLI of choice manually."
  113. );
  114. process.exitCode = 1;
  115. return;
  116. }
  117. const packageName = selectedPackage.package;
  118. console.log(
  119. `Installing '${
  120. selectedPackage.name
  121. }' (running '${packageManager} ${installOptions.join(
  122. " "
  123. )} ${packageName}')...`
  124. );
  125. runCommand(packageManager, installOptions.concat(packageName))
  126. .then(() => {
  127. require(packageName); //eslint-disable-line
  128. })
  129. .catch(error => {
  130. console.error(error);
  131. process.exitCode = 1;
  132. });
  133. });
  134. } else if (installedClis.length === 1) {
  135. const path = require("path");
  136. const pkgPath = require.resolve(`${installedClis[0].package}/package.json`);
  137. // eslint-disable-next-line node/no-missing-require
  138. const pkg = require(pkgPath);
  139. // eslint-disable-next-line node/no-missing-require
  140. require(path.resolve(
  141. path.dirname(pkgPath),
  142. pkg.bin[installedClis[0].binName]
  143. ));
  144. } else {
  145. console.warn(
  146. `You have installed ${installedClis
  147. .map(item => item.name)
  148. .join(
  149. " and "
  150. )} together. To work with the "webpack" command you need only one CLI package, please remove one of them or use them directly via their binary.`
  151. );
  152. }