simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

webpack-cli.js (69136B)


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