webpack-cli.js 88 KB

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