diff --git a/packages/webpack-cli/__tests__/cli-executer.test.js b/packages/webpack-cli/__tests__/cli-executer.test.js deleted file mode 100644 index f64847f5084..00000000000 --- a/packages/webpack-cli/__tests__/cli-executer.test.js +++ /dev/null @@ -1,65 +0,0 @@ -jest.mock('enquirer'); - -describe('CLI Executer', () => { - let cliExecuter = null; - let multiCalls = 0; - let multiChoices = null; - let multiMapper = null; - - let inputCalls = 0; - const inputConstructorObjs = []; - - beforeAll(() => { - let inputRunCount = 0; - - const enquirer = require('enquirer'); - enquirer.MultiSelect = class MultiSelect { - constructor(obj) { - multiCalls++; - multiChoices = obj.choices; - multiMapper = obj.result; - } - - run() { - return ['--config', '--entry', '--progress']; - } - }; - enquirer.Input = class Input { - constructor(obj) { - this.mapper = obj.result; - inputCalls++; - inputConstructorObjs.push(obj); - } - - run() { - inputRunCount++; - return this.mapper(`test${inputRunCount}`); - } - }; - - cliExecuter = require('../lib/utils/cli-executer'); - }); - - it('runs enquirer options then runs webpack', async () => { - const args = await cliExecuter(); - expect(args.length).toBe(5); - - // check that webpack options are actually being displayed that - // the user can select from - expect(multiCalls).toEqual(1); - expect(multiChoices instanceof Array).toBeTruthy(); - expect(multiChoices.length > 0).toBeTruthy(); - expect(multiChoices[0]).toMatch(/--entry/); - - // ensure flag names are parsed out correctly - expect(typeof multiMapper).toEqual('function'); - expect(multiMapper(['--test1: test flag', '--test2: test flag 2'])).toEqual(['--test1', '--test2']); - - // check that the user is then prompted to set values to - // some flags - expect(inputCalls).toEqual(2); - expect(inputConstructorObjs.length).toEqual(2); - expect(inputConstructorObjs[0].message).toEqual('Enter value of the --config flag'); - expect(inputConstructorObjs[1].message).toEqual('Enter value of the --entry flag'); - }); -}); diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index cb577a3ef32..57d141e89ab 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -1,99 +1,60 @@ -const { options } = require('colorette'); const WebpackCLI = require('./webpack-cli'); const { core } = require('./utils/cli-flags'); const logger = require('./utils/logger'); const { isCommandUsed } = require('./utils/arg-utils'); -const cliExecuter = require('./utils/cli-executer'); const argParser = require('./utils/arg-parser'); -require('./utils/process-log'); - process.title = 'webpack-cli'; -// Create a new instance of the CLI object -const cli = new WebpackCLI(); - -const parseArgs = (args) => argParser(core, args, true, process.title); - const runCLI = async (cliArgs) => { - let args; + const parsedArgs = argParser(core, cliArgs, true, process.title); const commandIsUsed = isCommandUsed(cliArgs); const parsedArgs = parseArgs(cliArgs); - if (commandIsUsed) { return; } try { - // 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 + // Create a new instance of the CLI object + const cli = new WebpackCLI(); + + // 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.unknownArgs.length > 0 && !parsedArgs.unknownArgs[0].startsWith('-')) { - if (parsedArgs.unknownArgs.length === 1) { - entry = parsedArgs.unknownArgs[0]; - } else { - entry = []; - parsedArgs.unknownArgs.forEach((unknown) => { - if (!unknown.startsWith('-')) { - entry.push(unknown); - } - }); - } - } else if (parsedArgs.unknownArgs.length > 0) { - parsedArgs.unknownArgs.forEach((unknown) => { - logger.warn(`Unknown argument: ${unknown}`); + + if (parsedArgs.unknownArgs.length > 0) { + entry = []; + + parsedArgs.unknownArgs = parsedArgs.unknownArgs.filter((item) => { + if (item.startsWith('-')) { + return true; + } + + entry.push(item); + + return false; }); - const args = await cliExecuter(); - const { opts } = parseArgs(args); - await cli.run(opts, core); - return; } + + if (parsedArgs.unknownArgs.length > 0) { + parsedArgs.unknownArgs.forEach(async (unknown) => { + logger.error(`Unknown argument: ${unknown}`); + }); + + process.exit(2); + } + const parsedArgsOpts = parsedArgs.opts; - // Enable/Disable color on console - options.enabled = parsedArgsOpts.color ? true : false; if (entry) { parsedArgsOpts.entry = entry; } - const result = await cli.run(parsedArgsOpts, core); - if (!result) { - return; - } - } catch (err) { - if (err.name === 'UNKNOWN_VALUE') { - logger.error(`Parse Error (unknown argument): ${err.value}`); - return; - } else if (err.name === 'ALREADY_SET') { - const argsMap = {}; - const keysToDelete = []; - cliArgs.forEach((arg, idx) => { - const oldMapValue = argsMap[arg]; - argsMap[arg] = { - value: cliArgs[idx], - pos: idx, - }; - // Swap idx of overridden value - if (oldMapValue) { - argsMap[arg].pos = oldMapValue.pos; - keysToDelete.push(idx + 1); - } - }); - // Filter out the value for the overridden key - const newArgKeys = Object.keys(argsMap).filter((arg) => !keysToDelete.includes(argsMap[arg].pos)); - - cliArgs = newArgKeys; - args = argParser('', core, cliArgs); - await cli.run(args.opts, core); - logger.warn('\nDuplicate flags found, defaulting to last set value'); - } else { - logger.error(err); - return; - } + await cli.run(parsedArgsOpts, core); + } catch (error) { + logger.error(error); + process.exit(2); } }; diff --git a/packages/webpack-cli/lib/utils/arg-parser.js b/packages/webpack-cli/lib/utils/arg-parser.js index d6204d0406c..896362c2503 100644 --- a/packages/webpack-cli/lib/utils/arg-parser.js +++ b/packages/webpack-cli/lib/utils/arg-parser.js @@ -16,6 +16,7 @@ const { defaultCommands } = require('./commands'); */ const argParser = (options, args, argsOnly = false, name = '') => { const parser = new commander.Command(); + // Set parser name parser.name(name); parser.storeOptionsAsProperties(false); @@ -58,6 +59,7 @@ const argParser = (options, args, argsOnly = false, name = '') => { options.reduce((parserInstance, option) => { let optionType = option.type; let isStringOrBool = false; + if (Array.isArray(optionType)) { // filter out duplicate types optionType = optionType.filter((type, index) => { @@ -84,7 +86,9 @@ const argParser = (options, args, argsOnly = false, name = '') => { } const flags = option.alias ? `-${option.alias}, --${option.name}` : `--${option.name}`; + let flagsWithType = flags; + if (isStringOrBool) { // commander recognizes [value] as an optional placeholder, // making this flag work either as a string or a boolean @@ -107,17 +111,25 @@ const argParser = (options, args, argsOnly = false, name = '') => { // 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 (!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(() => {}); @@ -151,8 +163,8 @@ const argParser = (options, args, argsOnly = false, name = '') => { 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); @@ -160,6 +172,7 @@ const argParser = (options, args, argsOnly = false, name = '') => { 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); diff --git a/packages/webpack-cli/lib/utils/cli-executer.js b/packages/webpack-cli/lib/utils/cli-executer.js deleted file mode 100644 index 276981f1285..00000000000 --- a/packages/webpack-cli/lib/utils/cli-executer.js +++ /dev/null @@ -1,64 +0,0 @@ -const { MultiSelect, Input } = require('enquirer'); -const { cyan } = require('colorette'); -const logger = require('./logger'); -const cliArgs = require('./cli-flags').core; - -const prompter = async () => { - const args = []; - - const typePrompt = new MultiSelect({ - name: 'type', - message: 'Which flags do you want to use?', - choices: cliArgs.reduce((prev, curr) => { - return [...prev, `--${curr.name}: ${curr.description}`]; - }, []), - result: (value) => { - return value.map((flag) => flag.split(':')[0]); - }, - }); - - const selections = await typePrompt.run(); - - const boolArgs = []; - const questions = []; - selections.forEach((selection) => { - const options = cliArgs.find((flag) => { - return flag.name === selection.slice(2); - }); - - if (options.type === Boolean) { - boolArgs.push(selection); - return; - } - - const valuePrompt = new Input({ - name: 'value', - message: `Enter value of the ${selection} flag`, - initial: options.defaultValue, - result: (value) => [selection, value], - validate: (value) => Boolean(value), - }); - questions.push(valuePrompt); - }); - - // Create promise chain to force synchronous prompt of question - for await (const question of questions) { - const flagArgs = await question.run(); - args.push(...flagArgs); - } - - return [...args, ...boolArgs]; -}; - -const run = async () => { - try { - const args = await prompter(); - logger.info('\nExecuting CLI\n'); - return args; - } catch (err) { - logger.error(`Action Interrupted, use ${cyan('webpack-cli help')} to see possible options.`); - process.exit(2); - } -}; - -module.exports = run; diff --git a/packages/webpack-cli/lib/utils/process-log.js b/packages/webpack-cli/lib/utils/process-log.js deleted file mode 100644 index 45933841849..00000000000 --- a/packages/webpack-cli/lib/utils/process-log.js +++ /dev/null @@ -1,18 +0,0 @@ -const logger = require('./logger'); - -function logErrorAndExit(error) { - if (error && error.stack) logger.error(error.stack); - process.exit(error.exitCode); -} - -process.on('uncaughtException', (error) => { - logger.error(`Uncaught exception: ${error}`); - logErrorAndExit(error); -}); - -process.on('unhandledRejection', (error) => { - logger.error(`Promise rejection: ${error}`); - logErrorAndExit(error); -}); - -//TODO: implement logger for debug diff --git a/test/watch/watch-flag.test.js b/test/watch/watch-flag.test.js index 362c01c3615..e326fed48d8 100644 --- a/test/watch/watch-flag.test.js +++ b/test/watch/watch-flag.test.js @@ -1,18 +1,19 @@ 'use strict'; +const stripAnsi = require('strip-ansi'); const { runAndGetWatchProc, isWebpack5 } = require('../utils/test-utils'); const { writeFileSync } = require('fs'); const { resolve } = require('path'); const wordsInStatsv4 = ['Hash', 'Version', 'Time', 'Built at:', 'main.js']; -const wordsInStatsv5 = ['asset', 'index.js', `compiled \u001b[1m\u001b[32msuccessfully\u001b[39m\u001b[22m`]; +const wordsInStatsv5 = ['asset', 'index.js', 'compiled successfully']; describe('--watch flag', () => { it('should recompile upon file change', (done) => { const proc = runAndGetWatchProc(__dirname, ['--watch'], false, '', true); let semaphore = 0; proc.stdout.on('data', (chunk) => { - const data = chunk.toString(); + const data = stripAnsi(chunk.toString()); if (semaphore === 0 && data.includes('watching files for updates')) { process.nextTick(() => { @@ -47,7 +48,7 @@ describe('--watch flag', () => { const proc = runAndGetWatchProc(__dirname, ['--watch'], false, '', true); let semaphore = 0; proc.stdout.on('data', (chunk) => { - const data = chunk.toString(); + const data = stripAnsi(chunk.toString()); if (semaphore === 0 && data.includes('Compilation starting')) { semaphore++;