From e663d100a794d6ca92730831dc419bc1d18493bc Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Wed, 18 Mar 2020 15:47:02 +0530 Subject: [PATCH 01/43] chore: use commander --- packages/webpack-cli/lib/bootstrap.js | 27 ++++--------- packages/webpack-cli/lib/utils/arg-parser.js | 41 ++++++++++++++++++++ packages/webpack-cli/lib/webpack-cli.js | 15 +++++-- packages/webpack-cli/package.json | 1 + 4 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 packages/webpack-cli/lib/utils/arg-parser.js diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index 485c129a259..d5f15344855 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -2,7 +2,7 @@ const WebpackCLI = require('./webpack-cli'); const { core, commands } = require('./utils/cli-flags'); const logger = require('./utils/logger'); const cliExecuter = require('./utils/cli-executer'); - +const argParser = require('./utils/arg-parser'); require('./utils/process-log'); process.title = 'webpack-cli'; @@ -21,7 +21,7 @@ const isCommandUsed = (commands) => }); const resolveNegatedArgs = (args) => { - args._unknown.forEach((arg, idx) => { + args.forEach((arg, idx) => { if (arg.includes('--') || arg.includes('--no')) { const argPair = arg.split('='); const optName = arg.includes('--no') ? argPair[0].slice(5) : argPair[0].slice(2); @@ -36,24 +36,14 @@ const resolveNegatedArgs = (args) => { if (cliFlag) { args[cliFlag.group][optName] = argValue; args._all[optName] = argValue; - args._unknown[idx] = null; + args.args[idx] = null; } } }); }; async function runCLI(cli, commandIsUsed) { - let args; - const helpFlagExists = isFlagPresent(process.argv, 'help'); - const versionFlagExists = isFlagPresent(process.argv, 'version') || isFlagPresent(process.argv, '-v'); - - if (helpFlagExists) { - cli.runHelp(process.argv); - return; - } else if (versionFlagExists) { - cli.runVersion(commandIsUsed); - return; - } + const parsedArgs = argParser('webpack', core, process.argv, cli.runHelp, cli.runVersion); if (commandIsUsed) { commandIsUsed.defaultOption = true; @@ -61,10 +51,9 @@ async function runCLI(cli, commandIsUsed) { return await cli.runCommand(commandIsUsed, ...args); } else { try { - args = cli.commandLineArgs(core, { stopAtFirstUnknown: false, partial: true }); - if (args._unknown) { - resolveNegatedArgs(args); - args._unknown + if (parsedArgs.args.length > 0) { + resolveNegatedArgs(parsedArgs.args); + parsedArgs.args .filter((e) => e) .forEach((unknown) => { logger.warn('Unknown argument:', unknown); @@ -72,7 +61,7 @@ async function runCLI(cli, commandIsUsed) { cliExecuter(); return; } - const result = await cli.run(args, core); + const result = await cli.run(parsedArgs.opts(), core); if (!result) { return; } diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js new file mode 100644 index 00000000000..0ccd86f9a26 --- /dev/null +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -0,0 +1,41 @@ +const commander = require("commander"); + +/** + * Creates Argument parser corresponding to the supplied options + * parse the args and return the result + * + * @param {object[]} options Array of objects with details about flags + * @param {string[]} args process.argv or it's subset + */ +function argParser(name, options, args, helpFunction, versionFunction) { + const parser = new commander.Command(); + // Set parser name + parser.name(name); + + // Use customized version output + parser.on('option:version', () => { + versionFunction(args); + process.exit(0); + }); + + // Use customised help output + parser.on('option:help', () => { + helpFunction(args); + process.exit(0); + }); + + // Allow execution if unknown arguments are present + parser.allowUnknownOption(true); + + // Register options on the parser + options.reduce((parserInstance, option) => { + const flags = option.alias ? `-${option.alias}, --${option.name}` : `--${option.name}`; + const flagsWithType = option.type !== Boolean ? flags + ' [type]' : flags; + parserInstance.option(flagsWithType, option.description, option.defaultValue); + return parserInstance; + }, parser); + + return parser.parse(args); +} + +module.exports = argParser; diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index 12e1b925229..d2af6738dab 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -36,12 +36,12 @@ class WebpackCLI extends GroupHelper { this.outputConfiguration = {}; } setMappedGroups(args, inlineOptions) { - const { _all } = args; - Object.keys(_all).forEach((key) => { - this.setGroupMap(key, _all[key], inlineOptions); + Object.keys(args).forEach((key) => { + this.setGroupMap(this._toKebabCase(key), args[key], inlineOptions); }); } setGroupMap(key, val, inlineOptions) { + if (val === undefined) return; const opt = inlineOptions.find((opt) => opt.name === key); const groupName = opt.group; if (this.groupMap.has(groupName)) { @@ -89,6 +89,15 @@ class WebpackCLI extends GroupHelper { getCoreFlags() { return core; } + + /** + * Convert camelcase to kebabcase + * @param {string} string + */ + _toKebabCase(string) { + return string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); + } + /** * Based on the parsed keys, the function will import and create * a group that handles respective values diff --git a/packages/webpack-cli/package.json b/packages/webpack-cli/package.json index bf205953041..f95da5296c3 100644 --- a/packages/webpack-cli/package.json +++ b/packages/webpack-cli/package.json @@ -29,6 +29,7 @@ "chalk": "^3.0.0", "command-line-args": "^5.1.1", "command-line-usage": "^6.1.0", + "commander": "^5.0.0", "enquirer": "^2.3.4", "execa": "^4.0.0", "import-local": "^3.0.2", From 87403bfb9a07eed6d5a38c63099c8cf50068bbab Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Wed, 18 Mar 2020 16:25:11 +0530 Subject: [PATCH 02/43] chore: add support for help and version command --- packages/webpack-cli/lib/bootstrap.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index d5f15344855..0f3901f1aa9 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -45,6 +45,16 @@ const resolveNegatedArgs = (args) => { async function runCLI(cli, commandIsUsed) { const parsedArgs = argParser('webpack', core, process.argv, cli.runHelp, cli.runVersion); + if (parsedArgs.args.includes('help')) { + cli.runHelp(process.argv); + process.exit(0); + } + + if (parsedArgs.args.includes('version')) { + cli.runVersion(); + process.exit(0); + } + if (commandIsUsed) { commandIsUsed.defaultOption = true; args = normalizeFlags(process.argv, commandIsUsed); From ec8c4749240ab3ab0f8298de48c4d60c28fb21c1 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Wed, 18 Mar 2020 16:49:06 +0530 Subject: [PATCH 03/43] chore: remove duplicate code for no-mode --- packages/webpack-cli/lib/groups/ZeroConfigGroup.js | 5 +---- test/no-mode/no-mode.test.js | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/webpack-cli/lib/groups/ZeroConfigGroup.js b/packages/webpack-cli/lib/groups/ZeroConfigGroup.js index 26c5daeed26..2baedb23ea2 100644 --- a/packages/webpack-cli/lib/groups/ZeroConfigGroup.js +++ b/packages/webpack-cli/lib/groups/ZeroConfigGroup.js @@ -20,7 +20,7 @@ class ZeroConfigGroup extends GroupHelper { if (process.env.NODE_ENV && (process.env.NODE_ENV === PRODUCTION || process.env.NODE_ENV === DEVELOPMENT)) { return process.env.NODE_ENV; } else { - if ((this.args.mode || this.args.noMode) && (this.args.dev || this.args.prod)) { + if ((this.args.mode !== undefined ) && (this.args.dev || this.args.prod)) { logger.warn( `You provided both ${this.args.mode ? 'mode' : 'no-mode'} and ${ this.args.prod ? '--prod' : '--dev' @@ -32,9 +32,6 @@ class ZeroConfigGroup extends GroupHelper { return NONE; } } - if (this.args.noMode && this.args.mode) { - logger.warn('You Provided both mode and no-mode arguments. You Should Provide just one. "mode" will be used.'); - } if (this.args.mode) { return this.args.mode; } diff --git a/test/no-mode/no-mode.test.js b/test/no-mode/no-mode.test.js index 5eff202d82e..aa71c614dfe 100644 --- a/test/no-mode/no-mode.test.js +++ b/test/no-mode/no-mode.test.js @@ -40,7 +40,7 @@ describe('no-mode flag', () => { it('should load a production config when --mode=production & --no-mode are passed', (done) => { const { stderr, stdout } = run(__dirname, ['--mode', 'production', '--no-mode']); - expect(stderr).toContain('"mode" will be used'); + expect(stderr).toBeFalsy(); expect(stdout).toBeTruthy(); stat(resolve(__dirname, './bin/main.js'), (err, stats) => { @@ -52,7 +52,7 @@ describe('no-mode flag', () => { it('should load a development config when --mode=development and --no-mode are passed', (done) => { const { stderr, stdout } = run(__dirname, ['--mode', 'development', '--no-mode']); - expect(stderr).toContain('"mode" will be used'); + expect(stderr).toBeFalsy(); expect(stdout).toBeTruthy(); stat(resolve(__dirname, './bin/main.js'), (err, stats) => { From f7009ae9bfb805e86328744b7f6a591224b28835 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Wed, 18 Mar 2020 16:55:25 +0530 Subject: [PATCH 04/43] chore: create helpers --- packages/webpack-cli/lib/utils/helpers.js | 10 ++++++++++ packages/webpack-cli/lib/webpack-cli.js | 11 ++--------- 2 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 packages/webpack-cli/lib/utils/helpers.js diff --git a/packages/webpack-cli/lib/utils/helpers.js b/packages/webpack-cli/lib/utils/helpers.js new file mode 100644 index 00000000000..34fdd0a55d4 --- /dev/null +++ b/packages/webpack-cli/lib/utils/helpers.js @@ -0,0 +1,10 @@ +/** + * Convert camelCase to kebab-case + * @param {string} str input string in camelCase + * @returns {string} output string in kebab-case + */ +function toKebabCase(str) { + return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase() +} + +module.exports = { toKebabCase }; diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index d2af6738dab..a5539442529 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -5,7 +5,7 @@ const { Compiler } = require('./utils/Compiler'); const { groups, core } = require('./utils/cli-flags'); const webpackMerge = require('webpack-merge'); const commandArgs = require('command-line-args'); - +const { toKebabCase } = require('./utils/helpers'); const defaultCommands = { init: 'init', loader: 'generate-loader', @@ -37,7 +37,7 @@ class WebpackCLI extends GroupHelper { } setMappedGroups(args, inlineOptions) { Object.keys(args).forEach((key) => { - this.setGroupMap(this._toKebabCase(key), args[key], inlineOptions); + this.setGroupMap(toKebabCase(key), args[key], inlineOptions); }); } setGroupMap(key, val, inlineOptions) { @@ -90,13 +90,6 @@ class WebpackCLI extends GroupHelper { return core; } - /** - * Convert camelcase to kebabcase - * @param {string} string - */ - _toKebabCase(string) { - return string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); - } /** * Based on the parsed keys, the function will import and create From af62829d89c39e8253fdc6874c6c4ca17e5b12c1 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Thu, 19 Mar 2020 14:55:37 +0530 Subject: [PATCH 05/43] chore: remove global flag --- .../webpack-cli/lib/groups/AdvancedGroup.js | 53 ------------------- packages/webpack-cli/lib/utils/cli-flags.js | 9 ---- test/global/global.test.js | 53 ------------------- test/global/global1.js | 1 - test/global/global2.js | 1 - test/global/index.js | 5 -- 6 files changed, 122 deletions(-) delete mode 100644 test/global/global.test.js delete mode 100644 test/global/global1.js delete mode 100644 test/global/global2.js delete mode 100644 test/global/index.js diff --git a/packages/webpack-cli/lib/groups/AdvancedGroup.js b/packages/webpack-cli/lib/groups/AdvancedGroup.js index c3abafb23e4..98dea3e4e4b 100644 --- a/packages/webpack-cli/lib/groups/AdvancedGroup.js +++ b/packages/webpack-cli/lib/groups/AdvancedGroup.js @@ -77,59 +77,6 @@ class AdvancedGroup extends GroupHelper { if (args.target) { options.target = args.target; } - - if (args.global) { - const globalArrLen = args.global.length; - if (!globalArrLen) { - logger.warn('Argument to global flag is none'); - return; - } - if (globalArrLen === 1) { - logger.warn('Argument to global flag expected a key/value pair'); - return; - } - - const providePluginObject = {}; - args.global.forEach((arg, idx) => { - const isKey = idx % 2 === 0; - const isConcatArg = arg.includes('='); - if (isKey && isConcatArg) { - const splitIdx = arg.indexOf('='); - const argVal = arg.substr(splitIdx + 1); - const argKey = arg.substr(0, splitIdx); - if (!argVal.length) { - logger.warn(`Found unmatching value for global flag key '${argKey}'`); - return; - } - // eslint-disable-next-line no-prototype-builtins - if (providePluginObject.hasOwnProperty(argKey)) { - logger.warn(`Overriding key '${argKey}' for global flag`); - } - providePluginObject[argKey] = argVal; - return; - } - if (isKey) { - const nextArg = args.global[idx + 1]; - // eslint-disable-next-line no-prototype-builtins - if (providePluginObject.hasOwnProperty(arg)) { - logger.warn(`Overriding key '${arg}' for global flag`); - } - if (!nextArg) { - logger.warn(`Found unmatching value for global flag key '${arg}'`); - return; - } - providePluginObject[arg] = nextArg; - } - }); - - const { ProvidePlugin } = require('webpack'); - const globalVal = new ProvidePlugin(providePluginObject); - if (options && options.plugins) { - options.plugins.unshift(globalVal); - } else { - options.plugins = [globalVal]; - } - } } run() { this.resolveOptions(); diff --git a/packages/webpack-cli/lib/utils/cli-flags.js b/packages/webpack-cli/lib/utils/cli-flags.js index 28b7a832f2e..a4e7f9ae595 100644 --- a/packages/webpack-cli/lib/utils/cli-flags.js +++ b/packages/webpack-cli/lib/utils/cli-flags.js @@ -147,15 +147,6 @@ module.exports = { description: 'Load a given plugin', link: 'https://webpack.js.org/plugins/', }, - { - name: 'global', - usage: '--global myVar ./global.js', - alias: 'g', - type: String, - multiple: true, - group: ADVANCED_GROUP, - description: 'Declares and exposes a global variable', - }, { name: 'target', usage: '--target', diff --git a/test/global/global.test.js b/test/global/global.test.js deleted file mode 100644 index 3e284d1df01..00000000000 --- a/test/global/global.test.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -const path = require('path'); -const execa = require('execa'); -const { sync: spawnSync } = execa; - -const { run } = require('../utils/test-utils'); - -describe('global flag', () => { - it('warns if there are no arguments to flag', () => { - const { stderr } = run(__dirname, ['--global']); - expect(stderr).toContain('Argument to global flag is none'); - }); - - it('warns if there are no value for key', () => { - const { stderr } = run(__dirname, ['--global', 'myVar']); - expect(stderr).toContain('Argument to global flag expected a key/value pair'); - }); - - it('is able to inject one variable to global scope', () => { - const { stdout } = run(__dirname, ['--global', 'myVar', './global1.js']); - expect(stdout).toContain('option has not been set, webpack will fallback to'); - const executable = path.join(__dirname, './bin/main.js'); - const bundledScript = spawnSync('node', [executable]); - expect(bundledScript.stdout).toEqual('myVar ./global1.js'); - }); - - it('is able to inject multiple variables to global scope', () => { - const { stdout } = run(__dirname, ['--global', 'myVar', './global1.js', '--global', 'myVar2', './global2.js']); - expect(stdout).toContain('option has not been set, webpack will fallback to'); - const executable = path.join(__dirname, './bin/main.js'); - const bundledScript = spawnSync('node', [executable]); - expect(bundledScript.stdout).toEqual('myVar ./global1.js\nmyVar ./global2.js'); - }); - - it('understands = syntax', () => { - const { stdout } = run(__dirname, ['--global', 'myVar', './global1.js', '--global', 'myVar2=./global2.js']); - expect(stdout).toContain('option has not been set, webpack will fallback to'); - const executable = path.join(__dirname, './bin/main.js'); - const bundledScript = spawnSync('node', [executable]); - expect(bundledScript.stdout).toEqual('myVar ./global1.js\nmyVar ./global2.js'); - }); - - it('warns on multiple flags that are inconsistent', () => { - const result = run(__dirname, ['--global', 'myVar', './global1.js', '--global', 'myVar2']); - // eslint-disable-next-line - expect(result.stderr).toContain("Found unmatching value for global flag key 'myVar2'"); - - const result2 = run(__dirname, ['--global', 'myVar', './global1.js', '--global', 'myVar2=']); - // eslint-disable-next-line - expect(result2.stderr).toContain("Found unmatching value for global flag key 'myVar2'"); - }); -}); diff --git a/test/global/global1.js b/test/global/global1.js deleted file mode 100644 index e7d024ed70b..00000000000 --- a/test/global/global1.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = 'myVar ./global1.js'; diff --git a/test/global/global2.js b/test/global/global2.js deleted file mode 100644 index c80fbb2b620..00000000000 --- a/test/global/global2.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = 'myVar ./global2.js'; diff --git a/test/global/index.js b/test/global/index.js deleted file mode 100644 index c832cbbeaeb..00000000000 --- a/test/global/index.js +++ /dev/null @@ -1,5 +0,0 @@ -console.log(myVar); - -try { - console.log(myVar2); -} catch(e) {} From eab3500685a7114630921abefe1f758fb4b39147 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Thu, 19 Mar 2020 15:43:26 +0530 Subject: [PATCH 06/43] chore: improve arg parsing and tests --- packages/webpack-cli/lib/groups/StatsGroup.js | 3 +++ packages/webpack-cli/lib/groups/ZeroConfigGroup.js | 5 +++++ packages/webpack-cli/lib/utils/cli-flags.js | 11 ++--------- test/config/basic/basic-config.test.js | 2 +- test/entry/defaults-index/entry-multi-args.test.js | 2 +- test/stats/stats.test.js | 2 +- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/webpack-cli/lib/groups/StatsGroup.js b/packages/webpack-cli/lib/groups/StatsGroup.js index 36fc0e30ccd..b8654e8c5c5 100644 --- a/packages/webpack-cli/lib/groups/StatsGroup.js +++ b/packages/webpack-cli/lib/groups/StatsGroup.js @@ -23,6 +23,9 @@ class StatsGroup extends GroupHelper { this.opts.option.stats = { verbose: true, }; + } else if (!StatsGroup.validOptions().includes(this.args.stats)) { + logger.warn(`'${this.args.stats}' is invalid value for stats. Using 'normal' option for stats`); + this.opts.options.stats = 'normal'; } else { this.opts.options.stats = this.args.stats; } diff --git a/packages/webpack-cli/lib/groups/ZeroConfigGroup.js b/packages/webpack-cli/lib/groups/ZeroConfigGroup.js index 2baedb23ea2..0d62ccb62bb 100644 --- a/packages/webpack-cli/lib/groups/ZeroConfigGroup.js +++ b/packages/webpack-cli/lib/groups/ZeroConfigGroup.js @@ -32,7 +32,12 @@ class ZeroConfigGroup extends GroupHelper { return NONE; } } + if (this.args.mode) { + if (this.args.mode !== PRODUCTION && this.args.mode !== DEVELOPMENT && this.args.mode !== NONE) { + logger.warn('You provided an invalid value for "mode" option.'); + return PRODUCTION; + } return this.args.mode; } if (this.args.prod) { diff --git a/packages/webpack-cli/lib/utils/cli-flags.js b/packages/webpack-cli/lib/utils/cli-flags.js index a4e7f9ae595..e6cc3012aaa 100644 --- a/packages/webpack-cli/lib/utils/cli-flags.js +++ b/packages/webpack-cli/lib/utils/cli-flags.js @@ -222,15 +222,8 @@ module.exports = { }, { name: 'mode', - usage: '--mode ', - type: (value) => { - if (value === 'development' || value === 'production' || value === 'none') { - return value; - } else { - logger.warn('You provided an invalid value for "mode" option.'); - return 'production'; - } - }, + usage: '--mode ', + type: String, group: ZERO_CONFIG_GROUP, description: 'Defines the mode to pass to webpack', link: 'https://webpack.js.org/concepts/#mode', diff --git a/test/config/basic/basic-config.test.js b/test/config/basic/basic-config.test.js index a98e3492652..dec2eef82bb 100644 --- a/test/config/basic/basic-config.test.js +++ b/test/config/basic/basic-config.test.js @@ -6,7 +6,7 @@ const { run } = require('../../utils/test-utils'); describe('basic config file', () => { it('is able to understand and parse a very basic configuration file', (done) => { const { stdout, stderr } = run(__dirname, ['-c', resolve(__dirname, 'webpack.config.js'), '--output', './binary/a.bundle.js']); - expect(stderr).toContain('Duplicate flags found, defaulting to last set value'); + expect(stderr).toBeFalsy(); expect(stdout).not.toBe(undefined); stat(resolve(__dirname, './binary/a.bundle.js'), (err, stats) => { expect(err).toBe(null); diff --git a/test/entry/defaults-index/entry-multi-args.test.js b/test/entry/defaults-index/entry-multi-args.test.js index abc7a291a98..270e0f8a94d 100644 --- a/test/entry/defaults-index/entry-multi-args.test.js +++ b/test/entry/defaults-index/entry-multi-args.test.js @@ -19,7 +19,7 @@ describe('single entry flag index present', () => { it('finds default index file, compiles and overrides with flags successfully', (done) => { const { stderr } = run(__dirname, ['--output', 'bin/main.js']); - expect(stderr).toContain('Duplicate flags found, defaulting to last set value'); + expect(stderr).toBeFalsy(); stat(resolve(__dirname, './bin/main.js'), (err, stats) => { expect(err).toBe(null); diff --git a/test/stats/stats.test.js b/test/stats/stats.test.js index 7eca9cf0e2e..656a6633825 100644 --- a/test/stats/stats.test.js +++ b/test/stats/stats.test.js @@ -48,7 +48,7 @@ describe('stats flag', () => { it('should warn when an unknown flag stats value is passed', () => { const { stderr, stdout } = run(__dirname, ['--stats', 'foo']); expect(stderr).toBeTruthy(); - expect(stderr).toContain('No value recognised for "stats" option'); + expect(stderr).toContain('invalid value for stats'); expect(stdout).toBeTruthy(); }); }); From ab17bf068e8763e9e090bfbd63f1edf107bc705f Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Sat, 21 Mar 2020 12:39:25 +0530 Subject: [PATCH 07/43] chore: remove command-line-args --- packages/webpack-cli/lib/webpack-cli.js | 5 ----- packages/webpack-cli/package.json | 1 - 2 files changed, 6 deletions(-) diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index a5539442529..de12593f4f9 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -81,11 +81,6 @@ class WebpackCLI extends GroupHelper { return options; } - // It exposes "command-line-args" function - commandLineArgs(...args) { - return commandArgs(...args); - } - getCoreFlags() { return core; } diff --git a/packages/webpack-cli/package.json b/packages/webpack-cli/package.json index f95da5296c3..397fd074556 100644 --- a/packages/webpack-cli/package.json +++ b/packages/webpack-cli/package.json @@ -27,7 +27,6 @@ "@webpack-cli/info": "^1.0.1-alpha.4", "ansi-escapes": "^4.3.1", "chalk": "^3.0.0", - "command-line-args": "^5.1.1", "command-line-usage": "^6.1.0", "commander": "^5.0.0", "enquirer": "^2.3.4", From 327fc7d99ad2b41d81dff0f686a02d045c6e4a24 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Sat, 21 Mar 2020 12:59:22 +0530 Subject: [PATCH 08/43] chore: remove instance of command-line-args --- packages/webpack-cli/lib/bootstrap.js | 5 ++-- packages/webpack-cli/lib/utils/arg-parser.js | 26 +++++++++++--------- packages/webpack-cli/lib/webpack-cli.js | 1 - 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index 0f3901f1aa9..43feb670daf 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -98,9 +98,8 @@ async function runCLI(cli, commandIsUsed) { const newArgKeys = Object.keys(argsMap).filter((arg) => !keysToDelete.includes(argsMap[arg].pos)); // eslint-disable-next-line require-atomic-updates process.argv = newArgKeys; - args = cli.commandLineArgs(core, { stopAtFirstUnknown: false, partial: true }); - - await cli.run(args, core); + args = argParser("", core, process.argv).opts; + await cli.run(args.opts(), core); process.stdout.write('\n'); logger.warn('Duplicate flags found, defaulting to last set value'); } else { diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index 0ccd86f9a26..3053c0a7622 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -7,22 +7,26 @@ const commander = require("commander"); * @param {object[]} options Array of objects with details about flags * @param {string[]} args process.argv or it's subset */ -function argParser(name, options, args, helpFunction, versionFunction) { +function argParser(name = "", options, args, helpFunction, versionFunction) { const parser = new commander.Command(); // Set parser name parser.name(name); - // Use customized version output - parser.on('option:version', () => { - versionFunction(args); - process.exit(0); - }); + // Use customized version output if available + if (versionFunction) { + parser.on('option:version', () => { + versionFunction(args); + process.exit(0); + }); + } - // Use customised help output - parser.on('option:help', () => { - helpFunction(args); - process.exit(0); - }); + // Use customised help output is avaliable + if (helpFunction) { + parser.on('option:help', () => { + helpFunction(args); + process.exit(0); + }); + } // Allow execution if unknown arguments are present parser.allowUnknownOption(true); diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index de12593f4f9..03fe305be4a 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -4,7 +4,6 @@ const GroupHelper = require('./utils/GroupHelper'); const { Compiler } = require('./utils/Compiler'); const { groups, core } = require('./utils/cli-flags'); const webpackMerge = require('webpack-merge'); -const commandArgs = require('command-line-args'); const { toKebabCase } = require('./utils/helpers'); const defaultCommands = { init: 'init', From 05173d8741d3c4efc836b2363c1bb6ccd1f248b6 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Sat, 21 Mar 2020 13:19:31 +0530 Subject: [PATCH 09/43] chore: add explaination to the change --- packages/webpack-cli/lib/groups/OutputGroup.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/webpack-cli/lib/groups/OutputGroup.js b/packages/webpack-cli/lib/groups/OutputGroup.js index be24b101b1d..97e3ba0d2af 100644 --- a/packages/webpack-cli/lib/groups/OutputGroup.js +++ b/packages/webpack-cli/lib/groups/OutputGroup.js @@ -27,7 +27,10 @@ class OutputGroup extends GroupHelper { const { args } = this; if (args) { const { output } = args; - if (!output) { + // TODO: Remove comment before merge + // We need to show warning when empty output flag is supplied + // which is set to boolean true by commander + if (!output || output === true) { return; } const outputInfo = path.parse(output); From 5244654993172d084555912575e36438907f6667 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Sun, 22 Mar 2020 17:51:37 +0530 Subject: [PATCH 10/43] chore: fix --- packages/webpack-cli/lib/bootstrap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index 43feb670daf..c3acc2e1dd7 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -98,7 +98,7 @@ async function runCLI(cli, commandIsUsed) { const newArgKeys = Object.keys(argsMap).filter((arg) => !keysToDelete.includes(argsMap[arg].pos)); // eslint-disable-next-line require-atomic-updates process.argv = newArgKeys; - args = argParser("", core, process.argv).opts; + args = argParser("", core, process.argv); await cli.run(args.opts(), core); process.stdout.write('\n'); logger.warn('Duplicate flags found, defaulting to last set value'); From 777f8e54318ded01f67c1ee2ad9be0f09e8bd036 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Tue, 24 Mar 2020 18:20:34 +0530 Subject: [PATCH 11/43] chore: migrate serve package --- packages/serve/src/index.ts | 31 +++++++++++++++---------- packages/webpack-cli/lib/webpack-cli.js | 11 +++++++++ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index 3c4587efb75..c5788d8a39c 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -15,18 +15,25 @@ export default function serve(...args): void { const core = cli.getCoreFlags(); // partial parsing usage: https://github.com/75lb/command-line-args/wiki/Partial-parsing - // since the webpack flags have the 'entry' option set as it's default option, - // we need to parse the dev server args first. Otherwise, the webpack parsing could snatch - // one of the dev server's options and set it to this 'entry' option. - // see: https://github.com/75lb/command-line-args/blob/master/doc/option-definition.md#optiondefaultoption--boolean - const devServerArgs = cli.commandLineArgs(devServer, { argv: args, partial: true }); - const webpackArgs = cli.commandLineArgs(core, { argv: devServerArgs._unknown || [], stopAtFirstUnknown: false }); - const finalArgs = argsToCamelCase(devServerArgs._all || {}); - // pass along the 'hot' argument to the dev server if it exists - if (webpackArgs && webpackArgs._all && typeof webpackArgs._all.hot !== 'undefined') { - finalArgs['hot'] = webpackArgs._all.hot; + // since the webpack flags have the 'entry' option set as it's default option, + // we need to parse the dev server args first. Otherwise, the webpack parsing could snatch + // one of the dev server's options and set it to this 'entry' option. + // see: https://github.com/75lb/command-line-args/blob/master/doc/option-definition.md#optiondefaultoption--boolean + const devServerArgs = cli.argParser("", devServer, args); + const webpackArgs = cli.argParser("", core, devServerArgs.args); + const finalArgs = argsToCamelCase(devServerArgs.opts() || {}); + // pass along the 'hot' argument to the dev server if it exists + if (webpackArgs && webpackArgs.opts() && typeof webpackArgs.opts().hot !== 'undefined') { + finalArgs['hot'] = webpackArgs.opts().hot; } - cli.getCompiler(webpackArgs, core).then((compiler): void => { - startDevServer(compiler, finalArgs); + + Object.keys(finalArgs).forEach(arg => { + if (finalArgs[arg] === undefined) { + delete finalArgs[arg]; + } }); + + cli.getCompiler(webpackArgs.opts(), core).then((compiler): void => { + startDevServer(compiler, finalArgs); + }); } diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index 03fe305be4a..078b550bbf8 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -5,6 +5,8 @@ const { Compiler } = require('./utils/Compiler'); const { groups, core } = require('./utils/cli-flags'); const webpackMerge = require('webpack-merge'); const { toKebabCase } = require('./utils/helpers'); +const argParser = require('./utils/arg-parser'); + const defaultCommands = { init: 'init', loader: 'generate-loader', @@ -80,6 +82,15 @@ class WebpackCLI extends GroupHelper { return options; } + /** + * Expose commander argParser + * @param {...any} args args for argParser + */ + argParser(...args) { + return argParser(...args); + } + + getCoreFlags() { return core; } From b9a49320144b5e45a64e67f15562a70051197ed5 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Tue, 24 Mar 2020 18:48:22 +0530 Subject: [PATCH 12/43] chore: handle error --- packages/serve/src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index c5788d8a39c..f7cf6f26fc6 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -33,6 +33,10 @@ export default function serve(...args): void { } }); + if (webpackArgs.args.length > 0) { + process.stderr.write(`Unknown argument: ${webpackArgs.args} flag`); + } + cli.getCompiler(webpackArgs.opts(), core).then((compiler): void => { startDevServer(compiler, finalArgs); }); From ad0cc8f2d1f7eefcb4944ff942532ef871f3f39e Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Thu, 26 Mar 2020 12:30:31 +0530 Subject: [PATCH 13/43] chore: reactor argparser --- packages/serve/src/index.ts | 27 +++++++++++--------- packages/webpack-cli/lib/bootstrap.js | 2 +- packages/webpack-cli/lib/utils/arg-parser.js | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index f7cf6f26fc6..27a1a035f7e 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -1,7 +1,7 @@ -import { devServer } from 'webpack-dev-server/bin/cli-flags'; -import WebpackCLI from 'webpack-cli'; -import startDevServer from './startDevServer'; -import argsToCamelCase from './args-to-camel-case'; +import { devServer } from "webpack-dev-server/bin/cli-flags"; +import WebpackCLI from "webpack-cli"; +import startDevServer from "./startDevServer"; +import argsToCamelCase from "./args-to-camel-case"; /** * @@ -11,20 +11,22 @@ import argsToCamelCase from './args-to-camel-case'; * @returns {Function} invokes the devServer API */ export default function serve(...args): void { - const cli = new WebpackCLI(); - const core = cli.getCoreFlags(); + const cli = new WebpackCLI(); + const core = cli.getCoreFlags(); + // partial parsing usage: https://github.com/75lb/command-line-args/wiki/Partial-parsing // since the webpack flags have the 'entry' option set as it's default option, // we need to parse the dev server args first. Otherwise, the webpack parsing could snatch // one of the dev server's options and set it to this 'entry' option. // see: https://github.com/75lb/command-line-args/blob/master/doc/option-definition.md#optiondefaultoption--boolean - const devServerArgs = cli.argParser("", devServer, args); - const webpackArgs = cli.argParser("", core, devServerArgs.args); - const finalArgs = argsToCamelCase(devServerArgs.opts() || {}); + const devServerArgs = cli.argParser(devServer, args); + const webpackArgs = cli.argParser(core, args, process.title, cli.runHelp, cli.runVersion); + + const finalArgs = argsToCamelCase(devServerArgs.opts() || {}); // pass along the 'hot' argument to the dev server if it exists - if (webpackArgs && webpackArgs.opts() && typeof webpackArgs.opts().hot !== 'undefined') { - finalArgs['hot'] = webpackArgs.opts().hot; + if (webpackArgs && webpackArgs.opts() && webpackArgs.opts().hot !== undefined) { + finalArgs['hot'] = webpackArgs.opts().hot; } Object.keys(finalArgs).forEach(arg => { @@ -34,7 +36,8 @@ export default function serve(...args): void { }); if (webpackArgs.args.length > 0) { - process.stderr.write(`Unknown argument: ${webpackArgs.args} flag`); + process.stderr.write(`Unknown argument: ${webpackArgs.args}`); + return; } cli.getCompiler(webpackArgs.opts(), core).then((compiler): void => { diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index c3acc2e1dd7..8cc20b168d2 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -43,7 +43,7 @@ const resolveNegatedArgs = (args) => { }; async function runCLI(cli, commandIsUsed) { - const parsedArgs = argParser('webpack', core, process.argv, cli.runHelp, cli.runVersion); + const parsedArgs = argParser(core, process.argv, process.title, cli.runHelp, cli.runVersion); if (parsedArgs.args.includes('help')) { cli.runHelp(process.argv); diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index 3053c0a7622..4fa97f4133a 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -7,7 +7,7 @@ const commander = require("commander"); * @param {object[]} options Array of objects with details about flags * @param {string[]} args process.argv or it's subset */ -function argParser(name = "", options, args, helpFunction, versionFunction) { +function argParser(options, args, name = "", helpFunction = undefined, versionFunction = undefined) { const parser = new commander.Command(); // Set parser name parser.name(name); From 1b3212d8ba9225fdf048c5f0af2787787093534f Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Thu, 26 Mar 2020 14:56:16 +0530 Subject: [PATCH 14/43] chore: get serve args from process.argv --- packages/serve/src/index.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index 27a1a035f7e..fdab886157e 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -10,21 +10,17 @@ import argsToCamelCase from "./args-to-camel-case"; * @param {String[]} args - args processed from the CLI * @returns {Function} invokes the devServer API */ -export default function serve(...args): void { +export default function serve(): void { const cli = new WebpackCLI(); const core = cli.getCoreFlags(); - // partial parsing usage: https://github.com/75lb/command-line-args/wiki/Partial-parsing + const filteredArgs = process.argv.filter(arg => (arg != "serve")); + const devServerArgs = cli.argParser(devServer, filteredArgs); + const webpackArgs = cli.argParser(core, filteredArgs, process.title, cli.runHelp, cli.runVersion); + const finalArgs = argsToCamelCase(devServerArgs.opts() || {}); - // since the webpack flags have the 'entry' option set as it's default option, - // we need to parse the dev server args first. Otherwise, the webpack parsing could snatch - // one of the dev server's options and set it to this 'entry' option. - // see: https://github.com/75lb/command-line-args/blob/master/doc/option-definition.md#optiondefaultoption--boolean - const devServerArgs = cli.argParser(devServer, args); - const webpackArgs = cli.argParser(core, args, process.title, cli.runHelp, cli.runVersion); - const finalArgs = argsToCamelCase(devServerArgs.opts() || {}); - // pass along the 'hot' argument to the dev server if it exists + // pass along the 'hot' argument to the dev server if it exists if (webpackArgs && webpackArgs.opts() && webpackArgs.opts().hot !== undefined) { finalArgs['hot'] = webpackArgs.opts().hot; } From 503a1f77f82935662d60fb160dabb3be63e4bbcd Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Thu, 26 Mar 2020 14:59:50 +0530 Subject: [PATCH 15/43] chore: sync message with test --- packages/serve/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index fdab886157e..558f738f772 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -32,7 +32,7 @@ export default function serve(): void { }); if (webpackArgs.args.length > 0) { - process.stderr.write(`Unknown argument: ${webpackArgs.args}`); + process.stderr.write(`Unknown option: ${webpackArgs.args}`); return; } From 7cf46bf478202c477bd435effc1d5b55d949c9f1 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Fri, 17 Apr 2020 13:07:44 -0500 Subject: [PATCH 16/43] chore: fix styling --- packages/serve/src/index.ts | 27 +++++++++---------- packages/webpack-cli/lib/bootstrap.js | 5 ++-- .../webpack-cli/lib/groups/AdvancedGroup.js | 1 - .../webpack-cli/lib/groups/ZeroConfigGroup.js | 2 +- packages/webpack-cli/lib/utils/arg-parser.js | 4 +-- packages/webpack-cli/lib/utils/helpers.js | 2 +- packages/webpack-cli/lib/webpack-cli.js | 2 -- 7 files changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index 558f738f772..bef8c1f2db8 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -1,7 +1,7 @@ -import { devServer } from "webpack-dev-server/bin/cli-flags"; -import WebpackCLI from "webpack-cli"; -import startDevServer from "./startDevServer"; -import argsToCamelCase from "./args-to-camel-case"; +import { devServer } from 'webpack-dev-server/bin/cli-flags'; +import WebpackCLI from 'webpack-cli'; +import startDevServer from './startDevServer'; +import argsToCamelCase from './args-to-camel-case'; /** * @@ -11,21 +11,20 @@ import argsToCamelCase from "./args-to-camel-case"; * @returns {Function} invokes the devServer API */ export default function serve(): void { - const cli = new WebpackCLI(); - const core = cli.getCoreFlags(); + const cli = new WebpackCLI(); + const core = cli.getCoreFlags(); - const filteredArgs = process.argv.filter(arg => (arg != "serve")); + const filteredArgs = process.argv.filter((arg) => arg != 'serve'); const devServerArgs = cli.argParser(devServer, filteredArgs); - const webpackArgs = cli.argParser(core, filteredArgs, process.title, cli.runHelp, cli.runVersion); + const webpackArgs = cli.argParser(core, filteredArgs, process.title, cli.runHelp, cli.runVersion); const finalArgs = argsToCamelCase(devServerArgs.opts() || {}); - // pass along the 'hot' argument to the dev server if it exists - if (webpackArgs && webpackArgs.opts() && webpackArgs.opts().hot !== undefined) { + if (webpackArgs && webpackArgs.opts() && webpackArgs.opts().hot !== undefined) { finalArgs['hot'] = webpackArgs.opts().hot; } - Object.keys(finalArgs).forEach(arg => { + Object.keys(finalArgs).forEach((arg) => { if (finalArgs[arg] === undefined) { delete finalArgs[arg]; } @@ -36,7 +35,7 @@ export default function serve(): void { return; } - cli.getCompiler(webpackArgs.opts(), core).then((compiler): void => { - startDevServer(compiler, finalArgs); - }); + cli.getCompiler(webpackArgs.opts(), core).then((compiler): void => { + startDevServer(compiler, finalArgs); + }); } diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index 8cc20b168d2..269348e13be 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -7,7 +7,7 @@ require('./utils/process-log'); process.title = 'webpack-cli'; -const isFlagPresent = (args, flag) => args.find((arg) => [flag, `--${flag}`].includes(arg)); +// const isFlagPresent = (args, flag) => args.find((arg) => [flag, `--${flag}`].includes(arg)); const isArgCommandName = (arg, cmd) => arg === cmd.name || arg === cmd.alias; const removeCmdFromArgs = (args, cmd) => args.filter((arg) => !isArgCommandName(arg, cmd)); const normalizeFlags = (args, cmd) => { @@ -43,6 +43,7 @@ const resolveNegatedArgs = (args) => { }; async function runCLI(cli, commandIsUsed) { + let args; const parsedArgs = argParser(core, process.argv, process.title, cli.runHelp, cli.runVersion); if (parsedArgs.args.includes('help')) { @@ -98,7 +99,7 @@ async function runCLI(cli, commandIsUsed) { const newArgKeys = Object.keys(argsMap).filter((arg) => !keysToDelete.includes(argsMap[arg].pos)); // eslint-disable-next-line require-atomic-updates process.argv = newArgKeys; - args = argParser("", core, process.argv); + args = argParser('', core, process.argv); await cli.run(args.opts(), core); process.stdout.write('\n'); logger.warn('Duplicate flags found, defaulting to last set value'); diff --git a/packages/webpack-cli/lib/groups/AdvancedGroup.js b/packages/webpack-cli/lib/groups/AdvancedGroup.js index 98dea3e4e4b..dcedc86bf4c 100644 --- a/packages/webpack-cli/lib/groups/AdvancedGroup.js +++ b/packages/webpack-cli/lib/groups/AdvancedGroup.js @@ -1,5 +1,4 @@ const GroupHelper = require('../utils/GroupHelper'); -const logger = require('../utils/logger'); class AdvancedGroup extends GroupHelper { constructor(options) { diff --git a/packages/webpack-cli/lib/groups/ZeroConfigGroup.js b/packages/webpack-cli/lib/groups/ZeroConfigGroup.js index 0d62ccb62bb..33e96f75916 100644 --- a/packages/webpack-cli/lib/groups/ZeroConfigGroup.js +++ b/packages/webpack-cli/lib/groups/ZeroConfigGroup.js @@ -20,7 +20,7 @@ class ZeroConfigGroup extends GroupHelper { if (process.env.NODE_ENV && (process.env.NODE_ENV === PRODUCTION || process.env.NODE_ENV === DEVELOPMENT)) { return process.env.NODE_ENV; } else { - if ((this.args.mode !== undefined ) && (this.args.dev || this.args.prod)) { + if (this.args.mode !== undefined && (this.args.dev || this.args.prod)) { logger.warn( `You provided both ${this.args.mode ? 'mode' : 'no-mode'} and ${ this.args.prod ? '--prod' : '--dev' diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index 4fa97f4133a..03268e2a42f 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -1,4 +1,4 @@ -const commander = require("commander"); +const commander = require('commander'); /** * Creates Argument parser corresponding to the supplied options @@ -7,7 +7,7 @@ const commander = require("commander"); * @param {object[]} options Array of objects with details about flags * @param {string[]} args process.argv or it's subset */ -function argParser(options, args, name = "", helpFunction = undefined, versionFunction = undefined) { +function argParser(options, args, name = '', helpFunction = undefined, versionFunction = undefined) { const parser = new commander.Command(); // Set parser name parser.name(name); diff --git a/packages/webpack-cli/lib/utils/helpers.js b/packages/webpack-cli/lib/utils/helpers.js index 34fdd0a55d4..4a3fd9f3382 100644 --- a/packages/webpack-cli/lib/utils/helpers.js +++ b/packages/webpack-cli/lib/utils/helpers.js @@ -4,7 +4,7 @@ * @returns {string} output string in kebab-case */ function toKebabCase(str) { - return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase() + return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); } module.exports = { toKebabCase }; diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index 078b550bbf8..a141e82ddbd 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -90,12 +90,10 @@ class WebpackCLI extends GroupHelper { return argParser(...args); } - getCoreFlags() { return core; } - /** * Based on the parsed keys, the function will import and create * a group that handles respective values From 231f1818e80858ebf9313003cfe8232272880749 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Fri, 17 Apr 2020 22:54:13 -0500 Subject: [PATCH 17/43] chore: fix problem with no mode flag --- packages/webpack-cli/lib/utils/cli-flags.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/webpack-cli/lib/utils/cli-flags.js b/packages/webpack-cli/lib/utils/cli-flags.js index e6cc3012aaa..b552db2ccd0 100644 --- a/packages/webpack-cli/lib/utils/cli-flags.js +++ b/packages/webpack-cli/lib/utils/cli-flags.js @@ -228,14 +228,14 @@ module.exports = { description: 'Defines the mode to pass to webpack', link: 'https://webpack.js.org/concepts/#mode', }, - { - name: 'no-mode', - usage: '--no-mode', - type: Boolean, - group: ZERO_CONFIG_GROUP, - description: 'Sets mode="none" which disables any default behavior', - link: 'https://webpack.js.org/concepts/#mode', - }, + // { + // name: 'no-mode', + // usage: '--no-mode', + // type: Boolean, + // group: ZERO_CONFIG_GROUP, + // description: 'Sets mode="none" which disables any default behavior', + // link: 'https://webpack.js.org/concepts/#mode', + // }, { name: 'version', usage: '--version | --version ', From 0e1db3bbd500bb80b46ca71a53c9c765cf9d6e6f Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 18 Apr 2020 01:39:24 -0500 Subject: [PATCH 18/43] chore: started adding info parsing --- packages/info/src/index.ts | 2 +- packages/info/src/options.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/info/src/index.ts b/packages/info/src/index.ts index 10fbafeeb6d..4c282068855 100644 --- a/packages/info/src/index.ts +++ b/packages/info/src/index.ts @@ -32,7 +32,7 @@ const DEFAULT_DETAILS: Information = { export default async function info(...args): Promise { const cli = new WebpackCLI(); - const infoArgs = cli.commandLineArgs(options, { argv: args, stopAtFirstUnknown: false }); + const infoArgs = cli.argParser(options, args).opts(); const envinfoConfig = {}; if (infoArgs._unknown && infoArgs._unknown.length > 0) { diff --git a/packages/info/src/options.ts b/packages/info/src/options.ts index 68bae2e8063..ccfe315e1b4 100644 --- a/packages/info/src/options.ts +++ b/packages/info/src/options.ts @@ -1,6 +1,7 @@ export default [ { name: 'output', + usage: '--output ', type: String, description: 'To get the output in specified format (accept json or markdown)', }, From 24cbc8510883524835f703f7106cb58aca8574a3 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 18 Apr 2020 11:03:34 -0500 Subject: [PATCH 19/43] chore: parsing without first few parts of process argv --- packages/info/src/index.ts | 2 +- packages/serve/src/index.ts | 5 +++-- packages/webpack-cli/lib/bootstrap.js | 2 +- packages/webpack-cli/lib/utils/arg-parser.js | 15 ++++++++++++--- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/info/src/index.ts b/packages/info/src/index.ts index 4c282068855..383911bf538 100644 --- a/packages/info/src/index.ts +++ b/packages/info/src/index.ts @@ -32,7 +32,7 @@ const DEFAULT_DETAILS: Information = { export default async function info(...args): Promise { const cli = new WebpackCLI(); - const infoArgs = cli.argParser(options, args).opts(); + const infoArgs = cli.argParser(options, args, true).opts(); const envinfoConfig = {}; if (infoArgs._unknown && infoArgs._unknown.length > 0) { diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index bef8c1f2db8..21906435de5 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -15,8 +15,9 @@ export default function serve(): void { const core = cli.getCoreFlags(); const filteredArgs = process.argv.filter((arg) => arg != 'serve'); - const devServerArgs = cli.argParser(devServer, filteredArgs); - const webpackArgs = cli.argParser(core, filteredArgs, process.title, cli.runHelp, cli.runVersion); + const parsedDevServerArgs = cli.argParser(devServer, filteredArgs); + const devServerArgs = parsedDevServerArgs.opts(); + const webpackArgs = cli.argParser(core, filteredArgs, false, process.title, cli.runHelp, cli.runVersion); const finalArgs = argsToCamelCase(devServerArgs.opts() || {}); // pass along the 'hot' argument to the dev server if it exists diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index 269348e13be..cc66f1c7cab 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -44,7 +44,7 @@ const resolveNegatedArgs = (args) => { async function runCLI(cli, commandIsUsed) { let args; - const parsedArgs = argParser(core, process.argv, process.title, cli.runHelp, cli.runVersion); + const parsedArgs = argParser(core, process.argv, false, process.title, cli.runHelp, cli.runVersion); if (parsedArgs.args.includes('help')) { cli.runHelp(process.argv); diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index 03268e2a42f..360275eca86 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -6,8 +6,10 @@ const commander = require('commander'); * * @param {object[]} options Array of objects with details about flags * @param {string[]} args process.argv or it's subset + * @param {boolean} argsOnly false if all of process.argv has been provided, true if + * args is only a subset of process.argv that removes the first couple elements */ -function argParser(options, args, name = '', helpFunction = undefined, versionFunction = undefined) { +function argParser(options, args, argsOnly = false, name = '', helpFunction = undefined, versionFunction = undefined) { const parser = new commander.Command(); // Set parser name parser.name(name); @@ -34,12 +36,19 @@ function argParser(options, args, name = '', helpFunction = undefined, versionFu // Register options on the parser options.reduce((parserInstance, option) => { const flags = option.alias ? `-${option.alias}, --${option.name}` : `--${option.name}`; - const flagsWithType = option.type !== Boolean ? flags + ' [type]' : flags; + const flagsWithType = option.type !== Boolean ? flags + ' ' : flags; parserInstance.option(flagsWithType, option.description, option.defaultValue); return parserInstance; }, parser); - return parser.parse(args); + // if we are parsing a subset of process.argv that includes + // only the arguments themselves (e.g. ['--option', 'value']) + // then we need from: 'user' passed into commander parse + // otherwise we are parsing a full process.argv + // (e.g. ['node', '/path/to/...', '--option', 'value']) + const parseOptions = argsOnly ? { from: 'user' } : {}; + + return parser.parse(args, parseOptions); } module.exports = argParser; From cbbed324ec4e23d1d86fd699c80e80b5409331c4 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 18 Apr 2020 11:06:28 -0500 Subject: [PATCH 20/43] chore: fixed args calling in serve --- packages/serve/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index 21906435de5..5a8db876274 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -18,7 +18,7 @@ export default function serve(): void { const parsedDevServerArgs = cli.argParser(devServer, filteredArgs); const devServerArgs = parsedDevServerArgs.opts(); const webpackArgs = cli.argParser(core, filteredArgs, false, process.title, cli.runHelp, cli.runVersion); - const finalArgs = argsToCamelCase(devServerArgs.opts() || {}); + const finalArgs = argsToCamelCase(devServerArgs || {}); // pass along the 'hot' argument to the dev server if it exists if (webpackArgs && webpackArgs.opts() && webpackArgs.opts().hot !== undefined) { From edf38f288e154013753b3f83f960526329e1b8d9 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 18 Apr 2020 11:11:53 -0500 Subject: [PATCH 21/43] chore: fixing serve --- packages/serve/src/index.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index 5a8db876274..9de85614335 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -17,12 +17,13 @@ export default function serve(): void { const filteredArgs = process.argv.filter((arg) => arg != 'serve'); const parsedDevServerArgs = cli.argParser(devServer, filteredArgs); const devServerArgs = parsedDevServerArgs.opts(); - const webpackArgs = cli.argParser(core, filteredArgs, false, process.title, cli.runHelp, cli.runVersion); + const parsedWebpackArgs = cli.argParser(core, parsedDevServerArgs.args, true, process.title, cli.runHelp, cli.runVersion); + const webpackArgs = parsedWebpackArgs.opts(); const finalArgs = argsToCamelCase(devServerArgs || {}); // pass along the 'hot' argument to the dev server if it exists - if (webpackArgs && webpackArgs.opts() && webpackArgs.opts().hot !== undefined) { - finalArgs['hot'] = webpackArgs.opts().hot; + if (webpackArgs && webpackArgs.hot !== undefined) { + finalArgs['hot'] = webpackArgs.hot; } Object.keys(finalArgs).forEach((arg) => { @@ -31,12 +32,12 @@ export default function serve(): void { } }); - if (webpackArgs.args.length > 0) { - process.stderr.write(`Unknown option: ${webpackArgs.args}`); + if (parsedWebpackArgs.args.length > 0) { + process.stderr.write(`Unknown option: ${parsedWebpackArgs.args}\n`); return; } - cli.getCompiler(webpackArgs.opts(), core).then((compiler): void => { + cli.getCompiler(webpackArgs, core).then((compiler): void => { startDevServer(compiler, finalArgs); }); } From a7fa0908093a3d1cfc97ffe98d9ab6370b90e993 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 18 Apr 2020 13:51:42 -0500 Subject: [PATCH 22/43] chore: fix no-mode tests --- packages/webpack-cli/lib/bootstrap.js | 3 ++- .../webpack-cli/lib/groups/ZeroConfigGroup.js | 6 ++++-- packages/webpack-cli/lib/utils/cli-flags.js | 18 +++++++++--------- test/no-mode/no-mode.test.js | 8 +++++--- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index cc66f1c7cab..7b28697f038 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -72,7 +72,8 @@ async function runCLI(cli, commandIsUsed) { cliExecuter(); return; } - const result = await cli.run(parsedArgs.opts(), core); + const parsedArgsOpts = parsedArgs.opts(); + const result = await cli.run(parsedArgsOpts, core); if (!result) { return; } diff --git a/packages/webpack-cli/lib/groups/ZeroConfigGroup.js b/packages/webpack-cli/lib/groups/ZeroConfigGroup.js index 33e96f75916..7259a4e835b 100644 --- a/packages/webpack-cli/lib/groups/ZeroConfigGroup.js +++ b/packages/webpack-cli/lib/groups/ZeroConfigGroup.js @@ -20,7 +20,9 @@ class ZeroConfigGroup extends GroupHelper { if (process.env.NODE_ENV && (process.env.NODE_ENV === PRODUCTION || process.env.NODE_ENV === DEVELOPMENT)) { return process.env.NODE_ENV; } else { - if (this.args.mode !== undefined && (this.args.dev || this.args.prod)) { + // commander sets mode to false if --no-mode is specified + const noMode = this.args.mode === false; + if ((this.args.mode || noMode) && (this.args.dev || this.args.prod)) { logger.warn( `You provided both ${this.args.mode ? 'mode' : 'no-mode'} and ${ this.args.prod ? '--prod' : '--dev' @@ -44,7 +46,7 @@ class ZeroConfigGroup extends GroupHelper { return PRODUCTION; } else if (this.args.dev) { return DEVELOPMENT; - } else if (this.args.noMode) { + } else if (noMode) { return NONE; } return PRODUCTION; diff --git a/packages/webpack-cli/lib/utils/cli-flags.js b/packages/webpack-cli/lib/utils/cli-flags.js index b552db2ccd0..def246fe544 100644 --- a/packages/webpack-cli/lib/utils/cli-flags.js +++ b/packages/webpack-cli/lib/utils/cli-flags.js @@ -222,20 +222,20 @@ module.exports = { }, { name: 'mode', - usage: '--mode ', + usage: '--mode ', type: String, group: ZERO_CONFIG_GROUP, description: 'Defines the mode to pass to webpack', link: 'https://webpack.js.org/concepts/#mode', }, - // { - // name: 'no-mode', - // usage: '--no-mode', - // type: Boolean, - // group: ZERO_CONFIG_GROUP, - // description: 'Sets mode="none" which disables any default behavior', - // link: 'https://webpack.js.org/concepts/#mode', - // }, + { + name: 'no-mode', + usage: '--no-mode', + type: Boolean, + group: ZERO_CONFIG_GROUP, + description: 'Sets mode="none" which disables any default behavior', + link: 'https://webpack.js.org/concepts/#mode', + }, { name: 'version', usage: '--version | --version ', diff --git a/test/no-mode/no-mode.test.js b/test/no-mode/no-mode.test.js index aa71c614dfe..f2f743d2661 100644 --- a/test/no-mode/no-mode.test.js +++ b/test/no-mode/no-mode.test.js @@ -38,10 +38,11 @@ describe('no-mode flag', () => { }); }); - it('should load a production config when --mode=production & --no-mode are passed', (done) => { + it('should load a none config when --mode=production is passed before --no-mode', (done) => { const { stderr, stdout } = run(__dirname, ['--mode', 'production', '--no-mode']); expect(stderr).toBeFalsy(); expect(stdout).toBeTruthy(); + expect(stdout).not.toContain('main.js.map'); stat(resolve(__dirname, './bin/main.js'), (err, stats) => { expect(err).toBe(null); @@ -50,10 +51,11 @@ describe('no-mode flag', () => { }); }); - it('should load a development config when --mode=development and --no-mode are passed', (done) => { - const { stderr, stdout } = run(__dirname, ['--mode', 'development', '--no-mode']); + it('should load a none config when --mode=production is passed after --no-mode', (done) => { + const { stderr, stdout } = run(__dirname, ['--no-mode', '--mode', 'production']); expect(stderr).toBeFalsy(); expect(stdout).toBeTruthy(); + expect(stdout).toContain('main.js.map'); stat(resolve(__dirname, './bin/main.js'), (err, stats) => { expect(err).toBe(null); From ab8e885386390fa18a6fb3970e1ca5364f313a63 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 18 Apr 2020 14:52:56 -0500 Subject: [PATCH 23/43] chore: fix output defaults tests --- packages/webpack-cli/lib/utils/arg-parser.js | 2 +- test/defaults/output-defaults.test.js | 21 +++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index 360275eca86..16fa5baf4e1 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -36,7 +36,7 @@ function argParser(options, args, argsOnly = false, name = '', helpFunction = un // Register options on the parser options.reduce((parserInstance, option) => { const flags = option.alias ? `-${option.alias}, --${option.name}` : `--${option.name}`; - const flagsWithType = option.type !== Boolean ? flags + ' ' : flags; + const flagsWithType = option.type !== Boolean ? flags + ' ' : flags; parserInstance.option(flagsWithType, option.description, option.defaultValue); return parserInstance; }, parser); diff --git a/test/defaults/output-defaults.test.js b/test/defaults/output-defaults.test.js index 947c4ba2366..8e04eb511ee 100644 --- a/test/defaults/output-defaults.test.js +++ b/test/defaults/output-defaults.test.js @@ -5,18 +5,18 @@ const { run } = require('../utils/test-utils'); describe('output flag defaults', () => { it('should create default file for a given directory', (done) => { - run(__dirname, ['--entry', './a.js', '--output', './binary'], false); - + const { stdout } = run(__dirname, ['--entry', './a.js', '--output', './binary'], false); + // Should print a warning about config fallback since we did not supply --defaults + expect(stdout).toContain('option has not been set, webpack will fallback to'); stat(resolve(__dirname, './binary/main.js'), (err, stats) => { expect(err).toBe(null); expect(stats.isFile()).toBe(true); done(); }); }); - it('set default output directory on empty flag', (done) => { - const { stdout } = run(__dirname, ['--entry', './a.js', '--output'], false); - // Should print a warning about config fallback since we did not supply --defaults - expect(stdout).toContain('option has not been set, webpack will fallback to'); + + it('set default output directory on no output flag', (done) => { + run(__dirname, ['--entry', './a.js'], false); stat(resolve(__dirname, './dist/main.js'), (err, stats) => { expect(err).toBe(null); @@ -24,9 +24,16 @@ describe('output flag defaults', () => { done(); }); }); + + it('throw error on empty output flag', () => { + const { stderr } = run(__dirname, ['--entry', './a.js', '--output'], false); + expect(stderr).toContain("error: option '-o, --output ' argument missing"); + }); + it('should not throw when --defaults flag is passed', (done) => { - const { stderr } = run(__dirname, ['--defaults'], false); + const { stdout, stderr } = run(__dirname, ['--defaults'], false); // When using --defaults it should not print warnings about config fallback + expect(stdout).not.toContain('option has not been set, webpack will fallback to'); expect(stderr).toBeFalsy(); stat(resolve(__dirname, './dist/main.js'), (err, stats) => { expect(err).toBe(null); From c4a4d6959fdbacbe5519b3a06f5e143e8b522f08 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 18 Apr 2020 15:06:40 -0500 Subject: [PATCH 24/43] chore: handle default webpack entry arg --- packages/webpack-cli/lib/bootstrap.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index 7b28697f038..a5943ca597c 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -62,7 +62,7 @@ async function runCLI(cli, commandIsUsed) { return await cli.runCommand(commandIsUsed, ...args); } else { try { - if (parsedArgs.args.length > 0) { + if (parsedArgs.args.length > 1) { resolveNegatedArgs(parsedArgs.args); parsedArgs.args .filter((e) => e) @@ -73,6 +73,12 @@ async function runCLI(cli, commandIsUsed) { return; } const parsedArgsOpts = parsedArgs.opts(); + // handle the default webpack entry CLI argument, where instead + // of doing 'webpack-cli --entry ./index.js' you can simply do + // 'webpack-cli ./index.js' + if (parsedArgs.args.length === 1) { + parsedArgsOpts.entry = parsedArgs.args[0]; + } const result = await cli.run(parsedArgsOpts, core); if (!result) { return; From 4eccb84168c15439b2dfe22fecd4b03100a86cd5 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 18 Apr 2020 15:14:29 -0500 Subject: [PATCH 25/43] chore: fix basic config test --- test/config/basic/basic-config.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/config/basic/basic-config.test.js b/test/config/basic/basic-config.test.js index dec2eef82bb..01feb8afb94 100644 --- a/test/config/basic/basic-config.test.js +++ b/test/config/basic/basic-config.test.js @@ -5,7 +5,11 @@ const { run } = require('../../utils/test-utils'); describe('basic config file', () => { it('is able to understand and parse a very basic configuration file', (done) => { - const { stdout, stderr } = run(__dirname, ['-c', resolve(__dirname, 'webpack.config.js'), '--output', './binary/a.bundle.js']); + const { stdout, stderr } = run( + __dirname, + ['-c', resolve(__dirname, 'webpack.config.js'), '--output', './binary/a.bundle.js'], + false, + ); expect(stderr).toBeFalsy(); expect(stdout).not.toBe(undefined); stat(resolve(__dirname, './binary/a.bundle.js'), (err, stats) => { From 26c65ccfd8ba43ce7f5944805066c9223a507085 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 18 Apr 2020 15:29:53 -0500 Subject: [PATCH 26/43] chore: fix version and help commands --- packages/serve/src/index.ts | 2 +- packages/webpack-cli/lib/bootstrap.js | 15 ++++----------- packages/webpack-cli/lib/utils/arg-parser.js | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index 9de85614335..44fc9a5c24d 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -17,7 +17,7 @@ export default function serve(): void { const filteredArgs = process.argv.filter((arg) => arg != 'serve'); const parsedDevServerArgs = cli.argParser(devServer, filteredArgs); const devServerArgs = parsedDevServerArgs.opts(); - const parsedWebpackArgs = cli.argParser(core, parsedDevServerArgs.args, true, process.title, cli.runHelp, cli.runVersion); + const parsedWebpackArgs = cli.argParser(core, parsedDevServerArgs.args, true, process.title); const webpackArgs = parsedWebpackArgs.opts(); const finalArgs = argsToCamelCase(devServerArgs || {}); diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index a5943ca597c..78ca0cc9270 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -44,17 +44,10 @@ const resolveNegatedArgs = (args) => { async function runCLI(cli, commandIsUsed) { let args; - const parsedArgs = argParser(core, process.argv, false, process.title, cli.runHelp, cli.runVersion); - - if (parsedArgs.args.includes('help')) { - cli.runHelp(process.argv); - process.exit(0); - } - - if (parsedArgs.args.includes('version')) { - cli.runVersion(); - process.exit(0); - } + const runVersion = () => { + cli.runVersion(commandIsUsed); + }; + const parsedArgs = argParser(core, process.argv, false, process.title, cli.runHelp, runVersion); if (commandIsUsed) { commandIsUsed.defaultOption = true; diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index 16fa5baf4e1..6860d5a53de 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -17,7 +17,7 @@ function argParser(options, args, argsOnly = false, name = '', helpFunction = un // Use customized version output if available if (versionFunction) { parser.on('option:version', () => { - versionFunction(args); + versionFunction(); process.exit(0); }); } From 783f4563a7ae9b88a8bb059c913e879b6ad2bb3f Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 18 Apr 2020 15:42:23 -0500 Subject: [PATCH 27/43] chore: change help, version, and fix entry checking --- packages/webpack-cli/lib/bootstrap.js | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index 78ca0cc9270..85079f4e898 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -49,13 +49,31 @@ async function runCLI(cli, commandIsUsed) { }; const parsedArgs = argParser(core, process.argv, false, process.title, cli.runHelp, runVersion); + if (parsedArgs.args.includes('help')) { + cli.runHelp(process.argv); + process.exit(0); + } + + if (parsedArgs.args.includes('version')) { + runVersion(); + process.exit(0); + } + if (commandIsUsed) { commandIsUsed.defaultOption = true; args = normalizeFlags(process.argv, commandIsUsed); return await cli.runCommand(commandIsUsed, ...args); } else { try { - if (parsedArgs.args.length > 1) { + // handle the default webpack entry CLI argument, where instead + // of doing 'webpack-cli --entry ./index.js' you can simply do + // 'webpack-cli ./index.js' + // if the unknown arg starts with a '-', it will be considered + // an unknown flag rather than an entry + let entry; + if (parsedArgs.args.length === 1 && !parsedArgs.args[0].startsWith('-')) { + entry = parsedArgs.args[0]; + } else if (parsedArgs.args.length > 0) { resolveNegatedArgs(parsedArgs.args); parsedArgs.args .filter((e) => e) @@ -66,11 +84,8 @@ async function runCLI(cli, commandIsUsed) { return; } const parsedArgsOpts = parsedArgs.opts(); - // handle the default webpack entry CLI argument, where instead - // of doing 'webpack-cli --entry ./index.js' you can simply do - // 'webpack-cli ./index.js' - if (parsedArgs.args.length === 1) { - parsedArgsOpts.entry = parsedArgs.args[0]; + if (entry) { + parsedArgsOpts.entry = entry; } const result = await cli.run(parsedArgsOpts, core); if (!result) { From 29aa5c026bfdbc21618992c3034e14173f14a778 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Thu, 23 Apr 2020 01:47:53 -0500 Subject: [PATCH 28/43] chore: add some negated flag handling --- packages/info/src/index.ts | 2 +- packages/serve/src/index.ts | 15 +++++--- packages/webpack-cli/lib/bootstrap.js | 39 ++++---------------- packages/webpack-cli/lib/utils/arg-parser.js | 38 ++++++++++++++++++- test/no-mode/no-mode.test.js | 8 +++- 5 files changed, 62 insertions(+), 40 deletions(-) diff --git a/packages/info/src/index.ts b/packages/info/src/index.ts index 383911bf538..d62b6a3353a 100644 --- a/packages/info/src/index.ts +++ b/packages/info/src/index.ts @@ -32,7 +32,7 @@ const DEFAULT_DETAILS: Information = { export default async function info(...args): Promise { const cli = new WebpackCLI(); - const infoArgs = cli.argParser(options, args, true).opts(); + const infoArgs = cli.argParser(options, args, true).opts; const envinfoConfig = {}; if (infoArgs._unknown && infoArgs._unknown.length > 0) { diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index 44fc9a5c24d..77ed62ab807 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -1,5 +1,6 @@ import { devServer } from 'webpack-dev-server/bin/cli-flags'; import WebpackCLI from 'webpack-cli'; +import logger from 'webpack-cli/lib/utils/logger'; import startDevServer from './startDevServer'; import argsToCamelCase from './args-to-camel-case'; @@ -16,9 +17,9 @@ export default function serve(): void { const filteredArgs = process.argv.filter((arg) => arg != 'serve'); const parsedDevServerArgs = cli.argParser(devServer, filteredArgs); - const devServerArgs = parsedDevServerArgs.opts(); - const parsedWebpackArgs = cli.argParser(core, parsedDevServerArgs.args, true, process.title); - const webpackArgs = parsedWebpackArgs.opts(); + const devServerArgs = parsedDevServerArgs.opts; + const parsedWebpackArgs = cli.argParser(core, parsedDevServerArgs.unknownArgs, true, process.title); + const webpackArgs = parsedWebpackArgs.opts; const finalArgs = argsToCamelCase(devServerArgs || {}); // pass along the 'hot' argument to the dev server if it exists @@ -32,8 +33,12 @@ export default function serve(): void { } }); - if (parsedWebpackArgs.args.length > 0) { - process.stderr.write(`Unknown option: ${parsedWebpackArgs.args}\n`); + if (parsedWebpackArgs.unknownArgs.length > 0) { + parsedWebpackArgs.unknownArgs + .filter((e) => e) + .forEach((unknown) => { + logger.warn('Unknown argument:', unknown); + }); return; } diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index 85079f4e898..860e1fbbeb8 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -20,28 +20,6 @@ const isCommandUsed = (commands) => return process.argv.includes(cmd.name) || process.argv.includes(cmd.alias); }); -const resolveNegatedArgs = (args) => { - args.forEach((arg, idx) => { - if (arg.includes('--') || arg.includes('--no')) { - const argPair = arg.split('='); - const optName = arg.includes('--no') ? argPair[0].slice(5) : argPair[0].slice(2); - - let argValue = arg.includes('--no') ? 'false' : argPair[1]; - if (argValue === 'false') { - argValue = false; - } else if (argValue === 'true') { - argValue = true; - } - const cliFlag = core.find((opt) => opt.name === optName); - if (cliFlag) { - args[cliFlag.group][optName] = argValue; - args._all[optName] = argValue; - args.args[idx] = null; - } - } - }); -}; - async function runCLI(cli, commandIsUsed) { let args; const runVersion = () => { @@ -49,12 +27,12 @@ async function runCLI(cli, commandIsUsed) { }; const parsedArgs = argParser(core, process.argv, false, process.title, cli.runHelp, runVersion); - if (parsedArgs.args.includes('help')) { + if (parsedArgs.unknownArgs.includes('help')) { cli.runHelp(process.argv); process.exit(0); } - if (parsedArgs.args.includes('version')) { + if (parsedArgs.unknownArgs.includes('version')) { runVersion(); process.exit(0); } @@ -71,11 +49,10 @@ async function runCLI(cli, commandIsUsed) { // if the unknown arg starts with a '-', it will be considered // an unknown flag rather than an entry let entry; - if (parsedArgs.args.length === 1 && !parsedArgs.args[0].startsWith('-')) { - entry = parsedArgs.args[0]; - } else if (parsedArgs.args.length > 0) { - resolveNegatedArgs(parsedArgs.args); - parsedArgs.args + if (parsedArgs.unknownArgs.length === 1 && !parsedArgs.unknownArgs[0].startsWith('-')) { + entry = parsedArgs.unknownArgs[0]; + } else if (parsedArgs.unknownArgs.length > 0) { + parsedArgs.unknownArgs .filter((e) => e) .forEach((unknown) => { logger.warn('Unknown argument:', unknown); @@ -83,7 +60,7 @@ async function runCLI(cli, commandIsUsed) { cliExecuter(); return; } - const parsedArgsOpts = parsedArgs.opts(); + const parsedArgsOpts = parsedArgs.opts; if (entry) { parsedArgsOpts.entry = entry; } @@ -115,7 +92,7 @@ async function runCLI(cli, commandIsUsed) { // eslint-disable-next-line require-atomic-updates process.argv = newArgKeys; args = argParser('', core, process.argv); - await cli.run(args.opts(), core); + await cli.run(args.opts, core); process.stdout.write('\n'); logger.warn('Duplicate flags found, defaulting to last set value'); } else { diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index 6860d5a53de..43bacaa5de1 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -1,4 +1,5 @@ const commander = require('commander'); +const logger = require('./logger'); /** * Creates Argument parser corresponding to the supplied options @@ -38,6 +39,10 @@ function argParser(options, args, argsOnly = false, name = '', helpFunction = un const flags = option.alias ? `-${option.alias}, --${option.name}` : `--${option.name}`; const flagsWithType = option.type !== Boolean ? flags + ' ' : flags; parserInstance.option(flagsWithType, option.description, option.defaultValue); + if (option.type === Boolean) { + const negatedFlag = `--no-${option.name}`; + parserInstance.option(negatedFlag, `negates ${option.name}`); + } return parserInstance; }, parser); @@ -48,7 +53,38 @@ function argParser(options, args, argsOnly = false, name = '', helpFunction = un // (e.g. ['node', '/path/to/...', '--option', 'value']) const parseOptions = argsOnly ? { from: 'user' } : {}; - return parser.parse(args, parseOptions); + const result = parser.parse(args, parseOptions); + const opts = result.opts(); + + const unknownArgs = result.args; + + args.forEach((arg) => { + const flagName = arg.slice(5); + const option = options.find((opt) => opt.name === flagName); + const flag = `--${flagName}`; + const flagUsed = args.includes(flag) && !unknownArgs.includes(flag); + let alias = ''; + let aliasUsed = false; + if (option && option.alias) { + alias = `-${option.alias}`; + aliasUsed = args.includes(alias) && !unknownArgs.includes(alias); + } + + // this is a negated flag that is not an unknown flag, but the flag + // it is negating was also provided + if (arg.startsWith('--no-') && (flagUsed || aliasUsed) && !unknownArgs.includes(arg)) { + logger.warn( + `You provided both ${ + flagUsed ? flag : alias + } and ${arg}. We will use only the last of these flags that you provided in your CLI arguments`, + ); + } + }); + + return { + unknownArgs, + opts, + }; } module.exports = argParser; diff --git a/test/no-mode/no-mode.test.js b/test/no-mode/no-mode.test.js index f2f743d2661..0ec137128bb 100644 --- a/test/no-mode/no-mode.test.js +++ b/test/no-mode/no-mode.test.js @@ -40,7 +40,9 @@ describe('no-mode flag', () => { it('should load a none config when --mode=production is passed before --no-mode', (done) => { const { stderr, stdout } = run(__dirname, ['--mode', 'production', '--no-mode']); - expect(stderr).toBeFalsy(); + expect(stderr).toContain( + 'You provided both --mode and --no-mode. We will use only the last of these flags that you provided in your CLI arguments', + ); expect(stdout).toBeTruthy(); expect(stdout).not.toContain('main.js.map'); @@ -53,7 +55,9 @@ describe('no-mode flag', () => { it('should load a none config when --mode=production is passed after --no-mode', (done) => { const { stderr, stdout } = run(__dirname, ['--no-mode', '--mode', 'production']); - expect(stderr).toBeFalsy(); + expect(stderr).toContain( + 'You provided both --mode and --no-mode. We will use only the last of these flags that you provided in your CLI arguments', + ); expect(stdout).toBeTruthy(); expect(stdout).toContain('main.js.map'); From f1888cf72d311b717dd1c8e98f85532a15375d8a Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Thu, 23 Apr 2020 19:50:56 -0500 Subject: [PATCH 29/43] chore: fix serve test --- test/serve/basic/serve-basic.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/serve/basic/serve-basic.test.js b/test/serve/basic/serve-basic.test.js index 8fc66398e7c..bb32dfd1846 100644 --- a/test/serve/basic/serve-basic.test.js +++ b/test/serve/basic/serve-basic.test.js @@ -48,7 +48,7 @@ describe('basic serve usage', () => { it('throws error on unknown flag', async () => { const { stdout, stderr } = await runServe(['--port', port, '--unknown-flag'], testPath); expect(stdout).toHaveLength(0); - expect(stderr).toContain('Unknown option: --unknown-flag'); + expect(stderr).toContain('Unknown argument: --unknown-flag'); }); } }); From 793e51e36247d90f2c5c096b99d8b20f0e383bd1 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Thu, 23 Apr 2020 20:23:31 -0500 Subject: [PATCH 30/43] chore: rename misleading parse-args to parse-node-args --- packages/webpack-cli/bin/cli.js | 4 ++-- .../lib/utils/{parse-args.js => parse-node-args.js} | 0 test/node/node.test.js | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename packages/webpack-cli/lib/utils/{parse-args.js => parse-node-args.js} (100%) diff --git a/packages/webpack-cli/bin/cli.js b/packages/webpack-cli/bin/cli.js index 1bf0a1be092..34faad08445 100755 --- a/packages/webpack-cli/bin/cli.js +++ b/packages/webpack-cli/bin/cli.js @@ -3,7 +3,7 @@ 'use strict'; require('v8-compile-cache'); const importLocal = require('import-local'); -const parseArgs = require('../lib/utils/parse-args'); +const parseNodeArgs = require('../lib/utils/parse-node-args'); const runner = require('../lib/runner'); // Prefer the local installation of webpack-cli @@ -13,6 +13,6 @@ if (importLocal(__filename)) { process.title = 'webpack'; const [, , ...rawArgs] = process.argv; -const { cliArgs, nodeArgs } = parseArgs(rawArgs); +const { cliArgs, nodeArgs } = parseNodeArgs(rawArgs); runner(nodeArgs, cliArgs); diff --git a/packages/webpack-cli/lib/utils/parse-args.js b/packages/webpack-cli/lib/utils/parse-node-args.js similarity index 100% rename from packages/webpack-cli/lib/utils/parse-args.js rename to packages/webpack-cli/lib/utils/parse-node-args.js diff --git a/test/node/node.test.js b/test/node/node.test.js index 1db66c623c3..aa26dd7527e 100644 --- a/test/node/node.test.js +++ b/test/node/node.test.js @@ -2,10 +2,10 @@ const { stat } = require('fs'); const { resolve } = require('path'); const { run } = require('../utils/test-utils'); -const parseArgs = require('../../packages/webpack-cli/lib/utils/parse-args'); +const parseNodeArgs = require('../../packages/webpack-cli/lib/utils/parse-node-args'); describe('node flags', () => { - it('parseArgs helper must work correctly', () => { + it('parseNodeArgs helper must work correctly', () => { [ { rawArgs: ['--foo', '--bar', '--baz=quux'], @@ -32,7 +32,7 @@ describe('node flags', () => { expectedNodeArgs: ['--name1=value1', '--name2="value2"', '--name3', 'value3', '-k', 'v'], }, ].map(({ rawArgs, expectedNodeArgs, expectedCliArgs }) => { - const { nodeArgs, cliArgs } = parseArgs(rawArgs); + const { nodeArgs, cliArgs } = parseNodeArgs(rawArgs); expect(nodeArgs).toEqual(expectedNodeArgs); expect(cliArgs).toEqual(expectedCliArgs); }); From 03b76183ad50a3ffbab8b119caedee6e3d8c763e Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Thu, 23 Apr 2020 21:28:11 -0500 Subject: [PATCH 31/43] chore: added arg-parser tests --- .../webpack-cli/__tests__/arg-parser.test.js | 183 ++++++++++++++++++ packages/webpack-cli/lib/utils/arg-parser.js | 6 + 2 files changed, 189 insertions(+) create mode 100644 packages/webpack-cli/__tests__/arg-parser.test.js diff --git a/packages/webpack-cli/__tests__/arg-parser.test.js b/packages/webpack-cli/__tests__/arg-parser.test.js new file mode 100644 index 00000000000..112dce8fd4e --- /dev/null +++ b/packages/webpack-cli/__tests__/arg-parser.test.js @@ -0,0 +1,183 @@ +const warnMock = jest.fn(); +jest.mock('../lib/utils/logger', () => { + return { + warn: warnMock, + }; +}); +jest.spyOn(process, 'exit').mockImplementation(() => {}); + +const argParser = require('../lib/utils/arg-parser'); +const { core } = require('../lib/utils/cli-flags'); + +const basicOptions = [ + { + name: 'bool-flag', + alias: 'b', + usage: '--bool-flag', + type: Boolean, + description: 'boolean flag', + }, + { + name: 'string-flag', + usage: '--string-flag ', + type: String, + description: 'string flag', + }, + { + name: 'string-flag-with-default', + usage: '--string-flag-with-default ', + type: String, + description: 'string flag', + defaultValue: 'default-value', + }, +]; + +const helpAndVersionOptions = basicOptions.slice(0); +helpAndVersionOptions.push( + { + name: 'help', + usage: '--help', + type: Boolean, + description: 'help', + }, + { + name: 'version', + alias: 'v', + usage: '--version', + type: Boolean, + description: 'version', + }, +); + +describe('arg-parser', () => { + beforeEach(() => { + warnMock.mockClear(); + }); + + it('parses no flags', () => { + const res = argParser(basicOptions, [], true); + expect(res.unknownArgs.length).toEqual(0); + expect(res.opts).toEqual({ + stringFlagWithDefault: 'default-value', + }); + expect(warnMock.mock.calls.length).toEqual(0); + }); + + it('parses basic flags', () => { + const res = argParser(basicOptions, ['--bool-flag', '--string-flag', 'val'], true); + expect(res.unknownArgs.length).toEqual(0); + expect(res.opts).toEqual({ + boolFlag: true, + stringFlag: 'val', + stringFlagWithDefault: 'default-value', + }); + expect(warnMock.mock.calls.length).toEqual(0); + }); + + it('parses negated boolean flags', () => { + const res = argParser(basicOptions, ['--no-bool-flag'], true); + expect(res.unknownArgs.length).toEqual(0); + expect(res.opts).toEqual({ + boolFlag: false, + stringFlagWithDefault: 'default-value', + }); + expect(warnMock.mock.calls.length).toEqual(0); + }); + + it('parses boolean flag alias', () => { + const res = argParser(basicOptions, ['-b'], true); + expect(res.unknownArgs.length).toEqual(0); + expect(res.opts).toEqual({ + boolFlag: true, + stringFlagWithDefault: 'default-value', + }); + expect(warnMock.mock.calls.length).toEqual(0); + }); + + it('warns on usage of both flag and same negated flag, setting it to false', () => { + const res = argParser(basicOptions, ['--bool-flag', '--no-bool-flag'], true); + expect(res.unknownArgs.length).toEqual(0); + expect(res.opts).toEqual({ + boolFlag: false, + stringFlagWithDefault: 'default-value', + }); + expect(warnMock.mock.calls.length).toEqual(1); + expect(warnMock.mock.calls[0][0]).toContain('You provided both --bool-flag and --no-bool-flag'); + }); + + it('warns on usage of both flag and same negated flag, setting it to true', () => { + const res = argParser(basicOptions, ['--no-bool-flag', '--bool-flag'], true); + expect(res.unknownArgs.length).toEqual(0); + expect(res.opts).toEqual({ + boolFlag: true, + stringFlagWithDefault: 'default-value', + }); + expect(warnMock.mock.calls.length).toEqual(1); + expect(warnMock.mock.calls[0][0]).toContain('You provided both --bool-flag and --no-bool-flag'); + }); + + it('warns on usage of both flag alias and same negated flag, setting it to true', () => { + const res = argParser(basicOptions, ['--no-bool-flag', '-b'], true); + expect(res.unknownArgs.length).toEqual(0); + expect(res.opts).toEqual({ + boolFlag: true, + stringFlagWithDefault: 'default-value', + }); + expect(warnMock.mock.calls.length).toEqual(1); + expect(warnMock.mock.calls[0][0]).toContain('You provided both -b and --no-bool-flag'); + }); + + it('parses string flag using equals sign', () => { + const res = argParser(basicOptions, ['--string-flag=val'], true); + expect(res.unknownArgs.length).toEqual(0); + expect(res.opts).toEqual({ + stringFlag: 'val', + stringFlagWithDefault: 'default-value', + }); + expect(warnMock.mock.calls.length).toEqual(0); + }); + + it('handles additional node args from argv', () => { + const res = argParser(basicOptions, ['node', 'index.js', '--bool-flag', '--string-flag', 'val'], false); + expect(res.unknownArgs.length).toEqual(0); + expect(res.opts).toEqual({ + boolFlag: true, + stringFlag: 'val', + stringFlagWithDefault: 'default-value', + }); + expect(warnMock.mock.calls.length).toEqual(0); + }); + + it('handles unknown args', () => { + const res = argParser(basicOptions, ['--unknown-arg', '-b', 'no-leading-dashes'], true); + expect(res.unknownArgs).toEqual(['--unknown-arg', 'no-leading-dashes']); + expect(res.opts).toEqual({ + boolFlag: true, + stringFlagWithDefault: 'default-value', + }); + expect(warnMock.mock.calls.length).toEqual(0); + }); + + it('calls help callback on --help', () => { + const helpCb = jest.fn(); + argParser(helpAndVersionOptions, ['--help'], true, '', helpCb); + expect(helpCb.mock.calls.length).toEqual(1); + expect(helpCb.mock.calls[0][0]).toEqual(['--help']); + }); + + it('calls version callback on --version', () => { + const versionCb = jest.fn(); + argParser(helpAndVersionOptions, ['--version'], true, '', () => {}, versionCb); + expect(versionCb.mock.calls.length).toEqual(1); + }); + + it('parses webpack args', () => { + const res = argParser(core, ['--entry', 'test.js', '--hot', '-o', './dist/', '--no-watch'], true); + expect(res.unknownArgs.length).toEqual(0); + expect(res.opts.entry).toEqual('test.js'); + expect(res.opts.hot).toBeTruthy(); + expect(res.opts.output).toEqual('./dist/'); + expect(res.opts.watch).toBeFalsy(); + expect(warnMock.mock.calls.length).toEqual(0); + }); +}); diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index 43bacaa5de1..d0dd153c34f 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -81,6 +81,12 @@ function argParser(options, args, argsOnly = false, name = '', helpFunction = un } }); + Object.keys(opts).forEach((key) => { + if (opts[key] === undefined) { + delete opts[key]; + } + }); + return { unknownArgs, opts, From 575fbe80a30bc00ffec5883eb6b05e9a80426128 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Thu, 23 Apr 2020 22:36:08 -0500 Subject: [PATCH 32/43] chore: use args passed to serve --- packages/serve/src/index.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index 77ed62ab807..10d0ecfa799 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -2,7 +2,6 @@ import { devServer } from 'webpack-dev-server/bin/cli-flags'; import WebpackCLI from 'webpack-cli'; import logger from 'webpack-cli/lib/utils/logger'; import startDevServer from './startDevServer'; -import argsToCamelCase from './args-to-camel-case'; /** * @@ -11,28 +10,20 @@ import argsToCamelCase from './args-to-camel-case'; * @param {String[]} args - args processed from the CLI * @returns {Function} invokes the devServer API */ -export default function serve(): void { +export default function serve(...args: string[]): void { const cli = new WebpackCLI(); const core = cli.getCoreFlags(); - const filteredArgs = process.argv.filter((arg) => arg != 'serve'); - const parsedDevServerArgs = cli.argParser(devServer, filteredArgs); + const parsedDevServerArgs = cli.argParser(devServer, args, true); const devServerArgs = parsedDevServerArgs.opts; const parsedWebpackArgs = cli.argParser(core, parsedDevServerArgs.unknownArgs, true, process.title); const webpackArgs = parsedWebpackArgs.opts; - const finalArgs = argsToCamelCase(devServerArgs || {}); // pass along the 'hot' argument to the dev server if it exists if (webpackArgs && webpackArgs.hot !== undefined) { - finalArgs['hot'] = webpackArgs.hot; + devServerArgs['hot'] = webpackArgs.hot; } - Object.keys(finalArgs).forEach((arg) => { - if (finalArgs[arg] === undefined) { - delete finalArgs[arg]; - } - }); - if (parsedWebpackArgs.unknownArgs.length > 0) { parsedWebpackArgs.unknownArgs .filter((e) => e) @@ -43,6 +34,6 @@ export default function serve(): void { } cli.getCompiler(webpackArgs, core).then((compiler): void => { - startDevServer(compiler, finalArgs); + startDevServer(compiler, devServerArgs); }); } From fa0f1457c0b1e34c9a1a279f537eca14d8c3bcea Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Thu, 23 Apr 2020 23:07:21 -0500 Subject: [PATCH 33/43] chore: fix stats flag handling --- packages/webpack-cli/lib/utils/arg-parser.js | 13 +++++++++---- packages/webpack-cli/lib/utils/cli-flags.js | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index d0dd153c34f..3a4fdf2ab79 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -38,10 +38,15 @@ function argParser(options, args, argsOnly = false, name = '', helpFunction = un options.reduce((parserInstance, option) => { const flags = option.alias ? `-${option.alias}, --${option.name}` : `--${option.name}`; const flagsWithType = option.type !== Boolean ? flags + ' ' : flags; - parserInstance.option(flagsWithType, option.description, option.defaultValue); - if (option.type === Boolean) { - const negatedFlag = `--no-${option.name}`; - parserInstance.option(negatedFlag, `negates ${option.name}`); + if (option.type !== Boolean && option.type !== String) { + // in this case the type is a parsing function + parserInstance.option(flagsWithType, option.description, option.defaultValue, option.type); + } else { + parserInstance.option(flagsWithType, option.description, option.defaultValue); + if (option.type === Boolean) { + const negatedFlag = `--no-${option.name}`; + parserInstance.option(negatedFlag, `negates ${option.name}`); + } } return parserInstance; }, parser); diff --git a/packages/webpack-cli/lib/utils/cli-flags.js b/packages/webpack-cli/lib/utils/cli-flags.js index def246fe544..65142bb363a 100644 --- a/packages/webpack-cli/lib/utils/cli-flags.js +++ b/packages/webpack-cli/lib/utils/cli-flags.js @@ -262,6 +262,7 @@ module.exports = { logger.warn('No value recognised for "stats" option'); return 'normal'; }, + defaultValue: 'normal', group: DISPLAY_GROUP, description: 'It instructs webpack on how to treat the stats', link: 'https://webpack.js.org/configuration/stats/#stats', From 91ee8f5984185e232cf71829ed42e9deedd4277a Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Thu, 23 Apr 2020 23:35:12 -0500 Subject: [PATCH 34/43] chore: fix function as a custom type --- .../webpack-cli/__tests__/arg-parser.test.js | 18 ++++++++++++++++++ packages/webpack-cli/lib/utils/arg-parser.js | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/webpack-cli/__tests__/arg-parser.test.js b/packages/webpack-cli/__tests__/arg-parser.test.js index 112dce8fd4e..ab7600321d0 100644 --- a/packages/webpack-cli/__tests__/arg-parser.test.js +++ b/packages/webpack-cli/__tests__/arg-parser.test.js @@ -30,6 +30,14 @@ const basicOptions = [ description: 'string flag', defaultValue: 'default-value', }, + { + name: 'custom-type-flag', + usage: '--custom-type-flag ', + type: (val) => { + return val.split(','); + }, + description: 'custom type flag', + }, ]; const helpAndVersionOptions = basicOptions.slice(0); @@ -158,6 +166,16 @@ describe('arg-parser', () => { expect(warnMock.mock.calls.length).toEqual(0); }); + it('handles custom type args', () => { + const res = argParser(basicOptions, ['--custom-type-flag', 'val1,val2,val3'], true); + expect(res.unknownArgs.length).toEqual(0); + expect(res.opts).toEqual({ + customTypeFlag: ['val1', 'val2', 'val3'], + stringFlagWithDefault: 'default-value', + }); + expect(warnMock.mock.calls.length).toEqual(0); + }); + it('calls help callback on --help', () => { const helpCb = jest.fn(); argParser(helpAndVersionOptions, ['--help'], true, '', helpCb); diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index 3a4fdf2ab79..973b629f504 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -40,7 +40,7 @@ function argParser(options, args, argsOnly = false, name = '', helpFunction = un const flagsWithType = option.type !== Boolean ? flags + ' ' : flags; if (option.type !== Boolean && option.type !== String) { // in this case the type is a parsing function - parserInstance.option(flagsWithType, option.description, option.defaultValue, option.type); + parserInstance.option(flagsWithType, option.description, option.type, option.defaultValue); } else { parserInstance.option(flagsWithType, option.description, option.defaultValue); if (option.type === Boolean) { From 9240879df178ea970f20ee3d6d0d49cdbb6fadac Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Thu, 23 Apr 2020 23:37:11 -0500 Subject: [PATCH 35/43] chore: change boolean logic in arg parser --- packages/webpack-cli/lib/utils/arg-parser.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index 973b629f504..da6219c0a74 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -38,16 +38,17 @@ function argParser(options, args, argsOnly = false, name = '', helpFunction = un options.reduce((parserInstance, option) => { const flags = option.alias ? `-${option.alias}, --${option.name}` : `--${option.name}`; const flagsWithType = option.type !== Boolean ? flags + ' ' : flags; - if (option.type !== Boolean && option.type !== String) { - // in this case the type is a parsing function - parserInstance.option(flagsWithType, option.description, option.type, option.defaultValue); - } else { + if (option.type === Boolean || option.type === String) { parserInstance.option(flagsWithType, option.description, option.defaultValue); if (option.type === Boolean) { const negatedFlag = `--no-${option.name}`; parserInstance.option(negatedFlag, `negates ${option.name}`); } + } else { + // in this case the type is a parsing function + parserInstance.option(flagsWithType, option.description, option.type, option.defaultValue); } + return parserInstance; }, parser); From f4c94dae43f2bcf290f2424b3377f27fec7cc873 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Thu, 23 Apr 2020 23:51:16 -0500 Subject: [PATCH 36/43] chore: fixed stats test and removed stats custom type --- packages/webpack-cli/lib/utils/cli-flags.js | 11 +---------- test/stats/stats.test.js | 1 + 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/webpack-cli/lib/utils/cli-flags.js b/packages/webpack-cli/lib/utils/cli-flags.js index 65142bb363a..33d752deb84 100644 --- a/packages/webpack-cli/lib/utils/cli-flags.js +++ b/packages/webpack-cli/lib/utils/cli-flags.js @@ -1,6 +1,3 @@ -const logger = require('../utils/logger'); -const StatsGroup = require('../groups/StatsGroup'); - const HELP_GROUP = 'help'; const CONFIG_GROUP = 'config'; const BASIC_GROUP = 'basic'; @@ -255,13 +252,7 @@ module.exports = { { name: 'stats', usage: '--stats verbose', - type: (value) => { - if (StatsGroup.validOptions().includes(value)) { - return value; - } - logger.warn('No value recognised for "stats" option'); - return 'normal'; - }, + type: String, defaultValue: 'normal', group: DISPLAY_GROUP, description: 'It instructs webpack on how to treat the stats', diff --git a/test/stats/stats.test.js b/test/stats/stats.test.js index 656a6633825..8fb0145fe82 100644 --- a/test/stats/stats.test.js +++ b/test/stats/stats.test.js @@ -48,6 +48,7 @@ describe('stats flag', () => { it('should warn when an unknown flag stats value is passed', () => { const { stderr, stdout } = run(__dirname, ['--stats', 'foo']); expect(stderr).toBeTruthy(); + console.log(stderr); expect(stderr).toContain('invalid value for stats'); expect(stdout).toBeTruthy(); }); From 11282eed90f328c20330c00f8c7964d18056acd2 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Fri, 24 Apr 2020 12:02:09 -0500 Subject: [PATCH 37/43] chore: removed output todo and added tests for warning --- .../webpack-cli/lib/groups/OutputGroup.js | 10 ++-- .../output-named-bundles.test.js | 60 +++++++++++++++++-- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/packages/webpack-cli/lib/groups/OutputGroup.js b/packages/webpack-cli/lib/groups/OutputGroup.js index 97e3ba0d2af..31de0e6dd66 100644 --- a/packages/webpack-cli/lib/groups/OutputGroup.js +++ b/packages/webpack-cli/lib/groups/OutputGroup.js @@ -1,5 +1,6 @@ const path = require('path'); const GroupHelper = require('../utils/GroupHelper'); +const logger = require('../utils/logger'); class OutputGroup extends GroupHelper { constructor(options) { @@ -27,10 +28,11 @@ class OutputGroup extends GroupHelper { const { args } = this; if (args) { const { output } = args; - // TODO: Remove comment before merge - // We need to show warning when empty output flag is supplied - // which is set to boolean true by commander - if (!output || output === true) { + + if (!output) { + logger.warn( + "You provided an empty output value. Falling back to the output value of your webpack config file, or './dist/' if none was provided", + ); return; } const outputInfo = path.parse(output); diff --git a/test/output/named-bundles/output-named-bundles.test.js b/test/output/named-bundles/output-named-bundles.test.js index 82966106b53..738c33e8032 100644 --- a/test/output/named-bundles/output-named-bundles.test.js +++ b/test/output/named-bundles/output-named-bundles.test.js @@ -1,11 +1,24 @@ 'use strict'; const { stat } = require('fs'); -const { resolve } = require('path'); +const { join, resolve } = require('path'); +const rimraf = require('rimraf'); const { run } = require('../../utils/test-utils'); describe('output flag named bundles', () => { + const clean = () => { + rimraf.sync(join(__dirname, 'bin')); + rimraf.sync(join(__dirname, 'dist')); + rimraf.sync(join(__dirname, 'binary')); + }; + + beforeEach(clean); + + afterAll(clean); + it('should output file given as flag instead of in configuration', (done) => { - run(__dirname, ['-c', resolve(__dirname, 'webpack.config.js'), '--output', './binary/a.bundle.js'], false); + const { stderr } = run(__dirname, ['-c', resolve(__dirname, 'webpack.config.js'), '--output', './binary/a.bundle.js'], false); + expect(stderr).toBeFalsy(); + stat(resolve(__dirname, './binary/a.bundle.js'), (err, stats) => { expect(err).toBe(null); expect(stats.isFile()).toBe(true); @@ -14,7 +27,12 @@ describe('output flag named bundles', () => { }); it('should create multiple bundles with an overriding flag', (done) => { - run(__dirname, ['-c', resolve(__dirname, 'webpack.single.config.js'), '--output', './bin/[name].bundle.js'], false); + const { stderr } = run( + __dirname, + ['-c', resolve(__dirname, 'webpack.single.config.js'), '--output', './bin/[name].bundle.js'], + false, + ); + expect(stderr).toBeFalsy(); stat(resolve(__dirname, './bin/b.bundle.js'), (err, stats) => { expect(err).toBe(null); @@ -29,8 +47,7 @@ describe('output flag named bundles', () => { it('should not throw error on same bundle name for multiple entries with defaults', (done) => { const { stderr } = run(__dirname, ['-c', resolve(__dirname, 'webpack.defaults.config.js'), '--defaults'], false); - - expect(stderr).toBe(''); + expect(stderr).toBeFalsy(); stat(resolve(__dirname, './dist/b.main.js'), (err, stats) => { expect(err).toBe(null); @@ -44,7 +61,8 @@ describe('output flag named bundles', () => { }); it('should successfully compile multiple entries', (done) => { - run(__dirname, ['-c', resolve(__dirname, 'webpack.multiple.config.js')], false); + const { stderr } = run(__dirname, ['-c', resolve(__dirname, 'webpack.multiple.config.js')], false); + expect(stderr).toBeFalsy(); stat(resolve(__dirname, './bin/b.bundle.js'), (err, stats) => { expect(err).toBe(null); @@ -56,4 +74,34 @@ describe('output flag named bundles', () => { }); done(); }); + + it('should output file in bin directory using default webpack config with warning for empty output value', (done) => { + const { stderr } = run(__dirname, ['--output='], false); + expect(stderr).toContain( + "You provided an empty output value. Falling back to the output value of your webpack config file, or './dist/' if none was provided", + ); + + stat(resolve(__dirname, './bin/bundle.js'), (err, stats) => { + expect(err).toBe(null); + expect(stats.isFile()).toBe(true); + done(); + }); + }); + + it('should output file in dist directory using default value with warning for empty output value', (done) => { + const { stderr } = run(__dirname, ['-c', resolve(__dirname, 'webpack.defaults.config.js'), '--defaults', '--output='], false); + expect(stderr).toContain( + "You provided an empty output value. Falling back to the output value of your webpack config file, or './dist/' if none was provided", + ); + + stat(resolve(__dirname, './dist/b.main.js'), (err, stats) => { + expect(err).toBe(null); + expect(stats.isFile()).toBe(true); + }); + stat(resolve(__dirname, './dist/c.main.js'), (err, stats) => { + expect(err).toBe(null); + expect(stats.isFile()).toBe(true); + }); + done(); + }); }); From 8339c7be81d7e69475c13594a483588fc936b947 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Fri, 24 Apr 2020 12:08:24 -0500 Subject: [PATCH 38/43] chore: changed invalid mode message and updated test --- packages/webpack-cli/lib/groups/ZeroConfigGroup.js | 2 +- test/mode/prod/prod.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webpack-cli/lib/groups/ZeroConfigGroup.js b/packages/webpack-cli/lib/groups/ZeroConfigGroup.js index 7259a4e835b..14f1e491d1f 100644 --- a/packages/webpack-cli/lib/groups/ZeroConfigGroup.js +++ b/packages/webpack-cli/lib/groups/ZeroConfigGroup.js @@ -37,7 +37,7 @@ class ZeroConfigGroup extends GroupHelper { if (this.args.mode) { if (this.args.mode !== PRODUCTION && this.args.mode !== DEVELOPMENT && this.args.mode !== NONE) { - logger.warn('You provided an invalid value for "mode" option.'); + logger.warn('You provided an invalid value for "mode" option. Using "production" by default'); return PRODUCTION; } return this.args.mode; diff --git a/test/mode/prod/prod.test.js b/test/mode/prod/prod.test.js index fe5a6cff333..78353586582 100644 --- a/test/mode/prod/prod.test.js +++ b/test/mode/prod/prod.test.js @@ -27,7 +27,7 @@ describe('mode flags', () => { it('should load a production config when --mode=abcd is passed', (done) => { const { stderr, stdout } = run(__dirname, ['--mode', 'abcd']); - expect(stderr).toContain('invalid value for "mode"'); + expect(stderr).toContain('invalid value for "mode" option. Using "production" by default'); expect(stdout).toBeTruthy(); stat(resolve(__dirname, './bin/main.js'), (err, stats) => { expect(err).toBe(null); From 426c6666980ac867b3f5e6efb28d163ea6565d2f Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Fri, 24 Apr 2020 12:15:45 -0500 Subject: [PATCH 39/43] chore: add comment about commander negation --- packages/webpack-cli/lib/utils/arg-parser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index da6219c0a74..85d6947c116 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -41,6 +41,7 @@ function argParser(options, args, argsOnly = false, name = '', helpFunction = un if (option.type === Boolean || option.type === String) { parserInstance.option(flagsWithType, option.description, option.defaultValue); if (option.type === Boolean) { + // commander requires explicitly adding the negated version of boolean flags const negatedFlag = `--no-${option.name}`; parserInstance.option(negatedFlag, `negates ${option.name}`); } From 2deb7e3f5532871448e1c1637bafc4a9d044ac5e Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Fri, 24 Apr 2020 12:27:37 -0500 Subject: [PATCH 40/43] chore: made output tests sync --- .../output-named-bundles.test.js | 80 +++++++------------ 1 file changed, 27 insertions(+), 53 deletions(-) diff --git a/test/output/named-bundles/output-named-bundles.test.js b/test/output/named-bundles/output-named-bundles.test.js index 738c33e8032..7a53fb80202 100644 --- a/test/output/named-bundles/output-named-bundles.test.js +++ b/test/output/named-bundles/output-named-bundles.test.js @@ -1,5 +1,5 @@ 'use strict'; -const { stat } = require('fs'); +const { statSync } = require('fs'); const { join, resolve } = require('path'); const rimraf = require('rimraf'); const { run } = require('../../utils/test-utils'); @@ -15,18 +15,15 @@ describe('output flag named bundles', () => { afterAll(clean); - it('should output file given as flag instead of in configuration', (done) => { + it('should output file given as flag instead of in configuration', () => { const { stderr } = run(__dirname, ['-c', resolve(__dirname, 'webpack.config.js'), '--output', './binary/a.bundle.js'], false); expect(stderr).toBeFalsy(); - stat(resolve(__dirname, './binary/a.bundle.js'), (err, stats) => { - expect(err).toBe(null); - expect(stats.isFile()).toBe(true); - done(); - }); + const stats = statSync(resolve(__dirname, './binary/a.bundle.js')); + expect(stats.isFile()).toBe(true); }); - it('should create multiple bundles with an overriding flag', (done) => { + it('should create multiple bundles with an overriding flag', () => { const { stderr } = run( __dirname, ['-c', resolve(__dirname, 'webpack.single.config.js'), '--output', './bin/[name].bundle.js'], @@ -34,74 +31,51 @@ describe('output flag named bundles', () => { ); expect(stderr).toBeFalsy(); - stat(resolve(__dirname, './bin/b.bundle.js'), (err, stats) => { - expect(err).toBe(null); - expect(stats.isFile()).toBe(true); - }); - stat(resolve(__dirname, './bin/c.bundle.js'), (err, stats) => { - expect(err).toBe(null); - expect(stats.isFile()).toBe(true); - }); - done(); + let stats = statSync(resolve(__dirname, './bin/b.bundle.js')); + expect(stats.isFile()).toBe(true); + stats = statSync(resolve(__dirname, './bin/c.bundle.js')); + expect(stats.isFile()).toBe(true); }); - it('should not throw error on same bundle name for multiple entries with defaults', (done) => { + it('should not throw error on same bundle name for multiple entries with defaults', () => { const { stderr } = run(__dirname, ['-c', resolve(__dirname, 'webpack.defaults.config.js'), '--defaults'], false); expect(stderr).toBeFalsy(); - stat(resolve(__dirname, './dist/b.main.js'), (err, stats) => { - expect(err).toBe(null); - expect(stats.isFile()).toBe(true); - }); - stat(resolve(__dirname, './dist/c.main.js'), (err, stats) => { - expect(err).toBe(null); - expect(stats.isFile()).toBe(true); - }); - done(); + let stats = statSync(resolve(__dirname, './dist/b.main.js')); + expect(stats.isFile()).toBe(true); + stats = statSync(resolve(__dirname, './dist/c.main.js')); + expect(stats.isFile()).toBe(true); }); - it('should successfully compile multiple entries', (done) => { + it('should successfully compile multiple entries', () => { const { stderr } = run(__dirname, ['-c', resolve(__dirname, 'webpack.multiple.config.js')], false); expect(stderr).toBeFalsy(); - stat(resolve(__dirname, './bin/b.bundle.js'), (err, stats) => { - expect(err).toBe(null); - expect(stats.isFile()).toBe(true); - }); - stat(resolve(__dirname, './bin/c.bundle.js'), (err, stats) => { - expect(err).toBe(null); - expect(stats.isFile()).toBe(true); - }); - done(); + let stats = statSync(resolve(__dirname, './bin/b.bundle.js')); + expect(stats.isFile()).toBe(true); + stats = statSync(resolve(__dirname, './bin/c.bundle.js')); + expect(stats.isFile()).toBe(true); }); - it('should output file in bin directory using default webpack config with warning for empty output value', (done) => { + it('should output file in bin directory using default webpack config with warning for empty output value', () => { const { stderr } = run(__dirname, ['--output='], false); expect(stderr).toContain( "You provided an empty output value. Falling back to the output value of your webpack config file, or './dist/' if none was provided", ); - stat(resolve(__dirname, './bin/bundle.js'), (err, stats) => { - expect(err).toBe(null); - expect(stats.isFile()).toBe(true); - done(); - }); + const stats = statSync(resolve(__dirname, './bin/bundle.js')); + expect(stats.isFile()).toBe(true); }); - it('should output file in dist directory using default value with warning for empty output value', (done) => { + it('should output file in dist directory using default value with warning for empty output value', () => { const { stderr } = run(__dirname, ['-c', resolve(__dirname, 'webpack.defaults.config.js'), '--defaults', '--output='], false); expect(stderr).toContain( "You provided an empty output value. Falling back to the output value of your webpack config file, or './dist/' if none was provided", ); - stat(resolve(__dirname, './dist/b.main.js'), (err, stats) => { - expect(err).toBe(null); - expect(stats.isFile()).toBe(true); - }); - stat(resolve(__dirname, './dist/c.main.js'), (err, stats) => { - expect(err).toBe(null); - expect(stats.isFile()).toBe(true); - }); - done(); + let stats = statSync(resolve(__dirname, './dist/b.main.js')); + expect(stats.isFile()).toBe(true); + stats = statSync(resolve(__dirname, './dist/c.main.js')); + expect(stats.isFile()).toBe(true); }); }); From dfb6f6c89150434829f2109a409707652076c149 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Fri, 24 Apr 2020 13:47:18 -0500 Subject: [PATCH 41/43] chore: fix spelling in comment --- packages/webpack-cli/lib/utils/arg-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index 85d6947c116..6bdfc51f502 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -23,7 +23,7 @@ function argParser(options, args, argsOnly = false, name = '', helpFunction = un }); } - // Use customised help output is avaliable + // Use customized help output if available if (helpFunction) { parser.on('option:help', () => { helpFunction(args); From c068011b0cae00b9071e9e201e93df15780c0a48 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 25 Apr 2020 02:12:52 -0500 Subject: [PATCH 42/43] chore: remove unnecessary log --- test/stats/stats.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/stats/stats.test.js b/test/stats/stats.test.js index 8fb0145fe82..656a6633825 100644 --- a/test/stats/stats.test.js +++ b/test/stats/stats.test.js @@ -48,7 +48,6 @@ describe('stats flag', () => { it('should warn when an unknown flag stats value is passed', () => { const { stderr, stdout } = run(__dirname, ['--stats', 'foo']); expect(stderr).toBeTruthy(); - console.log(stderr); expect(stderr).toContain('invalid value for stats'); expect(stdout).toBeTruthy(); }); From 9351796400bad7a0f9739a49bb2651396ccbd875 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 25 Apr 2020 02:34:01 -0500 Subject: [PATCH 43/43] chore: add runPromptWithAnswers changes for stability --- test/init/generator/init-inquirer.test.js | 2 +- test/loader/loader.test.js | 2 +- test/utils/test-utils.js | 42 ++++++++++++++++++----- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/test/init/generator/init-inquirer.test.js b/test/init/generator/init-inquirer.test.js index 0402c8edf72..2c97c9fd71b 100644 --- a/test/init/generator/init-inquirer.test.js +++ b/test/init/generator/init-inquirer.test.js @@ -22,7 +22,7 @@ describe('init', () => { }); it('should scaffold when given answers', async () => { - const stdout = await runPromptWithAnswers(genPath, ['init'], ['N', ENTER, ENTER, ENTER, ENTER, ENTER, ENTER, ENTER]); + const { stdout } = await runPromptWithAnswers(genPath, ['init'], ['N', ENTER, ENTER, ENTER, ENTER, ENTER, ENTER, ENTER]); expect(stdout).toBeTruthy(); expect(stdout).toContain(firstPrompt); diff --git a/test/loader/loader.test.js b/test/loader/loader.test.js index 0aec72825d8..49e625a9a5e 100644 --- a/test/loader/loader.test.js +++ b/test/loader/loader.test.js @@ -31,7 +31,7 @@ describe('loader command', () => { }); it('should scaffold loader template with a given name', async () => { - const stdout = await runPromptWithAnswers(__dirname, ['loader'], [loaderName, ENTER]); + const { stdout } = await runPromptWithAnswers(__dirname, ['loader'], [loaderName, ENTER]); expect(stdout).toContain(firstPrompt); diff --git a/test/utils/test-utils.js b/test/utils/test-utils.js index 78d049c9693..d107e50de74 100644 --- a/test/utils/test-utils.js +++ b/test/utils/test-utils.js @@ -67,17 +67,24 @@ function runWatch({ testCase, args = [], setOutput = true, outputKillStr = 'Time }); } -function runAndGetWatchProc(testCase, args = [], setOutput = true) { +function runAndGetWatchProc(testCase, args = [], setOutput = true, input = '', forcePipe = false) { const cwd = path.resolve(testCase); const outputPath = path.resolve(testCase, 'bin'); const argsWithOutput = setOutput ? args.concat('--output', outputPath) : args; - const webpackProc = execa(WEBPACK_PATH, argsWithOutput, { + const options = { cwd, reject: false, - stdio: ENABLE_LOG_COMPILATION ? 'inherit' : 'pipe', - }); + stdio: ENABLE_LOG_COMPILATION && !forcePipe ? 'inherit' : 'pipe', + }; + + // some tests don't work if the input option is an empty string + if (input) { + options.input = input; + } + + const webpackProc = execa(WEBPACK_PATH, argsWithOutput, options); return webpackProc; } @@ -86,8 +93,8 @@ function runAndGetWatchProc(testCase, args = [], setOutput = true) { * @param {string} location location of current working directory * @param {string[]} answers answers to be passed to stdout for inquirer question */ -const runPromptWithAnswers = async (location, args, answers) => { - const runner = runAndGetWatchProc(location, args, false); +const runPromptWithAnswers = (location, args, answers) => { + const runner = runAndGetWatchProc(location, args, false, '', true); runner.stdin.setDefaultEncoding('utf-8'); // Simulate answers by sending the answers after waiting for 2s @@ -102,14 +109,33 @@ const runPromptWithAnswers = async (location, args, answers) => { }); }, Promise.resolve()); - await simulateAnswers.then(() => { + simulateAnswers.then(() => { runner.stdin.end(); }); return new Promise((resolve) => { + const obj = {}; + let stdoutDone = false; + let stderrDone = false; runner.stdout.pipe( concat((result) => { - resolve(result.toString()); + stdoutDone = true; + obj.stdout = result.toString(); + if (stderrDone) { + runner.kill('SIGKILL'); + resolve(obj); + } + }), + ); + + runner.stderr.pipe( + concat((result) => { + stderrDone = true; + obj.stderr = result.toString(); + if (stdoutDone) { + runner.kill('SIGKILL'); + resolve(obj); + } }), ); });