diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index 860e1fbbeb8..07c27567955 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -23,7 +23,7 @@ const isCommandUsed = (commands) => async function runCLI(cli, commandIsUsed) { let args; const runVersion = () => { - cli.runVersion(commandIsUsed); + cli.runVersion(process.argv, commandIsUsed); }; const parsedArgs = argParser(core, process.argv, false, process.title, cli.runHelp, runVersion); diff --git a/packages/webpack-cli/lib/groups/HelpGroup.js b/packages/webpack-cli/lib/groups/HelpGroup.js index 97263473ee6..91d6130bad5 100644 --- a/packages/webpack-cli/lib/groups/HelpGroup.js +++ b/packages/webpack-cli/lib/groups/HelpGroup.js @@ -3,8 +3,8 @@ const { core, commands } = require('../utils/cli-flags'); const commandLineUsage = require('command-line-usage'); class HelpGroup { - outputHelp(isCommand = true, subject) { - if (subject) { + outputHelp(isCommand = true, subject, invalidArgs) { + if (subject && invalidArgs.length === 0) { const info = isCommand ? commands : core; // Contains object with details about given subject const options = info.find((commandOrFlag) => { @@ -34,20 +34,19 @@ class HelpGroup { }); process.stdout.write(flags); } + } else if (invalidArgs.length > 0) { + console.warn(chalk.yellow(`\nYou provided an invalid option '${invalidArgs[0]}'.`)); + process.stdout.write(this.run().outputOptions.help); } else { process.stdout.write(this.run().outputOptions.help); } process.stdout.write('\n Made with ♥️ by the webpack team \n'); } - outputVersion(externalPkg) { - const commandsUsed = () => { - return process.argv.filter((val) => commands.find(({ name }) => name === val)); - }; - - if (externalPkg && commandsUsed().length === 1) { + outputVersion(externalPkg, commandsUsed, invalidArgs) { + if (externalPkg && commandsUsed.length === 1 && invalidArgs.length === 0) { try { - if (commandsUsed().includes(externalPkg.name)) { + if (commandsUsed.includes(externalPkg.name)) { const { name, version } = require(`@webpack-cli/${externalPkg.name}/package.json`); process.stdout.write(`\n${name} ${version}`); } else { @@ -60,11 +59,17 @@ class HelpGroup { } } - if (commandsUsed().length > 1) { + if (commandsUsed.length > 1) { console.error(chalk.red('\nYou provided multiple commands. Please use only one command at a time.\n')); process.exit(); } + if (invalidArgs.length > 0) { + console.error(chalk.red(`\nError: Invalid Option '${invalidArgs[0]}'.`)); + console.info(chalk.cyan('Run webpack --help to see available commands and arguments.\n')); + process.exit(-2); + } + const pkgJSON = require('../../package.json'); const webpack = require('webpack'); process.stdout.write(`\nwebpack-cli ${pkgJSON.version}`); diff --git a/packages/webpack-cli/lib/utils/unknown-args.js b/packages/webpack-cli/lib/utils/unknown-args.js new file mode 100644 index 00000000000..4d06f43e3c4 --- /dev/null +++ b/packages/webpack-cli/lib/utils/unknown-args.js @@ -0,0 +1,10 @@ +const commandNames = require('./commands').names; +const flagNames = require('./core-flags').names; + +module.exports = { + commands: [...commandNames], + flags: [...flagNames], + allNames: [...commandNames, ...flagNames], + hasUnknownArgs: (args, ...names) => + args.filter((e) => !names.includes(e) && !e.includes('--color') && e !== 'version' && e !== '-v' && !e.includes('help')), +}; diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index a141e82ddbd..0ad86885d86 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -307,19 +307,21 @@ class WebpackCLI extends GroupHelper { runHelp(args) { const HelpGroup = require('./groups/HelpGroup'); - const commandNames = require('./utils/commands').names; - const flagNames = require('./utils/core-flags').names; - const allNames = [...commandNames, ...flagNames]; + const { commands, allNames, hasUnknownArgs } = require('./utils/unknown-args'); const subject = allNames.filter((name) => { return args.includes(name); })[0]; - const isCommand = commandNames.includes(subject); - return new HelpGroup().outputHelp(isCommand, subject); + const invalidArgs = hasUnknownArgs(args.slice(2), ...allNames); + const isCommand = commands.includes(subject); + return new HelpGroup().outputHelp(isCommand, subject, invalidArgs); } - runVersion(externalPkg) { + runVersion(args, externalPkg) { const HelpGroup = require('./groups/HelpGroup'); - return new HelpGroup().outputVersion(externalPkg); + const { commands, allNames, hasUnknownArgs } = require('./utils/unknown-args'); + const commandsUsed = args.filter((val) => commands.includes(val)); + const invalidArgs = hasUnknownArgs(args.slice(2), ...allNames); + return new HelpGroup().outputVersion(externalPkg, commandsUsed, invalidArgs); } } diff --git a/test/help/help-commands.test.js b/test/help/help-commands.test.js index 870ff2a3e44..f97d53e6703 100644 --- a/test/help/help-commands.test.js +++ b/test/help/help-commands.test.js @@ -4,16 +4,14 @@ const { run } = require('../utils/test-utils'); const helpHeader = 'The build tool for modern web applications'; describe('commands help', () => { - it('shows default help with invalid command', () => { - const { stdout, stderr } = run(__dirname, ['--help', 'myCommand'], false); - expect(stdout).toContain(helpHeader); - expect(stderr).toHaveLength(0); + it('throws error for invalid command with --help flag', () => { + const { stderr } = run(__dirname, ['--help', 'myCommand'], false); + expect(stderr).toContain(`You provided an invalid option 'myCommand'`); }); - it('shows command help with valid command', () => { - const { stdout, stderr } = run(__dirname, ['--help', 'init'], false); - expect(stdout).not.toContain(helpHeader); - expect(stdout).toContain('webpack init | init '); - expect(stderr).toHaveLength(0); + + it('throws error for invalid command with help command', () => { + const { stderr } = run(__dirname, ['help', 'myCommand'], false); + expect(stderr).toContain(`You provided an invalid option 'myCommand'`); }); it('gives precedence to earlier command in case of multiple commands', () => { diff --git a/test/help/help-flags.test.js b/test/help/help-flags.test.js index ed55a9fe6ba..76e0bc05e56 100644 --- a/test/help/help-flags.test.js +++ b/test/help/help-flags.test.js @@ -4,11 +4,16 @@ const { run } = require('../utils/test-utils'); const helpHeader = 'The build tool for modern web applications'; describe('commands help', () => { - it('shows default help with invalid flag', () => { - const { stdout, stderr } = run(__dirname, ['--help', '--my-flag'], false); - expect(stdout).toContain(helpHeader); - expect(stderr).toHaveLength(0); + it('throws error for invalid flag with --help flag', () => { + const { stderr } = run(__dirname, ['--help', '--my-flag'], false); + expect(stderr).toContain(`You provided an invalid option '--my-flag'`); + }); + + it('throws error for invalid flag with help command', () => { + const { stderr } = run(__dirname, ['help', '--my-flag'], false); + expect(stderr).toContain(`You provided an invalid option '--my-flag'`); }); + it('shows flag help with valid flag', () => { const { stdout, stderr } = run(__dirname, ['--help', '--merge'], false); expect(stdout).not.toContain(helpHeader); diff --git a/test/help/help-multi-args.test.js b/test/help/help-multi-args.test.js index 78d03553179..9614a2c48cb 100644 --- a/test/help/help-multi-args.test.js +++ b/test/help/help-multi-args.test.js @@ -1,24 +1,25 @@ 'use strict'; const { run } = require('../utils/test-utils'); -const outputDescription = 'Output location of the file generated by webpack'; -const createDescription = 'Initialize a new webpack configuration'; -describe('help flag with multiple arguments', () => { - it('outputs info with dashed syntax', () => { - const { stdout, stderr } = run(__dirname, ['--help', '--target', 'browser']); - expect(stdout).toContain(outputDescription); - expect(stderr).toHaveLength(0); - }); +const { commands } = require('../../packages/webpack-cli/lib/utils/cli-flags'); +const helpHeader = 'The build tool for modern web applications'; - it('outputs info with multiple arguments using dashes and with precedence', () => { - const { stdout, stderr } = run(__dirname, ['--target', 'browser', '--help']); - expect(stdout).toContain(outputDescription); - expect(stderr).toHaveLength(0); +describe('help cmd with multiple arguments', () => { + commands.forEach((cmd) => { + it(`shows cmd help with ${cmd.name}`, () => { + const { stdout, stderr } = run(__dirname, ['--help', `${cmd.name}`], false); + expect(stdout).not.toContain(helpHeader); + expect(stdout).toContain(`${cmd.name}`); + expect(stdout).toContain(`${cmd.usage}`); + expect(stdout).toContain(`${cmd.description}`); + expect(stderr).toHaveLength(0); + }); }); - it('outputs info with multiple commands and with precedence', () => { - const { stdout, stderr } = run(__dirname, ['init', 'help']); - expect(stdout).toContain(createDescription); + it('should output help for --version by taking precedence', () => { + const { stdout, stderr } = run(__dirname, ['--help', '--version'], false); + expect(stdout).not.toContain(helpHeader); + expect(stdout).toContain('webpack --version'); expect(stderr).toHaveLength(0); }); }); diff --git a/test/help/help-single-arg.test.js b/test/help/help-single-arg.test.js index 3bed9520b2f..fc814597ee6 100644 --- a/test/help/help-single-arg.test.js +++ b/test/help/help-single-arg.test.js @@ -18,6 +18,7 @@ describe('single help flag', () => { expect(stdout).toContain(example); expect(stderr).toHaveLength(0); }); + it('outputs help info with command syntax', () => { const { stdout, stderr } = run(__dirname, ['help'], false); expect(stdout).toContain(helpHeader); diff --git a/test/version/version-external-packages.test.js b/test/version/version-external-packages.test.js index 40927875d3c..affba2ef835 100644 --- a/test/version/version-external-packages.test.js +++ b/test/version/version-external-packages.test.js @@ -9,35 +9,53 @@ const cliPkgJSON = require('../../packages/webpack-cli/package.json'); describe('version flag with external packages', () => { it('outputs version with init', () => { - const { stdout, stderr } = run(__dirname, ['init', '--version']); + const { stdout, stderr } = run(__dirname, ['init', '--version'], false); expect(stdout).toContain(initPkgJSON.version); expect(stdout).toContain(cliPkgJSON.version); expect(stderr).toHaveLength(0); }); it('outputs version with info', () => { - const { stdout, stderr } = run(__dirname, ['info', '--version']); + const { stdout, stderr } = run(__dirname, ['info', '--version'], false); expect(stdout).toContain(infoPkgJSON.version); expect(stdout).toContain(cliPkgJSON.version); expect(stderr).toHaveLength(0); }); it('outputs version with serve', () => { - const { stdout, stderr } = run(__dirname, ['serve', '--version']); + const { stdout, stderr } = run(__dirname, ['serve', '--version'], false); expect(stdout).toContain(servePkgJSON.version); expect(stdout).toContain(cliPkgJSON.version); expect(stderr).toHaveLength(0); }); it('outputs version with migrate', () => { - const { stdout, stderr } = run(__dirname, ['migrate', '--version']); + const { stdout, stderr } = run(__dirname, ['migrate', '--version'], false); expect(stdout).toContain(migratePkgJSON.version); expect(stdout).toContain(cliPkgJSON.version); expect(stderr).toHaveLength(0); }); it(' should throw error for multiple commands', () => { - const { stderr } = run(__dirname, ['init', 'migrate', '--version']); + const { stderr } = run(__dirname, ['init', 'migrate', '--version'], false); expect(stderr).toContain('You provided multiple commands.'); }); + + it(' should throw error if invalid argument is present with --version flag', () => { + const { stderr, stdout } = run(__dirname, ['init', 'abc', '--version'], false); + expect(stderr).toContain(`Error: Invalid Option 'abc'`); + expect(stdout).toContain('Run webpack --help to see available commands and arguments'); + }); + + it(' should throw error if invalid argument is present with version command', () => { + const { stderr, stdout } = run(__dirname, ['init', 'abc', 'version'], false); + expect(stderr).toContain(`Error: Invalid Option 'abc'`); + expect(stdout).toContain('Run webpack --help to see available commands and arguments'); + }); + + it(' should throw error if invalid argument is present with -v alias', () => { + const { stderr, stdout } = run(__dirname, ['init', 'abc', '-v'], false); + expect(stderr).toContain(`Error: Invalid Option 'abc'`); + expect(stdout).toContain('Run webpack --help to see available commands and arguments'); + }); }); diff --git a/test/version/version-multi-args.test.js b/test/version/version-multi-args.test.js index c4b03a0ef1a..28064138658 100644 --- a/test/version/version-multi-args.test.js +++ b/test/version/version-multi-args.test.js @@ -4,14 +4,8 @@ const { run } = require('../utils/test-utils'); const pkgJSON = require('../../packages/webpack-cli/package.json'); describe('version flag with multiple arguments', () => { - it('outputs version with mixed syntax', () => { - const { stdout, stderr } = run(__dirname, ['--version', '--target', 'browser']); - expect(stdout).toContain(pkgJSON.version); - expect(stderr).toHaveLength(0); - }); - it('does not output version with help command', () => { - const { stdout, stderr } = run(__dirname, ['version', 'help']); + const { stdout, stderr } = run(__dirname, ['version', 'help'], false); expect(stdout).not.toContain(pkgJSON.version); const uniqueIdentifier = 'Made with ♥️ by the webpack team'; @@ -20,7 +14,7 @@ describe('version flag with multiple arguments', () => { }); it('does not output version with help dashed', () => { - const { stdout, stderr } = run(__dirname, ['version', '--help']); + const { stdout, stderr } = run(__dirname, ['version', '--help'], false); expect(stdout).not.toContain(pkgJSON.version); const uniqueIdentifier = 'Made with ♥️ by the webpack team'; @@ -28,9 +22,24 @@ describe('version flag with multiple arguments', () => { expect(stderr).toHaveLength(0); }); - it('outputs version with multiple dashed args and has precedence', () => { - const { stdout, stderr } = run(__dirname, ['--target', 'browser', '--version']); - expect(stdout).toContain(pkgJSON.version); - expect(stderr).toHaveLength(0); + it('throws error if invalid arg is passed with version command', () => { + const { stdout, stderr } = run(__dirname, ['version', 'abc'], false); + expect(stdout).not.toContain(pkgJSON.version); + expect(stderr).toContain(`Error: Invalid Option 'abc'`); + expect(stdout).toContain('Run webpack --help to see available commands and arguments'); + }); + + it('throws error if invalid arg is passed with --version flag', () => { + const { stdout, stderr } = run(__dirname, ['--version', 'abc'], false); + expect(stdout).not.toContain(pkgJSON.version); + expect(stderr).toContain(`Error: Invalid Option 'abc'`); + expect(stdout).toContain('Run webpack --help to see available commands and arguments'); + }); + + it('throws error if invalid arg is passed with -v alias', () => { + const { stdout, stderr } = run(__dirname, ['-v', 'abc'], false); + expect(stdout).not.toContain(pkgJSON.version); + expect(stderr).toContain(`Error: Invalid Option 'abc'`); + expect(stdout).toContain('Run webpack --help to see available commands and arguments'); }); }); diff --git a/test/version/version-single-arg.test.js b/test/version/version-single-arg.test.js index 7fe652e37d5..9f2b6a1f174 100644 --- a/test/version/version-single-arg.test.js +++ b/test/version/version-single-arg.test.js @@ -5,19 +5,19 @@ const pkgJSON = require('../../packages/webpack-cli/package.json'); describe('single version flag', () => { it('outputs versions with command syntax', () => { - const { stdout, stderr } = run(__dirname, ['version']); + const { stdout, stderr } = run(__dirname, ['version'], false); expect(stdout).toContain(pkgJSON.version); expect(stderr).toHaveLength(0); }); it('outputs versions with dashed syntax', () => { - const { stdout, stderr } = run(__dirname, ['--version']); + const { stdout, stderr } = run(__dirname, ['--version'], false); expect(stdout).toContain(pkgJSON.version); expect(stderr).toHaveLength(0); }); it('outputs versions with alias syntax', () => { - const { stdout, stderr } = run(__dirname, ['-v']); + const { stdout, stderr } = run(__dirname, ['-v'], false); expect(stdout).toContain(pkgJSON.version); expect(stderr).toHaveLength(0); });