diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/lib/bootstrap.js index 485c129a259..5ba44e8cbe4 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/lib/bootstrap.js @@ -51,7 +51,7 @@ async function runCLI(cli, commandIsUsed) { cli.runHelp(process.argv); return; } else if (versionFlagExists) { - cli.runVersion(commandIsUsed); + cli.runVersion(process.argv, commandIsUsed); return; } diff --git a/packages/webpack-cli/lib/groups/HelpGroup.js b/packages/webpack-cli/lib/groups/HelpGroup.js index 97263473ee6..9bfb11fdd44 100644 --- a/packages/webpack-cli/lib/groups/HelpGroup.js +++ b/packages/webpack-cli/lib/groups/HelpGroup.js @@ -3,8 +3,16 @@ 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, args) { + const [, , ...rawArgs] = args; + const commandsUsed = rawArgs.filter((val) => commands.find(({ name }) => name === val)); + const flagsUsed = rawArgs.filter((val) => core.find(({ name }) => name === val.slice(2))); + const argsUsed = [...commandsUsed, ...flagsUsed]; + const invalidArgs = rawArgs.filter( + (e) => !argsUsed.includes(e) && !e.includes('--color') && e !== 'version' && e !== '-v' && e !== 'help', + ); + + if (subject && invalidArgs.length === 0) { const info = isCommand ? commands : core; // Contains object with details about given subject const options = info.find((commandOrFlag) => { @@ -34,20 +42,27 @@ 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)); - }; + outputVersion(args, externalPkg) { + const [, , ...rawArgs] = args; + const commandsUsed = rawArgs.filter((val) => commands.find(({ name }) => name === val)); + const flagsUsed = rawArgs.filter((val) => core.find(({ name }) => name === val.slice(2))); + const argsUsed = [...commandsUsed, ...flagsUsed]; + const invalidArgs = rawArgs.filter( + (e) => !argsUsed.includes(e) && !e.includes('--color') && e !== 'version' && e !== '-v' && e !== 'help', + ); - if (externalPkg && commandsUsed().length === 1) { + 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 +75,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/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index 12e1b925229..9059f02bd4a 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -309,12 +309,12 @@ class WebpackCLI extends GroupHelper { return args.includes(name); })[0]; const isCommand = commandNames.includes(subject); - return new HelpGroup().outputHelp(isCommand, subject); + return new HelpGroup().outputHelp(isCommand, subject, args); } - runVersion(externalPkg) { + runVersion(args, externalPkg) { const HelpGroup = require('./groups/HelpGroup'); - return new HelpGroup().outputVersion(externalPkg); + return new HelpGroup().outputVersion(args, externalPkg); } } 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); }); diff --git a/yarn.lock b/yarn.lock index 8d46a82daf2..82c53ac05f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2415,16 +2415,6 @@ regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.26.0": - version "2.26.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.26.0.tgz#063390c404d9980767d76274df386c0aa675d91d" - integrity sha512-RELVoH5EYd+JlGprEyojUv9HeKcZqF7nZUGSblyAw1FwOGNnmQIU8kxJ69fttQvEwCsX5D6ECJT8GTozxrDKVQ== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.26.0" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" - "@typescript-eslint/experimental-utils@2.28.0": version "2.28.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.28.0.tgz#1fd0961cd8ef6522687b4c562647da6e71f8833d" @@ -2445,19 +2435,6 @@ "@typescript-eslint/typescript-estree" "2.28.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@2.26.0": - version "2.26.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.26.0.tgz#d8132cf1ee8a72234f996519a47d8a9118b57d56" - integrity sha512-3x4SyZCLB4zsKsjuhxDLeVJN6W29VwBnYpCsZ7vIdPel9ZqLfIZJgJXO47MNUkurGpQuIBALdPQKtsSnWpE1Yg== - dependencies: - debug "^4.1.1" - eslint-visitor-keys "^1.1.0" - glob "^7.1.6" - is-glob "^4.0.1" - lodash "^4.17.15" - semver "^6.3.0" - tsutils "^3.17.1" - "@typescript-eslint/typescript-estree@2.28.0": version "2.28.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.28.0.tgz#d34949099ff81092c36dc275b6a1ea580729ba00"