webpack.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. #!/usr/bin/env node
  2. /**
  3. * @param {string} command process to run
  4. * @param {string[]} args command line arguments
  5. * @returns {Promise<void>} promise
  6. */
  7. const runCommand = (command, args) => {
  8. const cp = require("child_process");
  9. return new Promise((resolve, reject) => {
  10. const executedCommand = cp.spawn(command, args, {
  11. stdio: "inherit",
  12. shell: true
  13. });
  14. executedCommand.on("error", error => {
  15. reject(error);
  16. });
  17. executedCommand.on("exit", code => {
  18. if (code === 0) {
  19. resolve();
  20. } else {
  21. reject();
  22. }
  23. });
  24. });
  25. };
  26. /**
  27. * @param {string} packageName name of the package
  28. * @returns {boolean} is the package installed?
  29. */
  30. const isInstalled = packageName => {
  31. if (process.versions.pnp) {
  32. return true;
  33. }
  34. const path = require("path");
  35. const fs = require("graceful-fs");
  36. let dir = __dirname;
  37. do {
  38. try {
  39. if (
  40. fs.statSync(path.join(dir, "node_modules", packageName)).isDirectory()
  41. ) {
  42. return true;
  43. }
  44. } catch (_error) {
  45. // Nothing
  46. }
  47. } while (dir !== (dir = path.dirname(dir)));
  48. // https://github.com/nodejs/node/blob/v18.9.1/lib/internal/modules/cjs/loader.js#L1274
  49. // eslint-disable-next-line no-warning-comments
  50. // @ts-ignore
  51. for (const internalPath of require("module").globalPaths) {
  52. try {
  53. if (fs.statSync(path.join(internalPath, packageName)).isDirectory()) {
  54. return true;
  55. }
  56. } catch (_error) {
  57. // Nothing
  58. }
  59. }
  60. return false;
  61. };
  62. /**
  63. * @param {CliOption} cli options
  64. * @returns {void}
  65. */
  66. const runCli = cli => {
  67. const path = require("path");
  68. const pkgPath = require.resolve(`${cli.package}/package.json`);
  69. // eslint-disable-next-line node/no-missing-require
  70. const pkg = require(pkgPath);
  71. // eslint-disable-next-line node/no-missing-require
  72. require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
  73. };
  74. /**
  75. * @typedef {Object} CliOption
  76. * @property {string} name display name
  77. * @property {string} package npm package name
  78. * @property {string} binName name of the executable file
  79. * @property {boolean} installed currently installed?
  80. * @property {string} url homepage
  81. */
  82. /** @type {CliOption} */
  83. const cli = {
  84. name: "webpack-cli",
  85. package: "webpack-cli",
  86. binName: "webpack-cli",
  87. installed: isInstalled("webpack-cli"),
  88. url: "https://github.com/webpack/webpack-cli"
  89. };
  90. if (!cli.installed) {
  91. const path = require("path");
  92. const fs = require("graceful-fs");
  93. const readLine = require("readline");
  94. const notify =
  95. "CLI for webpack must be installed.\n" + ` ${cli.name} (${cli.url})\n`;
  96. console.error(notify);
  97. let packageManager;
  98. if (fs.existsSync(path.resolve(process.cwd(), "yarn.lock"))) {
  99. packageManager = "yarn";
  100. } else if (fs.existsSync(path.resolve(process.cwd(), "pnpm-lock.yaml"))) {
  101. packageManager = "pnpm";
  102. } else {
  103. packageManager = "npm";
  104. }
  105. const installOptions = [packageManager === "yarn" ? "add" : "install", "-D"];
  106. console.error(
  107. `We will use "${packageManager}" to install the CLI via "${packageManager} ${installOptions.join(
  108. " "
  109. )} ${cli.package}".`
  110. );
  111. const question = `Do you want to install 'webpack-cli' (yes/no): `;
  112. const questionInterface = readLine.createInterface({
  113. input: process.stdin,
  114. output: process.stderr
  115. });
  116. // In certain scenarios (e.g. when STDIN is not in terminal mode), the callback function will not be
  117. // executed. Setting the exit code here to ensure the script exits correctly in those cases. The callback
  118. // function is responsible for clearing the exit code if the user wishes to install webpack-cli.
  119. process.exitCode = 1;
  120. questionInterface.question(question, answer => {
  121. questionInterface.close();
  122. const normalizedAnswer = answer.toLowerCase().startsWith("y");
  123. if (!normalizedAnswer) {
  124. console.error(
  125. "You need to install 'webpack-cli' to use webpack via CLI.\n" +
  126. "You can also install the CLI manually."
  127. );
  128. return;
  129. }
  130. process.exitCode = 0;
  131. console.log(
  132. `Installing '${
  133. cli.package
  134. }' (running '${packageManager} ${installOptions.join(" ")} ${
  135. cli.package
  136. }')...`
  137. );
  138. runCommand(packageManager, installOptions.concat(cli.package))
  139. .then(() => {
  140. runCli(cli);
  141. })
  142. .catch(error => {
  143. console.error(error);
  144. process.exitCode = 1;
  145. });
  146. });
  147. } else {
  148. runCli(cli);
  149. }