webpack-dev-server.js 4.2 KB

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