source: imaps-frontend/node_modules/webpack-cli/lib/webpack-cli.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

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