1 | const fs = require('fs');
|
2 | const path = require('path');
|
3 | const { pathToFileURL } = require('url');
|
4 | const Module = require('module');
|
5 |
|
6 | const { program } = require('commander');
|
7 | const utils = require('./utils');
|
8 |
|
9 | class WebpackCLI {
|
10 | constructor() {
|
11 |
|
12 | this.webpack = require('webpack');
|
13 | this.logger = utils.logger;
|
14 | this.utils = utils;
|
15 |
|
16 |
|
17 | this.program = program;
|
18 | this.program.name('webpack');
|
19 | this.program.configureOutput({
|
20 | writeErr: this.logger.error,
|
21 | outputError: (str, write) => write(`Error: ${this.utils.capitalizeFirstLetter(str.replace(/^error:/, '').trim())}`),
|
22 | });
|
23 | }
|
24 |
|
25 | async makeCommand(commandOptions, options, action) {
|
26 | const alreadyLoaded = this.program.commands.find(
|
27 | (command) => command.name() === commandOptions.name || command.aliases().includes(commandOptions.alias),
|
28 | );
|
29 |
|
30 | if (alreadyLoaded) {
|
31 | return;
|
32 | }
|
33 |
|
34 | const command = this.program.command(commandOptions.name, {
|
35 | noHelp: commandOptions.noHelp,
|
36 | hidden: commandOptions.hidden,
|
37 | isDefault: commandOptions.isDefault,
|
38 | });
|
39 |
|
40 | if (commandOptions.description) {
|
41 | command.description(commandOptions.description);
|
42 | }
|
43 |
|
44 | if (commandOptions.usage) {
|
45 | command.usage(commandOptions.usage);
|
46 | }
|
47 |
|
48 | if (Array.isArray(commandOptions.alias)) {
|
49 | command.aliases(commandOptions.alias);
|
50 | } else {
|
51 | command.alias(commandOptions.alias);
|
52 | }
|
53 |
|
54 | if (commandOptions.pkg) {
|
55 | command.pkg = commandOptions.pkg;
|
56 | } else {
|
57 | command.pkg = 'webpack-cli';
|
58 | }
|
59 |
|
60 | const { forHelp } = this.program;
|
61 |
|
62 | let allDependenciesInstalled = true;
|
63 |
|
64 | if (commandOptions.dependencies && commandOptions.dependencies.length > 0) {
|
65 | for (const dependency of commandOptions.dependencies) {
|
66 | const { packageExists } = this.utils;
|
67 | const isPkgExist = packageExists(dependency);
|
68 |
|
69 | if (isPkgExist) {
|
70 | continue;
|
71 | } else if (!isPkgExist && forHelp) {
|
72 | allDependenciesInstalled = false;
|
73 | continue;
|
74 | }
|
75 |
|
76 | const { promptInstallation, colors } = this.utils;
|
77 |
|
78 | try {
|
79 | await promptInstallation(dependency, () => {
|
80 | this.logger.error(
|
81 | `For using '${colors.green(commandOptions.name.split(' ')[0])}' command you need to install: '${colors.green(
|
82 | dependency,
|
83 | )}' package`,
|
84 | );
|
85 | });
|
86 | } catch (error) {
|
87 | this.logger.error("Action Interrupted, use 'webpack-cli help' to see possible commands.");
|
88 | this.logger.error(error);
|
89 | process.exit(2);
|
90 | }
|
91 | }
|
92 | }
|
93 |
|
94 | if (options) {
|
95 | if (typeof options === 'function') {
|
96 | if (forHelp && !allDependenciesInstalled) {
|
97 | command.description(
|
98 | `${commandOptions.description} To see all available options you need to install ${commandOptions.dependencies
|
99 | .map((dependency) => `'${dependency}'`)
|
100 | .join(',')}.`,
|
101 | );
|
102 | options = [];
|
103 | } else {
|
104 | options = options();
|
105 | }
|
106 | }
|
107 |
|
108 | options.forEach((optionForCommand) => {
|
109 | this.makeOption(command, optionForCommand);
|
110 | });
|
111 | }
|
112 |
|
113 | command.action(action);
|
114 |
|
115 | return command;
|
116 | }
|
117 |
|
118 | makeOption(command, option) {
|
119 | let type = option.type;
|
120 | let isMultipleTypes = Array.isArray(type);
|
121 | let isOptional = false;
|
122 |
|
123 | if (isMultipleTypes) {
|
124 | if (type.length === 1) {
|
125 | type = type[0];
|
126 | isMultipleTypes = false;
|
127 | } else {
|
128 | isOptional = type.includes(Boolean);
|
129 | }
|
130 | }
|
131 |
|
132 | const isMultiple = option.multiple;
|
133 | const isRequired = type !== Boolean && typeof type !== 'undefined';
|
134 |
|
135 | let flags = option.alias ? `-${option.alias}, --${option.name}` : `--${option.name}`;
|
136 |
|
137 | if (isOptional) {
|
138 |
|
139 | flags = `${flags} [value${isMultiple ? '...' : ''}]`;
|
140 | } else if (isRequired) {
|
141 |
|
142 | flags = `${flags} <value${isMultiple ? '...' : ''}>`;
|
143 | }
|
144 |
|
145 |
|
146 | const description = option.description || option.describe || '';
|
147 | const defaultValue = option.defaultValue;
|
148 |
|
149 | if (type === Boolean) {
|
150 | command.option(flags, description, defaultValue);
|
151 | } else if (type === Number) {
|
152 | let skipDefault = true;
|
153 |
|
154 | command.option(
|
155 | flags,
|
156 | description,
|
157 | (value, prev = []) => {
|
158 | if (defaultValue && isMultiple && skipDefault) {
|
159 | prev = [];
|
160 | skipDefault = false;
|
161 | }
|
162 |
|
163 | return isMultiple ? [].concat(prev).concat(Number(value)) : Number(value);
|
164 | },
|
165 | defaultValue,
|
166 | );
|
167 | } else if (type === String) {
|
168 | let skipDefault = true;
|
169 |
|
170 | command.option(
|
171 | flags,
|
172 | description,
|
173 | (value, prev = []) => {
|
174 | if (defaultValue && isMultiple && skipDefault) {
|
175 | prev = [];
|
176 | skipDefault = false;
|
177 | }
|
178 |
|
179 | return isMultiple ? [].concat(prev).concat(value) : value;
|
180 | },
|
181 | defaultValue,
|
182 | );
|
183 | } else if (isMultipleTypes) {
|
184 | let skipDefault = true;
|
185 |
|
186 | command.option(
|
187 | flags,
|
188 | description,
|
189 | (value, prev = []) => {
|
190 | if (defaultValue && isMultiple && skipDefault) {
|
191 | prev = [];
|
192 | skipDefault = false;
|
193 | }
|
194 |
|
195 | if (type.includes(Number)) {
|
196 | const numberValue = Number(value);
|
197 |
|
198 | if (!isNaN(numberValue)) {
|
199 | return isMultiple ? [].concat(prev).concat(numberValue) : numberValue;
|
200 | }
|
201 | }
|
202 |
|
203 | if (type.includes(String)) {
|
204 | return isMultiple ? [].concat(prev).concat(value) : value;
|
205 | }
|
206 |
|
207 | return value;
|
208 | },
|
209 | defaultValue,
|
210 | );
|
211 | } else {
|
212 | command.option(flags, description, type, defaultValue);
|
213 | }
|
214 |
|
215 | if (option.negative) {
|
216 |
|
217 | const negatedFlag = `--no-${option.name}`;
|
218 |
|
219 | command.option(negatedFlag, option.negatedDescription ? option.negatedDescription : `Negative '${option.name}' option.`);
|
220 | }
|
221 | }
|
222 |
|
223 | getBuiltInOptions() {
|
224 | if (this.builtInOptionsCache) {
|
225 | return this.builtInOptionsCache;
|
226 | }
|
227 |
|
228 | const minimumHelpFlags = [
|
229 | 'config',
|
230 | 'config-name',
|
231 | 'merge',
|
232 | 'env',
|
233 | 'mode',
|
234 | 'watch',
|
235 | 'watch-options-stdin',
|
236 | 'stats',
|
237 | 'devtool',
|
238 | 'entry',
|
239 | 'target',
|
240 | 'progress',
|
241 | 'json',
|
242 | 'name',
|
243 | 'output-path',
|
244 | ];
|
245 |
|
246 | const builtInFlags = [
|
247 |
|
248 | {
|
249 | name: 'config',
|
250 | alias: 'c',
|
251 | type: String,
|
252 | multiple: true,
|
253 | description: 'Provide path to a webpack configuration file e.g. ./webpack.config.js.',
|
254 | },
|
255 | {
|
256 | name: 'config-name',
|
257 | type: String,
|
258 | multiple: true,
|
259 | description: 'Name of the configuration to use.',
|
260 | },
|
261 | {
|
262 | name: 'merge',
|
263 | alias: 'm',
|
264 | type: Boolean,
|
265 | description: "Merge two or more configurations using 'webpack-merge'.",
|
266 | },
|
267 |
|
268 | {
|
269 | name: 'env',
|
270 | type: (value, previous = {}) => {
|
271 |
|
272 | const [allKeys, val] = value.split(/=(.+)/, 2);
|
273 | const splitKeys = allKeys.split(/\.(?!$)/);
|
274 |
|
275 | let prevRef = previous;
|
276 |
|
277 | splitKeys.forEach((someKey, index) => {
|
278 | if (!prevRef[someKey]) {
|
279 | prevRef[someKey] = {};
|
280 | }
|
281 |
|
282 | if (typeof prevRef[someKey] === 'string') {
|
283 | prevRef[someKey] = {};
|
284 | }
|
285 |
|
286 | if (index === splitKeys.length - 1) {
|
287 | prevRef[someKey] = val || true;
|
288 | }
|
289 |
|
290 | prevRef = prevRef[someKey];
|
291 | });
|
292 |
|
293 | return previous;
|
294 | },
|
295 | multiple: true,
|
296 | description: 'Environment passed to the configuration when it is a function.',
|
297 | },
|
298 | {
|
299 | name: 'node-env',
|
300 | type: String,
|
301 | multiple: false,
|
302 | description: 'Sets process.env.NODE_ENV to the specified value',
|
303 | },
|
304 |
|
305 |
|
306 | {
|
307 | name: 'hot',
|
308 | alias: 'h',
|
309 | type: Boolean,
|
310 | negative: true,
|
311 | description: 'Enables Hot Module Replacement',
|
312 | negatedDescription: 'Disables Hot Module Replacement.',
|
313 | },
|
314 | {
|
315 | name: 'analyze',
|
316 | type: Boolean,
|
317 | multiple: false,
|
318 | description: 'It invokes webpack-bundle-analyzer plugin to get bundle information.',
|
319 | },
|
320 | {
|
321 | name: 'progress',
|
322 | type: [Boolean, String],
|
323 | description: 'Print compilation progress during build.',
|
324 | },
|
325 | {
|
326 | name: 'prefetch',
|
327 | type: String,
|
328 | description: 'Prefetch this request.',
|
329 | },
|
330 |
|
331 |
|
332 | {
|
333 | name: 'json',
|
334 | type: [String, Boolean],
|
335 | alias: 'j',
|
336 | description: 'Prints result as JSON or store it in a file.',
|
337 | },
|
338 |
|
339 |
|
340 | {
|
341 | name: 'entry',
|
342 | type: String,
|
343 | multiple: true,
|
344 | description: 'The entry point(s) of your application e.g. ./src/main.js.',
|
345 | },
|
346 | {
|
347 | name: 'output-path',
|
348 | alias: 'o',
|
349 | type: String,
|
350 | description: 'Output location of the file generated by webpack e.g. ./dist/.',
|
351 | },
|
352 | {
|
353 | name: 'target',
|
354 | alias: 't',
|
355 | type: String,
|
356 | multiple: this.webpack.cli !== undefined,
|
357 | description: 'Sets the build target e.g. node.',
|
358 | },
|
359 | {
|
360 | name: 'devtool',
|
361 | type: String,
|
362 | negative: true,
|
363 | alias: 'd',
|
364 | description: 'Determine source maps to use.',
|
365 | negatedDescription: 'Do not generate source maps.',
|
366 | },
|
367 | {
|
368 | name: 'mode',
|
369 | type: String,
|
370 | description: 'Defines the mode to pass to webpack.',
|
371 | },
|
372 | {
|
373 | name: 'name',
|
374 | type: String,
|
375 | description: 'Name of the configuration. Used when loading multiple configurations.',
|
376 | },
|
377 | {
|
378 | name: 'stats',
|
379 | type: [String, Boolean],
|
380 | negative: true,
|
381 | description: 'It instructs webpack on how to treat the stats e.g. verbose.',
|
382 | negatedDescription: 'Disable stats output.',
|
383 | },
|
384 | {
|
385 | name: 'watch',
|
386 | type: Boolean,
|
387 | negative: true,
|
388 | alias: 'w',
|
389 | description: 'Watch for files changes.',
|
390 | negatedDescription: 'Do not watch for file changes.',
|
391 | },
|
392 | {
|
393 | name: 'watch-options-stdin',
|
394 | type: Boolean,
|
395 | negative: true,
|
396 | description: 'Stop watching when stdin stream has ended.',
|
397 | negatedDescription: 'Do not stop watching when stdin stream has ended.',
|
398 | },
|
399 | ];
|
400 |
|
401 |
|
402 |
|
403 | const coreFlags = this.webpack.cli
|
404 | ? Object.entries(this.webpack.cli.getArguments()).map(([flag, meta]) => {
|
405 | if (meta.simpleType === 'string') {
|
406 | meta.type = String;
|
407 | } else if (meta.simpleType === 'number') {
|
408 | meta.type = Number;
|
409 | } else {
|
410 | meta.type = Boolean;
|
411 | meta.negative = !flag.endsWith('-reset');
|
412 | }
|
413 |
|
414 | const inBuiltIn = builtInFlags.find((builtInFlag) => builtInFlag.name === flag);
|
415 |
|
416 | if (inBuiltIn) {
|
417 | return { ...meta, name: flag, group: 'core', ...inBuiltIn };
|
418 | }
|
419 |
|
420 | return { ...meta, name: flag, group: 'core' };
|
421 | })
|
422 | : [];
|
423 |
|
424 | const options = []
|
425 | .concat(builtInFlags.filter((builtInFlag) => !coreFlags.find((coreFlag) => builtInFlag.name === coreFlag.name)))
|
426 | .concat(coreFlags)
|
427 | .map((option) => {
|
428 | option.help = minimumHelpFlags.includes(option.name) ? 'minimum' : 'verbose';
|
429 |
|
430 | return option;
|
431 | });
|
432 |
|
433 | this.builtInOptionsCache = options;
|
434 |
|
435 | return options;
|
436 | }
|
437 |
|
438 | applyNodeEnv(options) {
|
439 | if (typeof options.nodeEnv === 'string') {
|
440 | process.env.NODE_ENV = options.nodeEnv;
|
441 | }
|
442 | }
|
443 |
|
444 | async run(args, parseOptions) {
|
445 |
|
446 | const buildCommandOptions = {
|
447 | name: 'build [entries...]',
|
448 | alias: ['bundle', 'b'],
|
449 | description: 'Run webpack (default command, can be omitted).',
|
450 | usage: '[entries...] [options]',
|
451 | };
|
452 | const watchCommandOptions = {
|
453 | name: 'watch [entries...]',
|
454 | alias: 'w',
|
455 | description: 'Run webpack and watch for files changes.',
|
456 | usage: '[entries...] [options]',
|
457 | };
|
458 | const versionCommandOptions = {
|
459 | name: 'version [commands...]',
|
460 | alias: 'v',
|
461 | description: "Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands.",
|
462 | };
|
463 | const helpCommandOptions = {
|
464 | name: 'help [command] [option]',
|
465 | alias: 'h',
|
466 | description: 'Display help for commands and options.',
|
467 | };
|
468 |
|
469 | const externalBuiltInCommandsInfo = [
|
470 | {
|
471 | name: 'serve [entries...]',
|
472 | alias: 's',
|
473 | pkg: '@webpack-cli/serve',
|
474 | },
|
475 | {
|
476 | name: 'info',
|
477 | alias: 'i',
|
478 | pkg: '@webpack-cli/info',
|
479 | },
|
480 | {
|
481 | name: 'init',
|
482 | alias: 'c',
|
483 | pkg: '@webpack-cli/init',
|
484 | },
|
485 | {
|
486 | name: 'loader',
|
487 | alias: 'l',
|
488 | pkg: '@webpack-cli/generators',
|
489 | },
|
490 | {
|
491 | name: 'plugin',
|
492 | alias: 'p',
|
493 | pkg: '@webpack-cli/generators',
|
494 | },
|
495 | {
|
496 | name: 'migrate',
|
497 | alias: 'm',
|
498 | pkg: '@webpack-cli/migrate',
|
499 | },
|
500 | {
|
501 | name: 'configtest [config-path]',
|
502 | alias: 't',
|
503 | pkg: '@webpack-cli/configtest',
|
504 | },
|
505 | ];
|
506 |
|
507 | const knownCommands = [
|
508 | buildCommandOptions,
|
509 | watchCommandOptions,
|
510 | versionCommandOptions,
|
511 | helpCommandOptions,
|
512 | ...externalBuiltInCommandsInfo,
|
513 | ];
|
514 | const getCommandName = (name) => name.split(' ')[0];
|
515 | const isKnownCommand = (name) =>
|
516 | knownCommands.find(
|
517 | (command) =>
|
518 | getCommandName(command.name) === name ||
|
519 | (Array.isArray(command.alias) ? command.alias.includes(name) : command.alias === name),
|
520 | );
|
521 | const isCommand = (input, commandOptions) => {
|
522 | const longName = getCommandName(commandOptions.name);
|
523 |
|
524 | if (input === longName) {
|
525 | return true;
|
526 | }
|
527 |
|
528 | if (commandOptions.alias) {
|
529 | if (Array.isArray(commandOptions.alias)) {
|
530 | return commandOptions.alias.includes(input);
|
531 | } else {
|
532 | return commandOptions.alias === input;
|
533 | }
|
534 | }
|
535 |
|
536 | return false;
|
537 | };
|
538 | const findCommandByName = (name) =>
|
539 | this.program.commands.find((command) => name === command.name() || command.alias().includes(name));
|
540 | const isOption = (value) => value.startsWith('-');
|
541 | const isGlobalOption = (value) =>
|
542 | value === '--color' ||
|
543 | value === '--no-color' ||
|
544 | value === '-v' ||
|
545 | value === '--version' ||
|
546 | value === '-h' ||
|
547 | value === '--help';
|
548 |
|
549 | const loadCommandByName = async (commandName, allowToInstall = false) => {
|
550 | const isBuildCommandUsed = isCommand(commandName, buildCommandOptions);
|
551 | const isWatchCommandUsed = isCommand(commandName, watchCommandOptions);
|
552 |
|
553 | if (isBuildCommandUsed || isWatchCommandUsed) {
|
554 | const options = this.getBuiltInOptions();
|
555 |
|
556 | await this.makeCommand(
|
557 | isBuildCommandUsed ? buildCommandOptions : watchCommandOptions,
|
558 | isWatchCommandUsed ? options.filter((option) => option.name !== 'watch') : options,
|
559 | async (entries, options) => {
|
560 | if (entries.length > 0) {
|
561 | options.entry = [...entries, ...(options.entry || [])];
|
562 | }
|
563 |
|
564 | await this.buildCommand(options, isWatchCommandUsed);
|
565 | },
|
566 | );
|
567 | } else if (isCommand(commandName, helpCommandOptions)) {
|
568 |
|
569 | this.makeCommand(helpCommandOptions, [], () => {});
|
570 | } else if (isCommand(commandName, versionCommandOptions)) {
|
571 |
|
572 | this.makeCommand(versionCommandOptions, [], () => {});
|
573 | } else {
|
574 | const builtInExternalCommandInfo = externalBuiltInCommandsInfo.find(
|
575 | (externalBuiltInCommandInfo) =>
|
576 | getCommandName(externalBuiltInCommandInfo.name) === commandName ||
|
577 | (Array.isArray(externalBuiltInCommandInfo.alias)
|
578 | ? externalBuiltInCommandInfo.alias.includes(commandName)
|
579 | : externalBuiltInCommandInfo.alias === commandName),
|
580 | );
|
581 |
|
582 | let pkg;
|
583 |
|
584 | if (builtInExternalCommandInfo) {
|
585 | ({ pkg } = builtInExternalCommandInfo);
|
586 | } else {
|
587 | pkg = commandName;
|
588 | }
|
589 |
|
590 | if (pkg !== 'webpack-cli' && !this.utils.packageExists(pkg)) {
|
591 | if (!allowToInstall) {
|
592 | return;
|
593 | }
|
594 |
|
595 | const { promptInstallation, colors } = this.utils;
|
596 |
|
597 | try {
|
598 | pkg = await promptInstallation(pkg, () => {
|
599 | this.logger.error(`For using this command you need to install: '${colors.green(pkg)}' package`);
|
600 | });
|
601 | } catch (error) {
|
602 | this.logger.error(`Action Interrupted, use '${colors.cyan('webpack-cli help')}' to see possible commands`);
|
603 | process.exit(2);
|
604 | }
|
605 | }
|
606 |
|
607 | let loadedCommand;
|
608 |
|
609 | try {
|
610 | loadedCommand = require(pkg);
|
611 | } catch (error) {
|
612 |
|
613 |
|
614 | return;
|
615 | }
|
616 |
|
617 | if (loadedCommand.default) {
|
618 | loadedCommand = loadedCommand.default;
|
619 | }
|
620 |
|
621 | let command;
|
622 |
|
623 | try {
|
624 | command = new loadedCommand();
|
625 |
|
626 | await command.apply(this);
|
627 | } catch (error) {
|
628 | this.logger.error(`Unable to load '${pkg}' command`);
|
629 | this.logger.error(error);
|
630 | process.exit(2);
|
631 | }
|
632 | }
|
633 | };
|
634 |
|
635 |
|
636 | this.program.exitOverride(async (error) => {
|
637 | if (error.exitCode === 0) {
|
638 | process.exit(0);
|
639 | }
|
640 |
|
641 | if (error.code === 'executeSubCommandAsync') {
|
642 | process.exit(2);
|
643 | }
|
644 |
|
645 | if (error.code === 'commander.help') {
|
646 | process.exit(0);
|
647 | }
|
648 |
|
649 | if (error.code === 'commander.unknownOption') {
|
650 | let name = error.message.match(/'(.+)'/);
|
651 |
|
652 | if (name) {
|
653 | name = name[1].substr(2);
|
654 |
|
655 | if (name.includes('=')) {
|
656 | name = name.split('=')[0];
|
657 | }
|
658 |
|
659 | const { operands } = this.program.parseOptions(this.program.args);
|
660 | const operand = typeof operands[0] !== 'undefined' ? operands[0] : getCommandName(buildCommandOptions.name);
|
661 |
|
662 | if (operand) {
|
663 | const command = findCommandByName(operand);
|
664 |
|
665 | if (!command) {
|
666 | this.logger.error(`Can't find and load command '${operand}'`);
|
667 | this.logger.error("Run 'webpack --help' to see available commands and options");
|
668 | process.exit(2);
|
669 | }
|
670 |
|
671 | command.options.forEach((option) => {
|
672 | if (this.utils.levenshtein.distance(name, option.long.slice(2)) < 3) {
|
673 | this.logger.error(`Did you mean '--${option.name()}'?`);
|
674 | }
|
675 | });
|
676 | }
|
677 | }
|
678 | }
|
679 |
|
680 |
|
681 |
|
682 |
|
683 |
|
684 |
|
685 |
|
686 | this.logger.error("Run 'webpack --help' to see available commands and options");
|
687 | process.exit(2);
|
688 | });
|
689 |
|
690 |
|
691 | const cli = this;
|
692 | this.program.option('--color', 'Enable colors on console.');
|
693 | this.program.on('option:color', function () {
|
694 | const { color } = this.opts();
|
695 |
|
696 | cli.utils.colors.options.changed = true;
|
697 | cli.utils.colors.options.enabled = color;
|
698 | });
|
699 | this.program.option('--no-color', 'Disable colors on console.');
|
700 | this.program.on('option:no-color', function () {
|
701 | const { color } = this.opts();
|
702 |
|
703 | cli.utils.colors.options.changed = true;
|
704 | cli.utils.colors.options.enabled = color;
|
705 | });
|
706 |
|
707 |
|
708 |
|
709 | const outputVersion = async (options) => {
|
710 |
|
711 | const possibleCommandNames = options.filter(
|
712 | (option) =>
|
713 | !isCommand(option, buildCommandOptions) &&
|
714 | !isCommand(option, watchCommandOptions) &&
|
715 | !isCommand(option, versionCommandOptions) &&
|
716 | !isCommand(option, helpCommandOptions),
|
717 | );
|
718 |
|
719 | possibleCommandNames.forEach((possibleCommandName) => {
|
720 | if (!isOption(possibleCommandName)) {
|
721 | return;
|
722 | }
|
723 |
|
724 | this.logger.error(`Unknown option '${possibleCommandName}'`);
|
725 | this.logger.error("Run 'webpack --help' to see available commands and options");
|
726 | process.exit(2);
|
727 | });
|
728 |
|
729 | if (possibleCommandNames.length > 0) {
|
730 | await Promise.all(possibleCommandNames.map((possibleCommand) => loadCommandByName(possibleCommand)));
|
731 |
|
732 | for (const possibleCommandName of possibleCommandNames) {
|
733 | const foundCommand = findCommandByName(possibleCommandName);
|
734 |
|
735 | if (!foundCommand) {
|
736 | this.logger.error(`Unknown command '${possibleCommandName}'`);
|
737 | this.logger.error("Run 'webpack --help' to see available commands and options");
|
738 | process.exit(2);
|
739 | }
|
740 |
|
741 | try {
|
742 | const { name, version } = require(`${foundCommand.pkg}/package.json`);
|
743 |
|
744 | this.logger.raw(`${name} ${version}`);
|
745 | } catch (e) {
|
746 | this.logger.error(`Error: External package '${foundCommand.pkg}' not found`);
|
747 | process.exit(2);
|
748 | }
|
749 | }
|
750 | }
|
751 |
|
752 | const pkgJSON = require('../package.json');
|
753 |
|
754 | this.logger.raw(`webpack ${this.webpack.version}`);
|
755 | this.logger.raw(`webpack-cli ${pkgJSON.version}`);
|
756 |
|
757 | if (this.utils.packageExists('webpack-dev-server')) {
|
758 |
|
759 | const { version } = require('webpack-dev-server/package.json');
|
760 |
|
761 | this.logger.raw(`webpack-dev-server ${version}`);
|
762 | }
|
763 |
|
764 | process.exit(0);
|
765 | };
|
766 | this.program.option(
|
767 | '-v, --version',
|
768 | "Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands.",
|
769 | );
|
770 |
|
771 | const outputHelp = async (options, isVerbose, isHelpCommandSyntax, program) => {
|
772 | const { bold } = this.utils.colors;
|
773 |
|
774 | const outputIncorrectUsageOfHelp = () => {
|
775 | this.logger.error('Incorrect use of help');
|
776 | this.logger.error("Please use: 'webpack help [command] [option]' | 'webpack [command] --help'");
|
777 | this.logger.error("Run 'webpack --help' to see available commands and options");
|
778 | process.exit(2);
|
779 | };
|
780 |
|
781 | const isGlobalHelp = options.length === 0;
|
782 | const isCommandHelp = options.length === 1 && !isOption(options[0]);
|
783 |
|
784 | if (isGlobalHelp || isCommandHelp) {
|
785 | const cliAPI = this;
|
786 |
|
787 | program.configureHelp({
|
788 | sortSubcommands: true,
|
789 |
|
790 | commandUsage: (command) => {
|
791 | let parentCmdNames = '';
|
792 |
|
793 | for (let parentCmd = command.parent; parentCmd; parentCmd = parentCmd.parent) {
|
794 | parentCmdNames = `${parentCmd.name()} ${parentCmdNames}`;
|
795 | }
|
796 |
|
797 | if (isGlobalHelp) {
|
798 | return `${parentCmdNames}${command.usage()}\n${this.utils.colors.bold(
|
799 | 'Alternative usage to run commands:',
|
800 | )} ${parentCmdNames}[command] [options]`;
|
801 | }
|
802 |
|
803 | return `${parentCmdNames}${command.name()}|${command.aliases().join('|')} ${command.usage()}`;
|
804 | },
|
805 |
|
806 | subcommandTerm: (command) => {
|
807 | const humanReadableArgumentName = (argument) => {
|
808 | const nameOutput = argument.name + (argument.variadic === true ? '...' : '');
|
809 |
|
810 | return argument.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']';
|
811 | };
|
812 | const args = command._args.map((arg) => humanReadableArgumentName(arg)).join(' ');
|
813 |
|
814 | return `${command.name()}|${command.aliases().join('|')}${args ? ` ${args}` : ''}${
|
815 | command.options.length > 0 ? ' [options]' : ''
|
816 | }`;
|
817 | },
|
818 | visibleOptions: function visibleOptions(command) {
|
819 | const options = cliAPI.getBuiltInOptions();
|
820 |
|
821 | return command.options.filter((option) => {
|
822 | if (option.hidden) {
|
823 | return false;
|
824 | }
|
825 |
|
826 | if (!isVerbose) {
|
827 | const foundOption = options.find((flag) => {
|
828 | if (option.negate && flag.negative) {
|
829 | return `no-${flag.name}` === option.name();
|
830 | }
|
831 |
|
832 | return flag.name === option.name();
|
833 | });
|
834 |
|
835 | if (foundOption) {
|
836 | return foundOption.help === 'minimum';
|
837 | }
|
838 |
|
839 | return true;
|
840 | }
|
841 |
|
842 | return true;
|
843 | });
|
844 | },
|
845 | padWidth(command, helper) {
|
846 | return Math.max(
|
847 | helper.longestArgumentTermLength(command, helper),
|
848 | helper.longestOptionTermLength(command, helper),
|
849 |
|
850 | helper.longestOptionTermLength(program, helper),
|
851 | helper.longestSubcommandTermLength(isGlobalHelp ? program : command, helper),
|
852 | );
|
853 | },
|
854 | formatHelp: (command, helper) => {
|
855 | const termWidth = helper.padWidth(command, helper);
|
856 | const helpWidth = helper.helpWidth || 80;
|
857 | const itemIndentWidth = 2;
|
858 | const itemSeparatorWidth = 2;
|
859 |
|
860 | const formatItem = (term, description) => {
|
861 | if (description) {
|
862 | const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
|
863 |
|
864 | return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
|
865 | }
|
866 |
|
867 | return term;
|
868 | };
|
869 |
|
870 | const formatList = (textArray) => textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
|
871 |
|
872 |
|
873 | let output = [`${bold('Usage:')} ${helper.commandUsage(command)}`, ''];
|
874 |
|
875 |
|
876 | const commandDescription = isGlobalHelp
|
877 | ? 'The build tool for modern web applications.'
|
878 | : helper.commandDescription(command);
|
879 |
|
880 | if (commandDescription.length > 0) {
|
881 | output = output.concat([commandDescription, '']);
|
882 | }
|
883 |
|
884 |
|
885 | const argumentList = helper
|
886 | .visibleArguments(command)
|
887 | .map((argument) => formatItem(argument.term, argument.description));
|
888 |
|
889 | if (argumentList.length > 0) {
|
890 | output = output.concat([bold('Arguments:'), formatList(argumentList), '']);
|
891 | }
|
892 |
|
893 |
|
894 | const optionList = helper
|
895 | .visibleOptions(command)
|
896 | .map((option) => formatItem(helper.optionTerm(option), helper.optionDescription(option)));
|
897 |
|
898 | if (optionList.length > 0) {
|
899 | output = output.concat([bold('Options:'), formatList(optionList), '']);
|
900 | }
|
901 |
|
902 |
|
903 | const globalOptionList = program.options.map((option) =>
|
904 | formatItem(helper.optionTerm(option), helper.optionDescription(option)),
|
905 | );
|
906 |
|
907 | if (globalOptionList.length > 0) {
|
908 | output = output.concat([bold('Global options:'), formatList(globalOptionList), '']);
|
909 | }
|
910 |
|
911 |
|
912 | const commandList = helper
|
913 | .visibleCommands(isGlobalHelp ? program : command)
|
914 | .map((command) => formatItem(helper.subcommandTerm(command), helper.subcommandDescription(command)));
|
915 |
|
916 | if (commandList.length > 0) {
|
917 | output = output.concat([bold('Commands:'), formatList(commandList), '']);
|
918 | }
|
919 |
|
920 | return output.join('\n');
|
921 | },
|
922 | });
|
923 |
|
924 | if (isGlobalHelp) {
|
925 | await Promise.all(
|
926 | knownCommands.map((knownCommand) => {
|
927 | return loadCommandByName(getCommandName(knownCommand.name));
|
928 | }),
|
929 | );
|
930 |
|
931 | const buildCommand = findCommandByName(getCommandName(buildCommandOptions.name));
|
932 |
|
933 | this.logger.raw(buildCommand.helpInformation());
|
934 | } else {
|
935 | const name = options[0];
|
936 |
|
937 | await loadCommandByName(name);
|
938 |
|
939 | const command = findCommandByName(name);
|
940 |
|
941 | if (!command) {
|
942 | this.logger.error(`Can't find and load command '${name}'`);
|
943 | this.logger.error("Run 'webpack --help' to see available commands and options");
|
944 | process.exit(2);
|
945 | }
|
946 |
|
947 | this.logger.raw(command.helpInformation());
|
948 | }
|
949 | } else if (isHelpCommandSyntax) {
|
950 | let isCommandSpecified = false;
|
951 | let commandName = getCommandName(buildCommandOptions.name);
|
952 | let optionName;
|
953 |
|
954 | if (options.length === 1) {
|
955 | optionName = options[0];
|
956 | } else if (options.length === 2) {
|
957 | isCommandSpecified = true;
|
958 | commandName = options[0];
|
959 | optionName = options[1];
|
960 |
|
961 | if (isOption(commandName)) {
|
962 | outputIncorrectUsageOfHelp();
|
963 | }
|
964 | } else {
|
965 | outputIncorrectUsageOfHelp();
|
966 | }
|
967 |
|
968 | await loadCommandByName(commandName);
|
969 |
|
970 | const command = isGlobalOption(optionName) ? program : findCommandByName(commandName);
|
971 |
|
972 | if (!command) {
|
973 | this.logger.error(`Can't find and load command '${commandName}'`);
|
974 | this.logger.error("Run 'webpack --help' to see available commands and options");
|
975 | process.exit(2);
|
976 | }
|
977 |
|
978 | const option = command.options.find((option) => option.short === optionName || option.long === optionName);
|
979 |
|
980 | if (!option) {
|
981 | this.logger.error(`Unknown option '${optionName}'`);
|
982 | this.logger.error("Run 'webpack --help' to see available commands and options");
|
983 | process.exit(2);
|
984 | }
|
985 |
|
986 | const nameOutput =
|
987 | option.flags.replace(/^.+[[<]/, '').replace(/(\.\.\.)?[\]>].*$/, '') + (option.variadic === true ? '...' : '');
|
988 | const value = option.required ? '<' + nameOutput + '>' : option.optional ? '[' + nameOutput + ']' : '';
|
989 |
|
990 | this.logger.raw(
|
991 | `${bold('Usage')}: webpack${isCommandSpecified ? ` ${commandName}` : ''} ${option.long}${value ? ` ${value}` : ''}`,
|
992 | );
|
993 |
|
994 | if (option.short) {
|
995 | this.logger.raw(
|
996 | `${bold('Short:')} webpack${isCommandSpecified ? ` ${commandName}` : ''} ${option.short}${
|
997 | value ? ` ${value}` : ''
|
998 | }`,
|
999 | );
|
1000 | }
|
1001 |
|
1002 | if (option.description) {
|
1003 | this.logger.raw(`${bold('Description:')} ${option.description}`);
|
1004 | }
|
1005 |
|
1006 | if (!option.negate && options.defaultValue) {
|
1007 | this.logger.raw(`${bold('Default value:')} ${JSON.stringify(option.defaultValue)}`);
|
1008 | }
|
1009 |
|
1010 | this.logger.raw('');
|
1011 |
|
1012 |
|
1013 |
|
1014 |
|
1015 | } else {
|
1016 | outputIncorrectUsageOfHelp();
|
1017 | }
|
1018 |
|
1019 | this.logger.raw("To see list of all supported commands and options run 'webpack --help=verbose'.\n");
|
1020 | this.logger.raw(`${bold('Webpack documentation:')} https://webpack.js.org/.`);
|
1021 | this.logger.raw(`${bold('CLI documentation:')} https://webpack.js.org/api/cli/.`);
|
1022 | this.logger.raw(`${bold('Made with ♥ by the webpack team')}.`);
|
1023 | process.exit(0);
|
1024 | };
|
1025 | this.program.helpOption(false);
|
1026 | this.program.addHelpCommand(false);
|
1027 | this.program.option('-h, --help [verbose]', 'Display help for commands and options.');
|
1028 |
|
1029 | let isInternalActionCalled = false;
|
1030 |
|
1031 |
|
1032 | this.program.usage('[options]');
|
1033 | this.program.allowUnknownOption(true);
|
1034 | this.program.action(async (options, program) => {
|
1035 | if (!isInternalActionCalled) {
|
1036 | isInternalActionCalled = true;
|
1037 | } else {
|
1038 | this.logger.error('No commands found to run');
|
1039 | process.exit(2);
|
1040 | }
|
1041 |
|
1042 |
|
1043 | const { operands, unknown } = this.program.parseOptions(program.args);
|
1044 | const defaultCommandToRun = getCommandName(buildCommandOptions.name);
|
1045 | const hasOperand = typeof operands[0] !== 'undefined';
|
1046 | const operand = hasOperand ? operands[0] : defaultCommandToRun;
|
1047 |
|
1048 | const isHelpCommandSyntax = isCommand(operand, helpCommandOptions);
|
1049 |
|
1050 | if (options.help || isHelpCommandSyntax) {
|
1051 | let isVerbose = false;
|
1052 |
|
1053 | if (options.help) {
|
1054 | if (typeof options.help === 'string') {
|
1055 | if (options.help !== 'verbose') {
|
1056 | this.logger.error("Unknown value for '--help' option, please use '--help=verbose'");
|
1057 | process.exit(2);
|
1058 | }
|
1059 |
|
1060 | isVerbose = true;
|
1061 | }
|
1062 | }
|
1063 |
|
1064 | this.program.forHelp = true;
|
1065 |
|
1066 | const optionsForHelp = []
|
1067 | .concat(options.help && hasOperand ? [operand] : [])
|
1068 |
|
1069 | .concat(operands.slice(1))
|
1070 |
|
1071 | .concat(unknown)
|
1072 | .concat(isHelpCommandSyntax && typeof options.color !== 'undefined' ? [options.color ? '--color' : '--no-color'] : [])
|
1073 | .concat(isHelpCommandSyntax && typeof options.version !== 'undefined' ? ['--version'] : []);
|
1074 |
|
1075 | await outputHelp(optionsForHelp, isVerbose, isHelpCommandSyntax, program);
|
1076 | }
|
1077 |
|
1078 | if (options.version || isCommand(operand, versionCommandOptions)) {
|
1079 | const optionsForVersion = []
|
1080 | .concat(options.version ? [operand] : [])
|
1081 | .concat(operands.slice(1))
|
1082 | .concat(unknown);
|
1083 |
|
1084 | await outputVersion(optionsForVersion, program);
|
1085 | }
|
1086 |
|
1087 | let commandToRun = operand;
|
1088 | let commandOperands = operands.slice(1);
|
1089 |
|
1090 | if (isKnownCommand(commandToRun)) {
|
1091 | await loadCommandByName(commandToRun, true);
|
1092 | } else {
|
1093 | let isEntrySyntax = fs.existsSync(operand);
|
1094 |
|
1095 | if (isEntrySyntax) {
|
1096 | commandToRun = defaultCommandToRun;
|
1097 | commandOperands = operands;
|
1098 |
|
1099 | await loadCommandByName(commandToRun);
|
1100 | } else {
|
1101 | this.logger.error(`Unknown command or entry '${operand}'`);
|
1102 |
|
1103 | const found = knownCommands.find(
|
1104 | (commandOptions) => this.utils.levenshtein.distance(operand, getCommandName(commandOptions.name)) < 3,
|
1105 | );
|
1106 |
|
1107 | if (found) {
|
1108 | this.logger.error(
|
1109 | `Did you mean '${getCommandName(found.name)}' (alias '${
|
1110 | Array.isArray(found.alias) ? found.alias.join(', ') : found.alias
|
1111 | }')?`,
|
1112 | );
|
1113 | }
|
1114 |
|
1115 | this.logger.error("Run 'webpack --help' to see available commands and options");
|
1116 | process.exit(2);
|
1117 | }
|
1118 | }
|
1119 |
|
1120 | await this.program.parseAsync([commandToRun, ...commandOperands, ...unknown], { from: 'user' });
|
1121 | });
|
1122 |
|
1123 | await this.program.parseAsync(args, parseOptions);
|
1124 | }
|
1125 |
|
1126 | async resolveConfig(options) {
|
1127 | const loadConfig = async (configPath) => {
|
1128 | const { interpret } = this.utils;
|
1129 | const ext = path.extname(configPath);
|
1130 | const interpreted = Object.keys(interpret.jsVariants).find((variant) => variant === ext);
|
1131 |
|
1132 | if (interpreted) {
|
1133 | const { rechoir } = this.utils;
|
1134 |
|
1135 | try {
|
1136 | rechoir.prepare(interpret.extensions, configPath);
|
1137 | } catch (error) {
|
1138 | if (error.failures) {
|
1139 | this.logger.error(`Unable load '${configPath}'`);
|
1140 | this.logger.error(error.message);
|
1141 |
|
1142 | error.failures.forEach((failure) => {
|
1143 | this.logger.error(failure.error.message);
|
1144 | });
|
1145 | this.logger.error('Please install one of them');
|
1146 | process.exit(2);
|
1147 | }
|
1148 |
|
1149 | this.logger.error(error);
|
1150 | process.exit(2);
|
1151 | }
|
1152 | }
|
1153 |
|
1154 | let options;
|
1155 |
|
1156 | try {
|
1157 | try {
|
1158 | options = require(configPath);
|
1159 | } catch (error) {
|
1160 | let previousModuleCompile;
|
1161 |
|
1162 |
|
1163 | if (this._originalModuleCompile) {
|
1164 | previousModuleCompile = Module.prototype._compile;
|
1165 |
|
1166 | Module.prototype._compile = this._originalModuleCompile;
|
1167 | }
|
1168 |
|
1169 | const dynamicImportLoader = this.utils.dynamicImportLoader();
|
1170 |
|
1171 | if (this._originalModuleCompile) {
|
1172 | Module.prototype._compile = previousModuleCompile;
|
1173 | }
|
1174 |
|
1175 | if (
|
1176 | (error.code === 'ERR_REQUIRE_ESM' || process.env.WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG) &&
|
1177 | pathToFileURL &&
|
1178 | dynamicImportLoader
|
1179 | ) {
|
1180 | const urlForConfig = pathToFileURL(configPath);
|
1181 |
|
1182 | options = await dynamicImportLoader(urlForConfig);
|
1183 | options = options.default;
|
1184 |
|
1185 | return { options, path: configPath };
|
1186 | }
|
1187 |
|
1188 | throw error;
|
1189 | }
|
1190 | } catch (error) {
|
1191 | this.logger.error(`Failed to load '${configPath}' config`);
|
1192 |
|
1193 | if (this.isValidationError(error)) {
|
1194 | this.logger.error(error.message);
|
1195 | } else {
|
1196 | this.logger.error(error);
|
1197 | }
|
1198 |
|
1199 | process.exit(2);
|
1200 | }
|
1201 |
|
1202 | if (options.default) {
|
1203 | options = options.default;
|
1204 | }
|
1205 |
|
1206 | return { options, path: configPath };
|
1207 | };
|
1208 |
|
1209 | const evaluateConfig = async (loadedConfig, argv) => {
|
1210 | const isMultiCompiler = Array.isArray(loadedConfig.options);
|
1211 | const config = isMultiCompiler ? loadedConfig.options : [loadedConfig.options];
|
1212 |
|
1213 | let evaluatedConfig = await Promise.all(
|
1214 | config.map(async (rawConfig) => {
|
1215 | if (typeof rawConfig.then === 'function') {
|
1216 | rawConfig = await rawConfig;
|
1217 | }
|
1218 |
|
1219 |
|
1220 | if (typeof rawConfig === 'function') {
|
1221 |
|
1222 | rawConfig = await rawConfig(argv.env, argv);
|
1223 | }
|
1224 |
|
1225 | return rawConfig;
|
1226 | }),
|
1227 | );
|
1228 |
|
1229 | loadedConfig.options = isMultiCompiler ? evaluatedConfig : evaluatedConfig[0];
|
1230 |
|
1231 | const isObject = (value) => typeof value === 'object' && value !== null;
|
1232 |
|
1233 | if (!isObject(loadedConfig.options) && !Array.isArray(loadedConfig.options)) {
|
1234 | this.logger.error(`Invalid configuration in '${loadedConfig.path}'`);
|
1235 | process.exit(2);
|
1236 | }
|
1237 |
|
1238 | return loadedConfig;
|
1239 | };
|
1240 |
|
1241 | let config = { options: {}, path: new WeakMap() };
|
1242 |
|
1243 | if (options.config && options.config.length > 0) {
|
1244 | const evaluatedConfigs = await Promise.all(
|
1245 | options.config.map(async (value) => evaluateConfig(await loadConfig(path.resolve(value)), options.argv || {})),
|
1246 | );
|
1247 |
|
1248 | config.options = [];
|
1249 |
|
1250 | evaluatedConfigs.forEach((evaluatedConfig) => {
|
1251 | if (Array.isArray(evaluatedConfig.options)) {
|
1252 | evaluatedConfig.options.forEach((options) => {
|
1253 | config.options.push(options);
|
1254 | config.path.set(options, evaluatedConfig.path);
|
1255 | });
|
1256 | } else {
|
1257 | config.options.push(evaluatedConfig.options);
|
1258 | config.path.set(evaluatedConfig.options, evaluatedConfig.path);
|
1259 | }
|
1260 | });
|
1261 |
|
1262 | config.options = config.options.length === 1 ? config.options[0] : config.options;
|
1263 | } else {
|
1264 | const { interpret } = this.utils;
|
1265 |
|
1266 |
|
1267 | const defaultConfigFiles = ['webpack.config', '.webpack/webpack.config', '.webpack/webpackfile']
|
1268 | .map((filename) =>
|
1269 |
|
1270 | [...Object.keys(interpret.extensions), '.cjs'].map((ext) => ({
|
1271 | path: path.resolve(filename + ext),
|
1272 | ext: ext,
|
1273 | module: interpret.extensions[ext],
|
1274 | })),
|
1275 | )
|
1276 | .reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
|
1277 |
|
1278 | let foundDefaultConfigFile;
|
1279 |
|
1280 | for (const defaultConfigFile of defaultConfigFiles) {
|
1281 | if (!fs.existsSync(defaultConfigFile.path)) {
|
1282 | continue;
|
1283 | }
|
1284 |
|
1285 | foundDefaultConfigFile = defaultConfigFile;
|
1286 | break;
|
1287 | }
|
1288 |
|
1289 | if (foundDefaultConfigFile) {
|
1290 | const loadedConfig = await loadConfig(foundDefaultConfigFile.path);
|
1291 | const evaluatedConfig = await evaluateConfig(loadedConfig, options.argv || {});
|
1292 |
|
1293 | config.options = evaluatedConfig.options;
|
1294 |
|
1295 | if (Array.isArray(config.options)) {
|
1296 | config.options.forEach((options) => {
|
1297 | config.path.set(options, evaluatedConfig.path);
|
1298 | });
|
1299 | } else {
|
1300 | config.path.set(evaluatedConfig.options, evaluatedConfig.path);
|
1301 | }
|
1302 | }
|
1303 | }
|
1304 |
|
1305 | if (options.configName) {
|
1306 | const notfoundConfigNames = [];
|
1307 |
|
1308 | config.options = options.configName.map((configName) => {
|
1309 | let found;
|
1310 |
|
1311 | if (Array.isArray(config.options)) {
|
1312 | found = config.options.find((options) => options.name === configName);
|
1313 | } else {
|
1314 | found = config.options.name === configName ? config.options : undefined;
|
1315 | }
|
1316 |
|
1317 | if (!found) {
|
1318 | notfoundConfigNames.push(configName);
|
1319 | }
|
1320 |
|
1321 | return found;
|
1322 | });
|
1323 |
|
1324 | if (notfoundConfigNames.length > 0) {
|
1325 | this.logger.error(
|
1326 | notfoundConfigNames.map((configName) => `Configuration with the name "${configName}" was not found.`).join(' '),
|
1327 | );
|
1328 | process.exit(2);
|
1329 | }
|
1330 | }
|
1331 |
|
1332 | if (options.merge) {
|
1333 | const { merge } = require('webpack-merge');
|
1334 |
|
1335 |
|
1336 |
|
1337 |
|
1338 | if (!Array.isArray(config.options) || config.options.length <= 1) {
|
1339 | this.logger.error('At least two configurations are required for merge.');
|
1340 | process.exit(2);
|
1341 | }
|
1342 |
|
1343 | const mergedConfigPaths = [];
|
1344 |
|
1345 | config.options = config.options.reduce((accumulator, options) => {
|
1346 | const configPath = config.path.get(options);
|
1347 | const mergedOptions = merge(accumulator, options);
|
1348 |
|
1349 | mergedConfigPaths.push(configPath);
|
1350 |
|
1351 | return mergedOptions;
|
1352 | }, {});
|
1353 | config.path.set(config.options, mergedConfigPaths);
|
1354 | }
|
1355 |
|
1356 | return config;
|
1357 | }
|
1358 |
|
1359 |
|
1360 | async applyOptions(config, options) {
|
1361 | if (options.analyze) {
|
1362 | if (!this.utils.packageExists('webpack-bundle-analyzer')) {
|
1363 | const { promptInstallation, colors } = this.utils;
|
1364 |
|
1365 | try {
|
1366 | await promptInstallation('webpack-bundle-analyzer', () => {
|
1367 | this.logger.error(`It looks like ${colors.yellow('webpack-bundle-analyzer')} is not installed.`);
|
1368 | });
|
1369 | } catch (error) {
|
1370 | this.logger.error(
|
1371 | `Action Interrupted, Please try once again or install ${colors.yellow('webpack-bundle-analyzer')} manually.`,
|
1372 | );
|
1373 | process.exit(2);
|
1374 | }
|
1375 |
|
1376 | this.logger.success(`${colors.yellow('webpack-bundle-analyzer')} was installed successfully.`);
|
1377 | }
|
1378 | }
|
1379 |
|
1380 | if (typeof options.progress === 'string' && options.progress !== 'profile') {
|
1381 | this.logger.error(`'${options.progress}' is an invalid value for the --progress option. Only 'profile' is allowed.`);
|
1382 | process.exit(2);
|
1383 | }
|
1384 |
|
1385 | const outputHints = (configOptions) => {
|
1386 | if (
|
1387 | configOptions.watch &&
|
1388 | options.argv &&
|
1389 | options.argv.env &&
|
1390 | (options.argv.env['WEBPACK_WATCH'] || options.argv.env['WEBPACK_SERVE'])
|
1391 | ) {
|
1392 | this.logger.warn(
|
1393 | `No need to use the '${
|
1394 | options.argv.env['WEBPACK_WATCH'] ? 'watch' : 'serve'
|
1395 | }' command together with '{ watch: true }' configuration, it does not make sense.`,
|
1396 | );
|
1397 |
|
1398 | if (options.argv.env['WEBPACK_SERVE']) {
|
1399 | configOptions.watch = false;
|
1400 | }
|
1401 | }
|
1402 |
|
1403 | return configOptions;
|
1404 | };
|
1405 |
|
1406 | config.options = Array.isArray(config.options)
|
1407 | ? config.options.map((options) => outputHints(options))
|
1408 | : outputHints(config.options);
|
1409 |
|
1410 | if (this.webpack.cli) {
|
1411 | const processArguments = (configOptions) => {
|
1412 | const args = this.getBuiltInOptions()
|
1413 | .filter((flag) => flag.group === 'core')
|
1414 | .reduce((accumulator, flag) => {
|
1415 | accumulator[flag.name] = flag;
|
1416 |
|
1417 | return accumulator;
|
1418 | }, {});
|
1419 |
|
1420 | const values = Object.keys(options).reduce((accumulator, name) => {
|
1421 | if (name === 'argv') {
|
1422 | return accumulator;
|
1423 | }
|
1424 |
|
1425 | const kebabName = this.utils.toKebabCase(name);
|
1426 |
|
1427 | if (args[kebabName]) {
|
1428 | accumulator[kebabName] = options[name];
|
1429 | }
|
1430 |
|
1431 | return accumulator;
|
1432 | }, {});
|
1433 |
|
1434 | const problems = this.webpack.cli.processArguments(args, configOptions, values);
|
1435 |
|
1436 | if (problems) {
|
1437 | const groupBy = (xs, key) => {
|
1438 | return xs.reduce((rv, x) => {
|
1439 | (rv[x[key]] = rv[x[key]] || []).push(x);
|
1440 |
|
1441 | return rv;
|
1442 | }, {});
|
1443 | };
|
1444 | const problemsByPath = groupBy(problems, 'path');
|
1445 |
|
1446 | for (const path in problemsByPath) {
|
1447 | const problems = problemsByPath[path];
|
1448 |
|
1449 | problems.forEach((problem) => {
|
1450 | this.logger.error(
|
1451 | `${this.utils.capitalizeFirstLetter(problem.type.replace(/-/g, ' '))}${
|
1452 | problem.value ? ` '${problem.value}'` : ''
|
1453 | } for the '--${problem.argument}' option${problem.index ? ` by index '${problem.index}'` : ''}`,
|
1454 | );
|
1455 |
|
1456 | if (problem.expected) {
|
1457 | this.logger.error(`Expected: '${problem.expected}'`);
|
1458 | }
|
1459 | });
|
1460 | }
|
1461 |
|
1462 | process.exit(2);
|
1463 | }
|
1464 |
|
1465 | return configOptions;
|
1466 | };
|
1467 |
|
1468 | config.options = Array.isArray(config.options)
|
1469 | ? config.options.map((options) => processArguments(options))
|
1470 | : processArguments(config.options);
|
1471 |
|
1472 | const setupDefaultOptions = (configOptions) => {
|
1473 |
|
1474 | if (configOptions.cache && configOptions.cache.type === 'filesystem') {
|
1475 | const configPath = config.path.get(configOptions);
|
1476 |
|
1477 | if (configPath) {
|
1478 | if (!configOptions.cache.buildDependencies) {
|
1479 | configOptions.cache.buildDependencies = {};
|
1480 | }
|
1481 |
|
1482 | if (!configOptions.cache.buildDependencies.defaultConfig) {
|
1483 | configOptions.cache.buildDependencies.defaultConfig = [];
|
1484 | }
|
1485 |
|
1486 | if (Array.isArray(configPath)) {
|
1487 | configPath.forEach((item) => {
|
1488 | configOptions.cache.buildDependencies.defaultConfig.push(item);
|
1489 | });
|
1490 | } else {
|
1491 | configOptions.cache.buildDependencies.defaultConfig.push(configPath);
|
1492 | }
|
1493 | }
|
1494 | }
|
1495 |
|
1496 | return configOptions;
|
1497 | };
|
1498 |
|
1499 | config.options = Array.isArray(config.options)
|
1500 | ? config.options.map((options) => setupDefaultOptions(options))
|
1501 | : setupDefaultOptions(config.options);
|
1502 | }
|
1503 |
|
1504 |
|
1505 |
|
1506 | const processLegacyArguments = (configOptions) => {
|
1507 | if (options.entry) {
|
1508 | configOptions.entry = options.entry;
|
1509 | }
|
1510 |
|
1511 | if (options.outputPath) {
|
1512 | configOptions.output = {
|
1513 | ...configOptions.output,
|
1514 | ...{ path: path.resolve(options.outputPath) },
|
1515 | };
|
1516 | }
|
1517 |
|
1518 | if (options.target) {
|
1519 | configOptions.target = options.target;
|
1520 | }
|
1521 |
|
1522 | if (typeof options.devtool !== 'undefined') {
|
1523 | configOptions.devtool = options.devtool;
|
1524 | }
|
1525 |
|
1526 | if (options.mode) {
|
1527 | configOptions.mode = options.mode;
|
1528 | } else if (
|
1529 | !configOptions.mode &&
|
1530 | process.env &&
|
1531 | process.env.NODE_ENV &&
|
1532 | (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'none')
|
1533 | ) {
|
1534 | configOptions.mode = process.env.NODE_ENV;
|
1535 | }
|
1536 |
|
1537 | if (options.name) {
|
1538 | configOptions.name = options.name;
|
1539 | }
|
1540 |
|
1541 | if (typeof options.stats !== 'undefined') {
|
1542 | configOptions.stats = options.stats;
|
1543 | }
|
1544 |
|
1545 | if (typeof options.watch !== 'undefined') {
|
1546 | configOptions.watch = options.watch;
|
1547 | }
|
1548 |
|
1549 | if (typeof options.watchOptionsStdin !== 'undefined') {
|
1550 | configOptions.watchOptions = {
|
1551 | ...configOptions.watchOptions,
|
1552 | ...{ stdin: options.watchOptionsStdin },
|
1553 | };
|
1554 | }
|
1555 |
|
1556 | return configOptions;
|
1557 | };
|
1558 |
|
1559 | config.options = Array.isArray(config.options)
|
1560 | ? config.options.map((options) => processLegacyArguments(options))
|
1561 | : processLegacyArguments(config.options);
|
1562 |
|
1563 |
|
1564 | const applyStatsColors = (configOptions) => {
|
1565 |
|
1566 | const statsForWebpack4 = this.webpack.Stats && this.webpack.Stats.presetToOptions;
|
1567 |
|
1568 | if (statsForWebpack4) {
|
1569 | if (typeof configOptions.stats === 'undefined') {
|
1570 | configOptions.stats = {};
|
1571 | } else if (typeof configOptions.stats === 'boolean' || typeof configOptions.stats === 'string') {
|
1572 | if (
|
1573 | typeof configOptions.stats === 'string' &&
|
1574 | configOptions.stats !== 'none' &&
|
1575 | configOptions.stats !== 'verbose' &&
|
1576 | configOptions.stats !== 'detailed' &&
|
1577 | configOptions.stats !== 'minimal' &&
|
1578 | configOptions.stats !== 'errors-only' &&
|
1579 | configOptions.stats !== 'errors-warnings'
|
1580 | ) {
|
1581 | return configOptions;
|
1582 | }
|
1583 |
|
1584 | configOptions.stats = this.webpack.Stats.presetToOptions(configOptions.stats);
|
1585 | }
|
1586 | } else {
|
1587 | if (typeof configOptions.stats === 'undefined') {
|
1588 | configOptions.stats = { preset: 'normal' };
|
1589 | } else if (typeof configOptions.stats === 'boolean') {
|
1590 | configOptions.stats = configOptions.stats ? { preset: 'normal' } : { preset: 'none' };
|
1591 | } else if (typeof configOptions.stats === 'string') {
|
1592 | configOptions.stats = { preset: configOptions.stats };
|
1593 | }
|
1594 | }
|
1595 |
|
1596 | let colors;
|
1597 |
|
1598 |
|
1599 | if (typeof this.utils.colors.options.changed !== 'undefined') {
|
1600 | colors = Boolean(this.utils.colors.options.enabled);
|
1601 | }
|
1602 |
|
1603 | else if (typeof configOptions.stats.colors !== 'undefined') {
|
1604 | colors = configOptions.stats.colors;
|
1605 | }
|
1606 |
|
1607 | else {
|
1608 | colors = Boolean(this.utils.colors.options.enabled);
|
1609 | }
|
1610 |
|
1611 | configOptions.stats.colors = colors;
|
1612 |
|
1613 | return configOptions;
|
1614 | };
|
1615 |
|
1616 | config.options = Array.isArray(config.options)
|
1617 | ? config.options.map((options) => applyStatsColors(options))
|
1618 | : applyStatsColors(config.options);
|
1619 |
|
1620 | return config;
|
1621 | }
|
1622 |
|
1623 | async applyCLIPlugin(config, cliOptions) {
|
1624 | const addCLIPlugin = (configOptions) => {
|
1625 | if (!configOptions.plugins) {
|
1626 | configOptions.plugins = [];
|
1627 | }
|
1628 |
|
1629 | const CLIPlugin = require('./plugins/CLIPlugin');
|
1630 |
|
1631 | configOptions.plugins.unshift(
|
1632 | new CLIPlugin({
|
1633 | configPath: config.path,
|
1634 | helpfulOutput: !cliOptions.json,
|
1635 | hot: cliOptions.hot,
|
1636 | progress: cliOptions.progress,
|
1637 | prefetch: cliOptions.prefetch,
|
1638 | analyze: cliOptions.analyze,
|
1639 | }),
|
1640 | );
|
1641 |
|
1642 | return configOptions;
|
1643 | };
|
1644 | config.options = Array.isArray(config.options)
|
1645 | ? config.options.map((options) => addCLIPlugin(options))
|
1646 | : addCLIPlugin(config.options);
|
1647 |
|
1648 | return config;
|
1649 | }
|
1650 |
|
1651 | needWatchStdin(compiler) {
|
1652 | if (compiler.compilers) {
|
1653 | return compiler.compilers.some((compiler) => compiler.options.watchOptions && compiler.options.watchOptions.stdin);
|
1654 | }
|
1655 |
|
1656 | return compiler.options.watchOptions && compiler.options.watchOptions.stdin;
|
1657 | }
|
1658 |
|
1659 | isValidationError(error) {
|
1660 |
|
1661 |
|
1662 | const ValidationError = this.webpack.ValidationError || this.webpack.WebpackOptionsValidationError;
|
1663 |
|
1664 | return error instanceof ValidationError || error.name === 'ValidationError';
|
1665 | }
|
1666 |
|
1667 | async createCompiler(options, callback) {
|
1668 | this.applyNodeEnv(options);
|
1669 |
|
1670 | let config = await this.resolveConfig(options);
|
1671 |
|
1672 | config = await this.applyOptions(config, options);
|
1673 | config = await this.applyCLIPlugin(config, options);
|
1674 |
|
1675 | let compiler;
|
1676 |
|
1677 | try {
|
1678 | compiler = this.webpack(
|
1679 | config.options,
|
1680 | callback
|
1681 | ? (error, stats) => {
|
1682 | if (error && this.isValidationError(error)) {
|
1683 | this.logger.error(error.message);
|
1684 | process.exit(2);
|
1685 | }
|
1686 |
|
1687 | callback(error, stats);
|
1688 | }
|
1689 | : callback,
|
1690 | );
|
1691 | } catch (error) {
|
1692 | if (this.isValidationError(error)) {
|
1693 | this.logger.error(error.message);
|
1694 | } else {
|
1695 | this.logger.error(error);
|
1696 | }
|
1697 |
|
1698 | process.exit(2);
|
1699 | }
|
1700 |
|
1701 |
|
1702 | if (compiler && compiler.compiler) {
|
1703 | compiler = compiler.compiler;
|
1704 | }
|
1705 |
|
1706 | return compiler;
|
1707 | }
|
1708 |
|
1709 | async buildCommand(options, isWatchCommand) {
|
1710 | let compiler;
|
1711 |
|
1712 | const callback = (error, stats) => {
|
1713 | if (error) {
|
1714 | this.logger.error(error);
|
1715 | process.exit(2);
|
1716 | }
|
1717 |
|
1718 | if (stats.hasErrors()) {
|
1719 | process.exitCode = 1;
|
1720 | }
|
1721 |
|
1722 | if (!compiler) {
|
1723 | return;
|
1724 | }
|
1725 |
|
1726 | const statsOptions = compiler.compilers
|
1727 | ? { children: compiler.compilers.map((compiler) => (compiler.options ? compiler.options.stats : undefined)) }
|
1728 | : compiler.options
|
1729 | ? compiler.options.stats
|
1730 | : undefined;
|
1731 |
|
1732 |
|
1733 | const statsForWebpack4 = this.webpack.Stats && this.webpack.Stats.presetToOptions;
|
1734 |
|
1735 | if (compiler.compilers && statsForWebpack4) {
|
1736 | statsOptions.colors = statsOptions.children.some((child) => child.colors);
|
1737 | }
|
1738 |
|
1739 | if (options.json) {
|
1740 | const { stringifyStream: createJsonStringifyStream } = require('@discoveryjs/json-ext');
|
1741 | const handleWriteError = (error) => {
|
1742 | this.logger.error(error);
|
1743 | process.exit(2);
|
1744 | };
|
1745 |
|
1746 | if (options.json === true) {
|
1747 | createJsonStringifyStream(stats.toJson(statsOptions))
|
1748 | .on('error', handleWriteError)
|
1749 | .pipe(process.stdout)
|
1750 | .on('error', handleWriteError)
|
1751 | .on('close', () => process.stdout.write('\n'));
|
1752 | } else {
|
1753 | createJsonStringifyStream(stats.toJson(statsOptions))
|
1754 | .on('error', handleWriteError)
|
1755 | .pipe(fs.createWriteStream(options.json))
|
1756 | .on('error', handleWriteError)
|
1757 |
|
1758 | .on('close', () =>
|
1759 | process.stderr.write(
|
1760 | `[webpack-cli] ${this.utils.colors.green(`stats are successfully stored as json to ${options.json}`)}\n`,
|
1761 | ),
|
1762 | );
|
1763 | }
|
1764 | } else {
|
1765 | const printedStats = stats.toString(statsOptions);
|
1766 |
|
1767 |
|
1768 | if (printedStats) {
|
1769 | this.logger.raw(printedStats);
|
1770 | }
|
1771 | }
|
1772 | };
|
1773 |
|
1774 | const env =
|
1775 | isWatchCommand || options.watch
|
1776 | ? { WEBPACK_WATCH: true, ...options.env }
|
1777 | : { WEBPACK_BUNDLE: true, WEBPACK_BUILD: true, ...options.env };
|
1778 |
|
1779 | options.argv = { ...options, env };
|
1780 |
|
1781 | if (isWatchCommand) {
|
1782 | options.watch = true;
|
1783 | }
|
1784 |
|
1785 | compiler = await this.createCompiler(options, callback);
|
1786 |
|
1787 | if (!compiler) {
|
1788 | return;
|
1789 | }
|
1790 |
|
1791 | const isWatch = (compiler) =>
|
1792 | compiler.compilers ? compiler.compilers.some((compiler) => compiler.options.watch) : compiler.options.watch;
|
1793 |
|
1794 | if (isWatch(compiler) && this.needWatchStdin(compiler)) {
|
1795 | process.stdin.on('end', () => {
|
1796 | process.exit(0);
|
1797 | });
|
1798 | process.stdin.resume();
|
1799 | }
|
1800 | }
|
1801 | }
|
1802 |
|
1803 | module.exports = WebpackCLI;
|