diff --git a/packages/webpack-cli/lib/groups/ConfigGroup.js b/packages/webpack-cli/lib/groups/ConfigGroup.js index d98a6183154..beac89662e8 100644 --- a/packages/webpack-cli/lib/groups/ConfigGroup.js +++ b/packages/webpack-cli/lib/groups/ConfigGroup.js @@ -167,17 +167,7 @@ const finalize = async (moduleObj, args) => { // `Promise` may return `Function` if (typeof rawConfig === 'function') { // when config is a function, pass the env from args to the config function - let envs; - - if (Array.isArray(env)) { - envs = env.reduce((envObject, envOption) => { - envObject[envOption] = true; - - return envObject; - }, {}); - } - - rawConfig = await rawConfig(envs, args); + rawConfig = await rawConfig(env, args); } return rawConfig; diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index dc2fbe82729..e60e1833b02 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -92,6 +92,28 @@ const argParser = (options, args, argsOnly = false, name = '') => { // a multiple argument parsing function const multiArg = (value, previous = []) => previous.concat([value]); parserInstance.option(flagsWithType, option.description, multiArg, option.defaultValue).action(() => {}); + } else if (option.multipleType) { + // for options which accept multiple types like env + // so you can do `--env platform=staging --env production` + // { platform: "staging", production: true } + const multiArg = (value, previous = {}) => { + // this ensures we're only splitting by the first `=` + const [allKeys, val] = value.split(/=(.+)/, 2); + const splitKeys = allKeys.split(/\.(?!$)/); + let prevRef = previous; + splitKeys.forEach((someKey, index) => { + if (!prevRef[someKey]) prevRef[someKey] = {}; + if ('string' === typeof prevRef[someKey]) { + prevRef[someKey] = {}; + } + if (index === splitKeys.length - 1) { + prevRef[someKey] = val || true; + } + prevRef = prevRef[someKey]; + }); + return previous; + }; + parserInstance.option(flagsWithType, option.description, multiArg, option.defaultValue).action(() => {}); } else { // Prevent default behavior for standalone options parserInstance.option(flagsWithType, option.description, option.defaultValue).action(() => {}); diff --git a/packages/webpack-cli/lib/utils/cli-flags.js b/packages/webpack-cli/lib/utils/cli-flags.js index 0f5f508169a..807d94c0677 100644 --- a/packages/webpack-cli/lib/utils/cli-flags.js +++ b/packages/webpack-cli/lib/utils/cli-flags.js @@ -205,7 +205,7 @@ const core = [ name: 'env', usage: '--env', type: String, - multiple: true, + multipleType: true, description: 'Environment passed to the configuration when it is a function', }, { diff --git a/test/config/type/function-with-env/function-with-env.test.js b/test/config/type/function-with-env/function-with-env.test.js index fae211de366..bbb878377ee 100644 --- a/test/config/type/function-with-env/function-with-env.test.js +++ b/test/config/type/function-with-env/function-with-env.test.js @@ -4,6 +4,13 @@ const { resolve } = require('path'); const { run } = require('../../../utils/test-utils'); describe('function configuration', () => { + it('should throw when env is not supplied', () => { + const { stderr, stdout, exitCode } = run(__dirname, ['--env'], false); + expect(stdout).toBeFalsy(); + expect(stderr).toBeTruthy(); + expect(stderr).toContain(`option '--env ' argument missing`); + expect(exitCode).toEqual(1); + }); it('is able to understand a configuration file as a function', () => { const { stderr, stdout } = run(__dirname, ['--env', 'isProd']); expect(stderr).toBeFalsy(); @@ -18,6 +25,62 @@ describe('function configuration', () => { // Should generate the appropriate files expect(existsSync(resolve(__dirname, './bin/dev.js'))).toBeTruthy(); }); + it('Supports passing string in env', () => { + const { stderr, stdout } = run(__dirname, [ + '--env', + 'environment=production', + '--env', + 'app.title=Luffy', + '-c', + 'webpack.env.config.js', + ]); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + // Should generate the appropriate files + expect(existsSync(resolve(__dirname, './bin/Luffy.js'))).toBeTruthy(); + }); + it('Supports long nested values in env', () => { + const { stderr, stdout } = run(__dirname, [ + '--env', + 'file.name.is.this=Atsumu', + '--env', + 'environment=production', + '-c', + 'webpack.env.config.js', + ]); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + // Should generate the appropriate files + expect(existsSync(resolve(__dirname, './bin/Atsumu.js'))).toBeTruthy(); + }); + it('Supports multiple equal in a string', () => { + const { stderr, stdout } = run(__dirname, [ + '--env', + 'file=name=is=Eren', + '--env', + 'environment=multipleq', + '-c', + 'webpack.env.config.js', + ]); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + // Should generate the appropriate files + expect(existsSync(resolve(__dirname, './bin/name=is=Eren.js'))).toBeTruthy(); + }); + it('Supports dot at the end', () => { + const { stderr, stdout } = run(__dirname, ['--env', 'name.=Hisoka', '--env', 'environment=dot', '-c', 'webpack.env.config.js']); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + // Should generate the appropriate files + expect(existsSync(resolve(__dirname, './bin/Hisoka.js'))).toBeTruthy(); + }); + it('Supports dot at the end', () => { + const { stderr, stdout } = run(__dirname, ['--env', 'name.', '--env', 'environment=dot', '-c', 'webpack.env.config.js']); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + // Should generate the appropriate files + expect(existsSync(resolve(__dirname, './bin/true.js'))).toBeTruthy(); + }); it('is able to understand multiple env flags', (done) => { const { stderr, stdout } = run(__dirname, ['--env', 'isDev', '--env', 'verboseStats', '--env', 'envMessage']); expect(stderr).toBeFalsy(); diff --git a/test/config/type/function-with-env/webpack.env.config.js b/test/config/type/function-with-env/webpack.env.config.js new file mode 100644 index 00000000000..b131cf9866d --- /dev/null +++ b/test/config/type/function-with-env/webpack.env.config.js @@ -0,0 +1,32 @@ +module.exports = (env) => { + const { environment, app, file } = env; + const customName = file && file.name && file.name.is && file.name.is.this; + const appTitle = app && app.title; + if (environment === 'production') { + return { + entry: './a.js', + output: { + filename: `${customName ? customName : appTitle}.js`, + }, + }; + } + if (environment === 'multipleq') { + const { file } = env; + return { + entry: './a.js', + output: { + filename: `${file}.js`, + }, + }; + } + if (environment === 'dot') { + const file = env['name.']; + return { + entry: './a.js', + output: { + filename: `${file}.js`, + }, + }; + } + return {}; +}; diff --git a/test/config/type/promise-function/promise-function.test.js b/test/config/type/promise-function/promise-function.test.js index 220e908bae5..ad5f372201e 100644 --- a/test/config/type/promise-function/promise-function.test.js +++ b/test/config/type/promise-function/promise-function.test.js @@ -6,8 +6,6 @@ const { run } = require('../../../utils/test-utils'); describe('promise function', () => { it('is able to understand a configuration file as a promise', (done) => { const { stdout, stderr } = run(__dirname, ['-c', './webpack.config.js'], false); - console.log(stdout); - console.log(stderr); expect(stdout).toBeTruthy(); expect(stderr).toBeFalsy(); stat(resolve(__dirname, './binary/promise.js'), (err, stats) => {