webpack.js 4.8 KB

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