cli.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. #!/usr/bin/env node
  2. // @ts-check
  3. const { Command } = require('commander');
  4. const { spawn } = require('child_process');
  5. const path = require('path');
  6. const pkg = require('../package.json');
  7. const { assertSupportedNodeVersion } = require('../src/Engine.js');
  8. run().catch(err => {
  9. console.error(err);
  10. process.exitCode = process.exitCode || 1;
  11. process.exit();
  12. });
  13. /**
  14. * Run the program.
  15. */
  16. async function run() {
  17. const program = new Command();
  18. program.name('mix');
  19. program.version(pkg.version);
  20. program.option(
  21. '--mix-config <path>',
  22. 'The path to your Mix configuration file.',
  23. 'webpack.mix'
  24. );
  25. program.option('--no-progress', 'Disable progress reporting', false);
  26. program
  27. .command('watch')
  28. .description('Build and watch files for changes.')
  29. .option('--hot', 'Enable hot reloading.', false)
  30. .option('--https', 'Enable https.', false)
  31. .action((opts, cmd) =>
  32. executeScript('watch', { ...program.opts(), ...opts }, cmd.args)
  33. );
  34. program
  35. .command('build', { isDefault: true })
  36. .description('Compile Mix.')
  37. .option('-p, --production', 'Run Mix in production mode.', false)
  38. .action((opts, cmd) =>
  39. executeScript('build', { ...program.opts(), ...opts }, cmd.args)
  40. );
  41. await program.parseAsync(process.argv);
  42. }
  43. /**
  44. * Execute the script.
  45. *
  46. * @param {"build"|"watch"} cmd
  47. * @param {{[key: string]: any}} opts
  48. * @param {string[]} args
  49. */
  50. async function executeScript(cmd, opts, args = []) {
  51. assertSupportedNodeVersion();
  52. const env = getEffectiveEnv(opts);
  53. // We MUST use a relative path because the files
  54. // created by npm dont correctly handle paths
  55. // containg spaces on Windows (yarn does)
  56. const configPath = path.relative(
  57. process.cwd(),
  58. require.resolve('../setup/webpack.config.js')
  59. );
  60. const script = [
  61. commandScript(cmd, opts),
  62. `--config="${configPath}"`,
  63. ...quoteArgs(args)
  64. ].join(' ');
  65. const scriptEnv = {
  66. NODE_ENV: env,
  67. MIX_FILE: opts.mixConfig
  68. };
  69. const nodeEnv = requiresLegacyOpenSSLProvider()
  70. ? { NODE_OPTIONS: process.env.NODE_OPTIONS || `--openssl-legacy-provider` }
  71. : {};
  72. if (isTesting()) {
  73. process.stdout.write(
  74. JSON.stringify({
  75. script,
  76. env: scriptEnv
  77. })
  78. );
  79. return;
  80. }
  81. function restart() {
  82. let child = spawn(script, {
  83. stdio: 'inherit',
  84. shell: true,
  85. env: {
  86. ...process.env,
  87. ...nodeEnv,
  88. ...scriptEnv
  89. }
  90. });
  91. let shouldOverwriteExitCode = true;
  92. child.on('exit', (code, signal) => {
  93. // Note adapted from cross-env:
  94. // https://github.com/kentcdodds/cross-env/blob/3edefc7b450fe273655664f902fd03d9712177fe/src/index.js#L30-L31
  95. // The process exit code can be null when killed by the OS (like an out of memory error) or sometimes by node
  96. // SIGINT means the _user_ pressed Ctrl-C to interrupt the process execution
  97. // Return the appropriate error code in that case
  98. if (code === null) {
  99. code = signal === 'SIGINT' ? 130 : 1;
  100. }
  101. if (shouldOverwriteExitCode) {
  102. process.exitCode = code;
  103. }
  104. });
  105. process.on('SIGINT', () => {
  106. shouldOverwriteExitCode = false;
  107. child.kill('SIGINT');
  108. });
  109. process.on('SIGTERM', () => {
  110. shouldOverwriteExitCode = false;
  111. child.kill('SIGTERM');
  112. });
  113. }
  114. restart();
  115. }
  116. /**
  117. * Get the command-specific portion of the script.
  118. *
  119. * @param {"build"|"watch"} cmd
  120. * @param {{[key: string]: any}} opts
  121. */
  122. function commandScript(cmd, opts) {
  123. const showProgress = isTTY() && opts.progress;
  124. const script = ['webpack'];
  125. if (cmd === 'build' && showProgress) {
  126. script.push('--progress');
  127. } else if (cmd === 'watch' && !opts.hot) {
  128. script.push('--watch');
  129. if (showProgress) {
  130. script.push('--progress');
  131. }
  132. } else if (cmd === 'watch' && opts.hot) {
  133. script.push('serve', '--hot');
  134. if (opts.https) {
  135. script.push('--https');
  136. }
  137. }
  138. return script.join(' ');
  139. }
  140. /**
  141. * Get the command arguments with quoted values.
  142. *
  143. * @param {string[]} args
  144. */
  145. function quoteArgs(args) {
  146. return args.map(arg => {
  147. // Split string at first = only
  148. const pattern = /^([^=]+)=(.*)$/;
  149. const keyValue = arg.includes('=') ? pattern.exec(arg).slice(1) : [];
  150. if (keyValue.length === 2) {
  151. return `${keyValue[0]}="${keyValue[1]}"`;
  152. }
  153. return arg;
  154. });
  155. }
  156. /**
  157. * Get the effective envirnoment to run in
  158. *
  159. ** @param {{[key: string]: any}} opts
  160. */
  161. function getEffectiveEnv(opts) {
  162. // If we've requested a production compile we enforce use of the production env
  163. // If we don't a user's global NODE_ENV may override and prevent minification of assets
  164. if (opts.production) {
  165. return 'production';
  166. }
  167. // We use `development` by default or under certain specific conditions when testing
  168. if (!process.env.NODE_ENV || (isTesting() && process.env.NODE_ENV === 'test')) {
  169. return 'development';
  170. }
  171. // Otherwsise defer to the current value of NODE_ENV
  172. return process.env.NODE_ENV;
  173. }
  174. function isTesting() {
  175. return process.env.TESTING;
  176. }
  177. function isTTY() {
  178. if (isTesting() && process.env.IS_TTY !== undefined) {
  179. return process.env.IS_TTY === 'true';
  180. }
  181. if (isTesting() && process.stdout.isTTY === undefined) {
  182. return true;
  183. }
  184. return process.stdout.isTTY;
  185. }
  186. function requiresLegacyOpenSSLProvider() {
  187. if (!process.version.startsWith('v17.') && !process.version.startsWith('v18.')) {
  188. return false;
  189. }
  190. try {
  191. require('crypto').createHash('md4').update('test').digest('hex');
  192. return false;
  193. } catch (err) {
  194. return true;
  195. }
  196. }