webpack-cli.js 82 KB

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