webpack-cli.js 82 KB

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