UNPKG

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