From 6b3161479578f572f803f579c7e71073eb797184 Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Wed, 27 Jan 2021 14:45:38 +0300 Subject: [PATCH] feat: entries syntax (#2369) --- OPTIONS.md | 40 +- packages/serve/src/index.ts | 10 +- .../webpack-cli/__tests__/resolveArgs.test.js | 20 +- packages/webpack-cli/lib/utils/cli-flags.js | 202 ------ packages/webpack-cli/lib/webpack-cli.js | 669 +++++++++++++----- test/build/basic/basic.test.js | 126 +++- test/build/basic/entry.config.js | 4 + test/build/basic/src/again.js | 1 + test/build/basic/src/entry.js | 1 + test/build/basic/src/other.js | 1 + test/config/absent/config-absent.test.js | 6 +- test/config/invalid-path/invalid-path.test.js | 6 +- .../with-config-path/with-config-path.test.js | 2 +- test/core-flags/experiments-flag.test.js | 5 +- test/core-flags/externals-flags.test.js | 5 +- test/core-flags/module-flags.test.js | 5 +- test/core-flags/optimization-flags.test.js | 5 +- test/core-flags/output-flags.test.js | 5 +- test/core-flags/performance-flags.test.js | 5 +- test/core-flags/resolve-flags.test.js | 5 +- test/core-flags/snapshot-flags.test.js | 5 +- test/core-flags/stats-flags.test.js | 5 +- test/core-flags/watch-flags.test.js | 5 +- test/core-flags/webpack.test.config.js | 1 - test/help/help.test.js | 428 ++++++----- .../config-absent/merge-config-absent.test.js | 4 +- test/serve/basic/serve-basic.test.js | 47 +- test/serve/basic/serve.config.js | 7 + test/serve/basic/src/entry.js | 1 + test/serve/help/serve-help.test.js | 17 +- test/unknown/unknown.test.js | 4 +- test/watch/basic/basic.test.js | 35 + test/watch/basic/src/entry.js | 1 + 33 files changed, 991 insertions(+), 692 deletions(-) delete mode 100644 packages/webpack-cli/lib/utils/cli-flags.js create mode 100644 test/build/basic/entry.config.js create mode 100644 test/build/basic/src/again.js create mode 100644 test/build/basic/src/entry.js create mode 100644 test/build/basic/src/other.js delete mode 100644 test/core-flags/webpack.test.config.js create mode 100644 test/serve/basic/serve.config.js create mode 100644 test/serve/basic/src/entry.js create mode 100644 test/watch/basic/src/entry.js diff --git a/OPTIONS.md b/OPTIONS.md index fd2ea885820..f87c5a7269e 100644 --- a/OPTIONS.md +++ b/OPTIONS.md @@ -1,12 +1,6 @@ ``` -Usage: webpack [options] -Alternative usage: webpack --config [options] -Alternative usage: webpack build [options] -Alternative usage: webpack bundle [options] -Alternative usage: webpack b [options] -Alternative usage: webpack build --config [options] -Alternative usage: webpack bundle --config [options] -Alternative usage: webpack b --config [options] +Usage: webpack [entries...] [options] +Alternative usage to run commands: webpack [command] [options] The build tool for modern web applications. @@ -793,23 +787,23 @@ Options: --no-watch-options-stdin Do not stop watching when stdin stream has ended. Global options: - --color Enable colors on console. - --no-color Disable colors on console. - -v, --version Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands. - -h, --help [verbose] Display help for commands and options. + --color Enable colors on console. + --no-color Disable colors on console. + -v, --version Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands. + -h, --help [verbose] Display help for commands and options. Commands: - build|bundle|b [options] Run webpack (default command, can be omitted). - watch|w [options] Run webpack and watch for files changes. - version|v [commands...] Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands. - help|h [command] [option] Display help for commands and options. - serve|s [options] Run the webpack dev server. - info|i [options] Outputs information about your system. - init|c [options] [scaffold...] Initialize a new webpack configuration. - loader|l [output-path] Scaffold a loader. - migrate|m [new-config-path] Migrate a configuration to a new version. - configtest|t [config-path] Tests webpack configuration against validation errors. - plugin|p [output-path] Scaffold a plugin. + build|bundle|b [entries...] [options] Run webpack (default command, can be omitted). + configtest|t [config-path] Tests webpack configuration against validation errors. + help|h [command] [option] Display help for commands and options. + info|i [options] Outputs information about your system. + init|c [scaffold...] [options] Initialize a new webpack configuration. + loader|l [output-path] Scaffold a loader. + migrate|m [new-config-path] Migrate a configuration to a new version. + plugin|p [output-path] Scaffold a plugin. + serve|s [entries...] [options] Run the webpack dev server. + version|v [commands...] Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands. + watch|w [entries...] [options] Run webpack and watch for files changes. To see list of all supported commands and options run 'webpack --help=verbose'. diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index 3448af21808..5b72fd66a3a 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -6,10 +6,10 @@ class ServeCommand { await cli.makeCommand( { - name: 'serve', + name: 'serve [entries...]', alias: 's', description: 'Run the webpack dev server.', - usage: '[options]', + usage: '[entries...] [options]', pkg: '@webpack-cli/serve', dependencies: ['webpack-dev-server'], }, @@ -28,7 +28,7 @@ class ServeCommand { return [...builtInOptions, ...devServerFlags]; }, - async (options) => { + async (entries, options) => { const builtInOptions = cli.getBuiltInOptions(); let devServerFlags = []; @@ -72,6 +72,10 @@ class ServeCommand { processor(devServerOptions); } + if (entries.length > 0) { + webpackOptions.entry = [...entries, ...(webpackOptions.entry || [])]; + } + webpackOptions.argv = { ...options, env: { WEBPACK_SERVE: true, ...options.env } }; const compiler = await cli.createCompiler(webpackOptions); diff --git a/packages/webpack-cli/__tests__/resolveArgs.test.js b/packages/webpack-cli/__tests__/resolveArgs.test.js index 67a1a0feeb1..db8215e98d5 100644 --- a/packages/webpack-cli/__tests__/resolveArgs.test.js +++ b/packages/webpack-cli/__tests__/resolveArgs.test.js @@ -2,24 +2,24 @@ const { resolve } = require('path'); const webpackCLI = require('../lib/webpack-cli'); const targetValues = ['web', 'webworker', 'node', 'async-node', 'node-webkit', 'electron-main', 'electron-renderer', 'electron-preload']; -const applyOptions = new webpackCLI().applyOptions; +const cli = new webpackCLI(); describe('BasicResolver', () => { it('should handle the output option', async () => { - const result = await applyOptions({ options: {} }, { outputPath: './bundle' }); + const result = await cli.applyOptions({ options: {} }, { outputPath: './bundle' }); expect(result.options.output.path).toEqual(resolve('bundle')); }); it('should handle the mode option [production]', async () => { - const result = await applyOptions({ options: {} }, { mode: 'production' }); + const result = await cli.applyOptions({ options: {} }, { mode: 'production' }); expect(result.options).toMatchObject({ mode: 'production' }); expect(result.options.mode).toEqual('production'); }); it('should handle the mode option [development]', async () => { - const result = await applyOptions( + const result = await cli.applyOptions( { options: {} }, { mode: 'development', @@ -31,7 +31,7 @@ describe('BasicResolver', () => { }); it('should handle the mode option [none]', async () => { - const result = await applyOptions( + const result = await cli.applyOptions( { options: {} }, { mode: 'none', @@ -44,34 +44,34 @@ describe('BasicResolver', () => { it('should prefer supplied move flag over NODE_ENV', async () => { process.env.NODE_ENV = 'production'; - const result = await applyOptions({ options: {} }, { mode: 'development' }); + const result = await cli.applyOptions({ options: {} }, { mode: 'development' }); expect(result.options).toMatchObject({ mode: 'development' }); }); it('should prefer supplied move flag over mode from config', async () => { - const result = await applyOptions({ options: { mode: 'development' } }, { mode: 'production' }); + const result = await cli.applyOptions({ options: { mode: 'development' } }, { mode: 'production' }); expect(result.options).toMatchObject({ mode: 'production' }); }); it('should prefer mode form config over NODE_ENV', async () => { process.env.NODE_ENV = 'development'; - const result = await applyOptions({ options: {} }, { mode: 'production' }); + const result = await cli.applyOptions({ options: {} }, { mode: 'production' }); expect(result.options).toMatchObject({ mode: 'production' }); }); it('should prefer mode form flag over NODE_ENV and config', async () => { process.env.NODE_ENV = 'development'; - const result = await applyOptions({ options: {} }, {}); + const result = await cli.applyOptions({ options: {} }, {}); expect(result.options).toMatchObject({ mode: 'development' }); }); targetValues.map((option) => { it(`should handle ${option} option`, async () => { - const result = await applyOptions({ options: {} }, { target: option }); + const result = await cli.applyOptions({ options: {} }, { target: option }); expect(result.options.target).toEqual(option); }); diff --git a/packages/webpack-cli/lib/utils/cli-flags.js b/packages/webpack-cli/lib/utils/cli-flags.js deleted file mode 100644 index 8f3256aebb9..00000000000 --- a/packages/webpack-cli/lib/utils/cli-flags.js +++ /dev/null @@ -1,202 +0,0 @@ -const packageExists = require('./package-exists'); -const cli = packageExists('webpack') ? require('webpack').cli : undefined; - -const minimumHelpFlags = [ - 'config', - 'config-name', - 'merge', - 'env', - 'mode', - 'watch', - 'watch-options-stdin', - 'stats', - 'devtool', - 'entry', - 'target', - 'progress', - 'json', - 'name', - 'output-path', -]; - -const builtInFlags = [ - // For configs - { - name: 'config', - alias: 'c', - type: String, - multiple: true, - description: 'Provide path to a webpack configuration file e.g. ./webpack.config.js.', - }, - { - name: 'config-name', - type: String, - multiple: true, - description: 'Name of the configuration to use.', - }, - { - name: 'merge', - alias: 'm', - type: Boolean, - description: "Merge two or more configurations using 'webpack-merge'.", - }, - // Complex configs - { - name: 'env', - type: (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 (typeof prevRef[someKey] === 'string') { - prevRef[someKey] = {}; - } - - if (index === splitKeys.length - 1) { - prevRef[someKey] = val || true; - } - - prevRef = prevRef[someKey]; - }); - - return previous; - }, - multiple: true, - description: 'Environment passed to the configuration when it is a function.', - }, - - // Adding more plugins - { - name: 'hot', - alias: 'h', - type: Boolean, - negative: true, - description: 'Enables Hot Module Replacement', - negatedDescription: 'Disables Hot Module Replacement.', - }, - { - name: 'analyze', - type: Boolean, - multiple: false, - description: 'It invokes webpack-bundle-analyzer plugin to get bundle information.', - }, - { - name: 'progress', - type: [Boolean, String], - description: 'Print compilation progress during build.', - }, - { - name: 'prefetch', - type: String, - description: 'Prefetch this request.', - }, - - // Output options - { - name: 'json', - type: [String, Boolean], - alias: 'j', - description: 'Prints result as JSON or store it in a file.', - }, - - // For webpack@4 - { - name: 'entry', - type: String, - multiple: true, - description: 'The entry point(s) of your application e.g. ./src/main.js.', - }, - { - name: 'output-path', - alias: 'o', - type: String, - description: 'Output location of the file generated by webpack e.g. ./dist/.', - }, - { - name: 'target', - alias: 't', - type: String, - multiple: cli !== undefined, - description: 'Sets the build target e.g. node.', - }, - { - name: 'devtool', - type: String, - negative: true, - alias: 'd', - description: 'Determine source maps to use.', - negatedDescription: 'Do not generate source maps.', - }, - { - name: 'mode', - type: String, - description: 'Defines the mode to pass to webpack.', - }, - { - name: 'name', - type: String, - description: 'Name of the configuration. Used when loading multiple configurations.', - }, - { - name: 'stats', - type: [String, Boolean], - negative: true, - description: 'It instructs webpack on how to treat the stats e.g. verbose.', - negatedDescription: 'Disable stats output.', - }, - { - name: 'watch', - type: Boolean, - negative: true, - alias: 'w', - description: 'Watch for files changes.', - negatedDescription: 'Do not watch for file changes.', - }, - { - name: 'watch-options-stdin', - type: Boolean, - negative: true, - description: 'Stop watching when stdin stream has ended.', - negatedDescription: 'Do not stop watching when stdin stream has ended.', - }, -]; - -// Extract all the flags being exported from core. -// A list of cli flags generated by core can be found here https://github.com/webpack/webpack/blob/master/test/__snapshots__/Cli.test.js.snap -const coreFlags = cli - ? Object.entries(cli.getArguments()).map(([flag, meta]) => { - if (meta.simpleType === 'string') { - meta.type = String; - } else if (meta.simpleType === 'number') { - meta.type = Number; - } else { - meta.type = Boolean; - meta.negative = !flag.endsWith('-reset'); - } - - const inBuiltIn = builtInFlags.find((builtInFlag) => builtInFlag.name === flag); - - if (inBuiltIn) { - return { ...meta, name: flag, group: 'core', ...inBuiltIn }; - } - - return { ...meta, name: flag, group: 'core' }; - }) - : []; -const flags = [] - .concat(builtInFlags.filter((builtInFlag) => !coreFlags.find((coreFlag) => builtInFlag.name === coreFlag.name))) - .concat(coreFlags) - .map((option) => { - option.help = minimumHelpFlags.includes(option.name) ? 'minimum' : 'verbose'; - - return option; - }); - -module.exports = { cli, flags }; diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index aac07d43a46..b945f9b189f 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -1,17 +1,16 @@ +const fs = require('fs'); +const path = require('path'); + const { program } = require('commander'); const getPkg = require('./utils/package-exists'); const webpack = getPkg('webpack') ? require('webpack') : undefined; -const path = require('path'); -const { merge } = require('webpack-merge'); -const { extensions, jsVariants } = require('interpret'); +const interpret = require('interpret'); const rechoir = require('rechoir'); -const { createWriteStream, existsSync } = require('fs'); const { distance } = require('fastest-levenshtein'); const { options: coloretteOptions, yellow, cyan, green, bold } = require('colorette'); const logger = require('./utils/logger'); const capitalizeFirstLetter = require('./utils/capitalize-first-letter'); -const { cli, flags } = require('./utils/cli-flags'); const CLIPlugin = require('./plugins/CLIPlugin'); const promptInstallation = require('./utils/prompt-installation'); const toKebabCase = require('./utils/to-kebab-case'); @@ -85,7 +84,9 @@ class WebpackCLI { try { await promptInstallation(dependency, () => { logger.error( - `For using '${green(commandOptions.name)}' command you need to install: '${green(dependency)}' package`, + `For using '${green(commandOptions.name.split(' ')[0])}' command you need to install: '${green( + dependency, + )}' package`, ); }); } catch (error) { @@ -226,22 +227,219 @@ class WebpackCLI { } getBuiltInOptions() { - return flags; + const minimumHelpFlags = [ + 'config', + 'config-name', + 'merge', + 'env', + 'mode', + 'watch', + 'watch-options-stdin', + 'stats', + 'devtool', + 'entry', + 'target', + 'progress', + 'json', + 'name', + 'output-path', + ]; + + const builtInFlags = [ + // For configs + { + name: 'config', + alias: 'c', + type: String, + multiple: true, + description: 'Provide path to a webpack configuration file e.g. ./webpack.config.js.', + }, + { + name: 'config-name', + type: String, + multiple: true, + description: 'Name of the configuration to use.', + }, + { + name: 'merge', + alias: 'm', + type: Boolean, + description: "Merge two or more configurations using 'webpack-merge'.", + }, + // Complex configs + { + name: 'env', + type: (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 (typeof prevRef[someKey] === 'string') { + prevRef[someKey] = {}; + } + + if (index === splitKeys.length - 1) { + prevRef[someKey] = val || true; + } + + prevRef = prevRef[someKey]; + }); + + return previous; + }, + multiple: true, + description: 'Environment passed to the configuration when it is a function.', + }, + + // Adding more plugins + { + name: 'hot', + alias: 'h', + type: Boolean, + negative: true, + description: 'Enables Hot Module Replacement', + negatedDescription: 'Disables Hot Module Replacement.', + }, + { + name: 'analyze', + type: Boolean, + multiple: false, + description: 'It invokes webpack-bundle-analyzer plugin to get bundle information.', + }, + { + name: 'progress', + type: [Boolean, String], + description: 'Print compilation progress during build.', + }, + { + name: 'prefetch', + type: String, + description: 'Prefetch this request.', + }, + + // Output options + { + name: 'json', + type: [String, Boolean], + alias: 'j', + description: 'Prints result as JSON or store it in a file.', + }, + + // For webpack@4 + { + name: 'entry', + type: String, + multiple: true, + description: 'The entry point(s) of your application e.g. ./src/main.js.', + }, + { + name: 'output-path', + alias: 'o', + type: String, + description: 'Output location of the file generated by webpack e.g. ./dist/.', + }, + { + name: 'target', + alias: 't', + type: String, + multiple: this.webpack.cli !== undefined, + description: 'Sets the build target e.g. node.', + }, + { + name: 'devtool', + type: String, + negative: true, + alias: 'd', + description: 'Determine source maps to use.', + negatedDescription: 'Do not generate source maps.', + }, + { + name: 'mode', + type: String, + description: 'Defines the mode to pass to webpack.', + }, + { + name: 'name', + type: String, + description: 'Name of the configuration. Used when loading multiple configurations.', + }, + { + name: 'stats', + type: [String, Boolean], + negative: true, + description: 'It instructs webpack on how to treat the stats e.g. verbose.', + negatedDescription: 'Disable stats output.', + }, + { + name: 'watch', + type: Boolean, + negative: true, + alias: 'w', + description: 'Watch for files changes.', + negatedDescription: 'Do not watch for file changes.', + }, + { + name: 'watch-options-stdin', + type: Boolean, + negative: true, + description: 'Stop watching when stdin stream has ended.', + negatedDescription: 'Do not stop watching when stdin stream has ended.', + }, + ]; + + // Extract all the flags being exported from core. + // A list of cli flags generated by core can be found here https://github.com/webpack/webpack/blob/master/test/__snapshots__/Cli.test.js.snap + const coreFlags = this.webpack.cli + ? Object.entries(this.webpack.cli.getArguments()).map(([flag, meta]) => { + if (meta.simpleType === 'string') { + meta.type = String; + } else if (meta.simpleType === 'number') { + meta.type = Number; + } else { + meta.type = Boolean; + meta.negative = !flag.endsWith('-reset'); + } + + const inBuiltIn = builtInFlags.find((builtInFlag) => builtInFlag.name === flag); + + if (inBuiltIn) { + return { ...meta, name: flag, group: 'core', ...inBuiltIn }; + } + + return { ...meta, name: flag, group: 'core' }; + }) + : []; + + return [] + .concat(builtInFlags.filter((builtInFlag) => !coreFlags.find((coreFlag) => builtInFlag.name === coreFlag.name))) + .concat(coreFlags) + .map((option) => { + option.help = minimumHelpFlags.includes(option.name) ? 'minimum' : 'verbose'; + + return option; + }); } async run(args, parseOptions) { // Built-in internal commands const buildCommandOptions = { - name: 'build', + name: 'build [entries...]', alias: ['bundle', 'b'], description: 'Run webpack (default command, can be omitted).', - usage: '[options]', + usage: '[entries...] [options]', }; const watchCommandOptions = { - name: 'watch', + name: 'watch [entries...]', alias: 'w', description: 'Run webpack and watch for files changes.', - usage: '[options]', + usage: '[entries...] [options]', }; const versionCommandOptions = { name: 'version [commands...]', @@ -256,7 +454,7 @@ class WebpackCLI { // Built-in external commands const externalBuiltInCommandsInfo = [ { - name: 'serve', + name: 'serve [entries...]', alias: 's', pkg: '@webpack-cli/serve', }, @@ -306,20 +504,23 @@ class WebpackCLI { getCommandName(command.name) === name || (Array.isArray(command.alias) ? command.alias.includes(name) : command.alias === name), ); - const isBuildCommand = (name) => - getCommandName(buildCommandOptions.name) === name || - (Array.isArray(buildCommandOptions.alias) ? buildCommandOptions.alias.includes(name) : buildCommandOptions.alias === name); - const isWatchCommand = (name) => - getCommandName(watchCommandOptions.name) === name || - (Array.isArray(watchCommandOptions.alias) ? watchCommandOptions.alias.includes(name) : watchCommandOptions.alias === name); - const isHelpCommand = (name) => - getCommandName(helpCommandOptions.name) === name || - (Array.isArray(helpCommandOptions.alias) ? helpCommandOptions.alias.includes(name) : helpCommandOptions.alias === name); - const isVersionCommand = (name) => - getCommandName(versionCommandOptions.name) === name || - (Array.isArray(versionCommandOptions.alias) - ? versionCommandOptions.alias.includes(name) - : versionCommandOptions.alias === name); + const isCommand = (input, commandOptions) => { + const longName = getCommandName(commandOptions.name); + + if (input === longName) { + return true; + } + + if (commandOptions.alias) { + if (Array.isArray(commandOptions.alias)) { + return commandOptions.alias.includes(input); + } else { + return commandOptions.alias === input; + } + } + + return false; + }; const findCommandByName = (name) => this.program.commands.find((command) => name === command.name() || command.alias().includes(name)); const isOption = (value) => value.startsWith('-'); @@ -332,21 +533,16 @@ class WebpackCLI { value === '--help'; const loadCommandByName = async (commandName, allowToInstall = false) => { - const isBuildCommandUsed = isBuildCommand(commandName); - const isWatchCommandUsed = isWatchCommand(commandName); + const isBuildCommandUsed = isCommand(commandName, buildCommandOptions); + const isWatchCommandUsed = isCommand(commandName, watchCommandOptions); if (isBuildCommandUsed || isWatchCommandUsed) { await this.makeCommand( isBuildCommandUsed ? buildCommandOptions : watchCommandOptions, this.getBuiltInOptions(), - async (options, program) => { - if (program.args.length > 0) { - const possibleCommands = [].concat([buildCommandOptions.name]).concat(program.args); - - logger.error('Running multiple commands at the same time is not possible'); - logger.error(`Found commands: ${possibleCommands.map((item) => `'${item}'`).join(', ')}`); - logger.error("Run 'webpack --help' to see available commands and options"); - process.exit(2); + async (entries, options) => { + if (entries.length > 0) { + options.entry = [...entries, ...(options.entry || [])]; } if (isWatchCommandUsed) { @@ -361,13 +557,13 @@ class WebpackCLI { options.watch = true; } - await this.bundleCommand(options); + await this.buildCommand(options); }, ); - } else if (isHelpCommand(commandName)) { + } else if (isCommand(commandName, helpCommandOptions)) { // Stub for the `help` command this.makeCommand(helpCommandOptions, [], () => {}); - } else if (isVersionCommand(commandName)) { + } else if (isCommand(commandName, versionCommandOptions)) { // Stub for the `help` command this.makeCommand(versionCommandOptions, [], () => {}); } else { @@ -455,7 +651,7 @@ class WebpackCLI { } const { operands } = this.program.parseOptions(this.program.args); - const operand = typeof operands[0] !== 'undefined' ? operands[0] : 'build'; + const operand = typeof operands[0] !== 'undefined' ? operands[0] : getCommandName(buildCommandOptions.name); if (operand) { const command = findCommandByName(operand); @@ -506,7 +702,11 @@ class WebpackCLI { const outputVersion = async (options) => { // Filter `bundle`, `watch`, `version` and `help` commands const possibleCommandNames = options.filter( - (option) => !isBuildCommand(option) && !isWatchCommand(option) && !isVersionCommand(option) && !isHelpCommand(option), + (option) => + !isCommand(option, buildCommandOptions) && + !isCommand(option, watchCommandOptions) && + !isCommand(option, versionCommandOptions) && + !isCommand(option, helpCommandOptions), ); possibleCommandNames.forEach((possibleCommandName) => { @@ -544,7 +744,7 @@ class WebpackCLI { const pkgJSON = require('../package.json'); - logger.raw(`webpack ${webpack.version}`); + logger.raw(`webpack ${this.webpack.version}`); logger.raw(`webpack-cli ${pkgJSON.version}`); if (getPkg('webpack-dev-server')) { @@ -562,46 +762,6 @@ class WebpackCLI { ); const outputHelp = async (options, isVerbose, isHelpCommandSyntax, program) => { - const hideVerboseOptions = (command) => { - command.options = command.options.filter((option) => { - const foundOption = flags.find((flag) => { - if (option.negate && flag.negative) { - return `no-${flag.name}` === option.name(); - } - - return flag.name === option.name(); - }); - - if (foundOption && foundOption.help) { - return foundOption.help === 'minimum'; - } - - return true; - }); - }; - const outputGlobalOptions = () => { - const programHelpInformation = program.helpInformation(); - const globalOptions = programHelpInformation.match(/Options:\n(?.+)\nCommands:\n/s); - - if (globalOptions && globalOptions.groups.globalOptions) { - logger.raw('\nGlobal options:'); - logger.raw(globalOptions.groups.globalOptions.trimRight()); - } - }; - const outputGlobalCommands = () => { - const programHelpInformation = program.helpInformation(); - const globalCommands = programHelpInformation.match(/Commands:\n(?.+)/s); - - if (globalCommands.groups.globalCommands) { - logger.raw('\nCommands:'); - logger.raw( - globalCommands.groups.globalCommands - .trimRight() - // `commander` doesn't support multiple alias in help - .replace('build|bundle [options] ', 'build|bundle|b [options]'), - ); - } - }; const outputIncorrectUsageOfHelp = () => { logger.error('Incorrect use of help'); logger.error("Please use: 'webpack help [command] [option]' | 'webpack [command] --help'"); @@ -609,61 +769,177 @@ class WebpackCLI { process.exit(2); }; - if (options.length === 0) { - await Promise.all( - knownCommands.map((knownCommand) => { - return loadCommandByName(getCommandName(knownCommand.name)); - }), - ); + const isGlobalHelp = options.length === 0; + const isCommandHelp = options.length === 1 && !isOption(options[0]); - const bundleCommand = findCommandByName(buildCommandOptions.name); + if (isGlobalHelp || isCommandHelp) { + const cliAPI = this; - if (!isVerbose) { - hideVerboseOptions(bundleCommand); - } + program.configureHelp({ + sortSubcommands: true, + // Support multiple aliases + commandUsage: (command) => { + let parentCmdNames = ''; - let helpInformation = bundleCommand - .helpInformation() - .trimRight() - .replace(buildCommandOptions.description, 'The build tool for modern web applications.') - .replace( - /Usage:.+/, - 'Usage: webpack [options]\nAlternative usage: webpack --config [options]\nAlternative usage: webpack build [options]\nAlternative usage: webpack bundle [options]\nAlternative usage: webpack b [options]\nAlternative usage: webpack build --config [options]\nAlternative usage: webpack bundle --config [options]\nAlternative usage: webpack b --config [options]', - ); + for (let parentCmd = command.parent; parentCmd; parentCmd = parentCmd.parent) { + parentCmdNames = `${parentCmd.name()} ${parentCmdNames}`; + } - logger.raw(helpInformation); + if (isGlobalHelp) { + return `${parentCmdNames}${command.usage()}\n${bold( + 'Alternative usage to run commands:', + )} ${parentCmdNames}[command] [options]`; + } - outputGlobalOptions(); - outputGlobalCommands(); - } else if (options.length === 1 && !isOption(options[0])) { - const name = options[0]; + return `${parentCmdNames}${command.name()}|${command.aliases().join('|')} ${command.usage()}`; + }, + // Support multiple aliases + subcommandTerm: (command) => { + const humanReadableArgumentName = (argument) => { + const nameOutput = argument.name + (argument.variadic === true ? '...' : ''); + + return argument.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']'; + }; + const args = command._args.map((arg) => humanReadableArgumentName(arg)).join(' '); + + return `${command.name()}|${command.aliases().join('|')}${args ? ` ${args}` : ''}${ + command.options.length > 0 ? ' [options]' : '' + }`; + }, + visibleOptions: function visibleOptions(command) { + const options = cliAPI.getBuiltInOptions(); - await loadCommandByName(name); + return command.options.filter((option) => { + if (option.hidden) { + return false; + } - const command = findCommandByName(name); + if (!isVerbose) { + const foundOption = options.find((flag) => { + if (option.negate && flag.negative) { + return `no-${flag.name}` === option.name(); + } - if (!command) { - logger.error(`Can't find and load command '${name}'`); - logger.error("Run 'webpack --help' to see available commands and options"); - process.exit(2); - } + return flag.name === option.name(); + }); - if (!isVerbose) { - hideVerboseOptions(command); - } + if (foundOption) { + return foundOption.help === 'minimum'; + } - let helpInformation = command.helpInformation().trimRight(); + return true; + } - if (isBuildCommand(name)) { - helpInformation = helpInformation.replace('build|bundle', 'build|bundle|b'); - } + return true; + }); + }, + padWidth(command, helper) { + return Math.max( + helper.longestArgumentTermLength(command, helper), + helper.longestOptionTermLength(command, helper), + // For global options + helper.longestOptionTermLength(program, helper), + helper.longestSubcommandTermLength(isGlobalHelp ? program : command, helper), + ); + }, + formatHelp: (command, helper) => { + const termWidth = helper.padWidth(command, helper); + const helpWidth = helper.helpWidth || 80; + const itemIndentWidth = 2; + const itemSeparatorWidth = 2; // between term and description + + const formatItem = (term, description) => { + if (description) { + const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`; + + return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth); + } + + return term; + }; + + const formatList = (textArray) => textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth)); + + // Usage + let output = [`${bold('Usage:')} ${helper.commandUsage(command)}`, '']; + + // Description + const commandDescription = isGlobalHelp + ? 'The build tool for modern web applications.' + : helper.commandDescription(command); + + if (commandDescription.length > 0) { + output = output.concat([commandDescription, '']); + } + + // Arguments + const argumentList = helper + .visibleArguments(command) + .map((argument) => formatItem(argument.term, argument.description)); + + if (argumentList.length > 0) { + output = output.concat([bold('Arguments:'), formatList(argumentList), '']); + } + + // Options + const optionList = helper + .visibleOptions(command) + .map((option) => formatItem(helper.optionTerm(option), helper.optionDescription(option))); + + if (optionList.length > 0) { + output = output.concat([bold('Options:'), formatList(optionList), '']); + } + + // Global options + const globalOptionList = program.options.map((option) => + formatItem(helper.optionTerm(option), helper.optionDescription(option)), + ); + + if (globalOptionList.length > 0) { + output = output.concat([bold('Global options:'), formatList(globalOptionList), '']); + } + + // Commands + const commandList = helper + .visibleCommands(isGlobalHelp ? program : command) + .map((command) => formatItem(helper.subcommandTerm(command), helper.subcommandDescription(command))); + + if (commandList.length > 0) { + output = output.concat([bold('Commands:'), formatList(commandList), '']); + } + + return output.join('\n'); + }, + }); + + if (isGlobalHelp) { + await Promise.all( + knownCommands.map((knownCommand) => { + return loadCommandByName(getCommandName(knownCommand.name)); + }), + ); + + const buildCommand = findCommandByName(getCommandName(buildCommandOptions.name)); + + logger.raw(buildCommand.helpInformation()); + } else { + const name = options[0]; + + await loadCommandByName(name); - logger.raw(helpInformation); + const command = findCommandByName(name); - outputGlobalOptions(); + if (!command) { + logger.error(`Can't find and load command '${name}'`); + logger.error("Run 'webpack --help' to see available commands and options"); + process.exit(2); + } + + logger.raw(command.helpInformation()); + } } else if (isHelpCommandSyntax) { let isCommandSpecified = false; - let commandName = buildCommandOptions.name; + let commandName = getCommandName(buildCommandOptions.name); let optionName; if (options.length === 1) { @@ -682,7 +958,7 @@ class WebpackCLI { await loadCommandByName(commandName); - const command = isGlobalOption(optionName) ? this.program : findCommandByName(commandName); + const command = isGlobalOption(optionName) ? program : findCommandByName(commandName); if (!command) { logger.error(`Can't find and load command '${commandName}'`); @@ -702,20 +978,28 @@ class WebpackCLI { option.flags.replace(/^.+[[<]/, '').replace(/(\.\.\.)?[\]>].*$/, '') + (option.variadic === true ? '...' : ''); const value = option.required ? '<' + nameOutput + '>' : option.optional ? '[' + nameOutput + ']' : ''; - logger.raw(`Usage: webpack${isCommandSpecified ? ` ${commandName}` : ''} ${option.long}${value ? ` ${value}` : ''}`); + logger.raw( + `${bold('Usage')}: webpack${isCommandSpecified ? ` ${commandName}` : ''} ${option.long}${value ? ` ${value}` : ''}`, + ); if (option.short) { - logger.raw(`Short: webpack${isCommandSpecified ? ` ${commandName}` : ''} ${option.short}${value ? ` ${value}` : ''}`); + logger.raw( + `${bold('Short:')} webpack${isCommandSpecified ? ` ${commandName}` : ''} ${option.short}${ + value ? ` ${value}` : '' + }`, + ); } if (option.description) { - logger.raw(`Description: ${option.description}`); + logger.raw(`${bold('Description:')} ${option.description}`); } if (!option.negate && options.defaultValue) { - logger.raw(`Default value: ${JSON.stringify(option.defaultValue)}`); + logger.raw(`${bold('Default value:')} ${JSON.stringify(option.defaultValue)}`); } + logger.raw(''); + // TODO implement this after refactor cli arguments // logger.raw('Possible values: foo | bar'); // logger.raw('Documentation: https://webpack.js.org/option/name/'); @@ -723,9 +1007,9 @@ class WebpackCLI { outputIncorrectUsageOfHelp(); } - logger.raw("\nTo see list of all supported commands and options run 'webpack --help=verbose'.\n"); - logger.raw('Webpack documentation: https://webpack.js.org/.'); - logger.raw('CLI documentation: https://webpack.js.org/api/cli/.'); + logger.raw("To see list of all supported commands and options run 'webpack --help=verbose'.\n"); + logger.raw(`${bold('Webpack documentation:')} https://webpack.js.org/.`); + logger.raw(`${bold('CLI documentation:')} https://webpack.js.org/api/cli/.`); logger.raw(`${bold('Made with ♥ by the webpack team')}.`); process.exit(0); }; @@ -748,10 +1032,11 @@ class WebpackCLI { // Command and options const { operands, unknown } = this.program.parseOptions(program.args); + const defaultCommandToRun = getCommandName(buildCommandOptions.name); const hasOperand = typeof operands[0] !== 'undefined'; - const operand = hasOperand ? operands[0] : 'build'; + const operand = hasOperand ? operands[0] : defaultCommandToRun; - const isHelpCommandSyntax = isHelpCommand(operand); + const isHelpCommandSyntax = isCommand(operand, helpCommandOptions); if (options.help || isHelpCommandSyntax) { let isVerbose = false; @@ -781,7 +1066,7 @@ class WebpackCLI { await outputHelp(optionsForHelp, isVerbose, isHelpCommandSyntax, program); } - if (options.version || isVersionCommand(operand)) { + if (options.version || isCommand(operand, versionCommandOptions)) { const optionsForVersion = [] .concat(options.version ? [operand] : []) .concat(operands.slice(1)) @@ -790,26 +1075,44 @@ class WebpackCLI { await outputVersion(optionsForVersion, program); } - if (isKnownCommand(operand)) { - await loadCommandByName(operand, true); - } else { - logger.error(`Unknown command '${operand}'`); + let commandToRun = operand; + let commandOperands = operands.slice(1); - const found = knownCommands.find((commandOptions) => distance(operand, getCommandName(commandOptions.name)) < 3); + if (isKnownCommand(commandToRun)) { + await loadCommandByName(commandToRun, true); + } else { + let isEntrySyntax = true; - if (found) { - logger.error( - `Did you mean '${getCommandName(found.name)}' (alias '${ - Array.isArray(found.alias) ? found.alias.join(', ') : found.alias - }')?`, - ); + try { + await fs.promises.access(operand, fs.constants.F_OK); + } catch (error) { + isEntrySyntax = false; } - logger.error("Run 'webpack --help' to see available commands and options"); - process.exit(2); + if (isEntrySyntax) { + commandToRun = defaultCommandToRun; + commandOperands = operands; + + await loadCommandByName(commandToRun); + } else { + logger.error(`Unknown command or entry '${operand}'`); + + const found = knownCommands.find((commandOptions) => distance(operand, getCommandName(commandOptions.name)) < 3); + + if (found) { + logger.error( + `Did you mean '${getCommandName(found.name)}' (alias '${ + Array.isArray(found.alias) ? found.alias.join(', ') : found.alias + }')?`, + ); + } + + logger.error("Run 'webpack --help' to see available commands and options"); + process.exit(2); + } } - await this.program.parseAsync([operand, ...operands.slice(1), ...unknown], { from: 'user' }); + await this.program.parseAsync([commandToRun, ...commandOperands, ...unknown], { from: 'user' }); }); await this.program.parseAsync(args, parseOptions); @@ -818,11 +1121,11 @@ class WebpackCLI { async resolveConfig(options) { const loadConfig = async (configPath) => { const ext = path.extname(configPath); - const interpreted = Object.keys(jsVariants).find((variant) => variant === ext); + const interpreted = Object.keys(interpret.jsVariants).find((variant) => variant === ext); if (interpreted) { try { - rechoir.prepare(extensions, configPath); + rechoir.prepare(interpret.extensions, configPath); } catch (error) { if (error.failures) { logger.error(`Unable load '${configPath}'`); @@ -868,7 +1171,7 @@ class WebpackCLI { throw error; } } catch (error) { - logger.error(`Failed to load '${configPath}'`); + logger.error(`Failed to load '${configPath}' config`); logger.error(error); process.exit(2); } @@ -916,18 +1219,7 @@ class WebpackCLI { if (options.config && options.config.length > 0) { const evaluatedConfigs = await Promise.all( - options.config.map(async (value) => { - const configPath = path.resolve(value); - - if (!existsSync(configPath)) { - logger.error(`The specified config file doesn't exist in '${configPath}'`); - process.exit(2); - } - - const loadedConfig = await loadConfig(configPath); - - return evaluateConfig(loadedConfig, options.argv || {}); - }), + options.config.map(async (value) => evaluateConfig(await loadConfig(path.resolve(value)), options.argv || {})), ); config.options = []; @@ -950,10 +1242,10 @@ class WebpackCLI { const defaultConfigFiles = ['webpack.config', '.webpack/webpack.config', '.webpack/webpackfile'] .map((filename) => // Since .cjs is not available on interpret side add it manually to default config extension list - [...Object.keys(extensions), '.cjs'].map((ext) => ({ + [...Object.keys(interpret.extensions), '.cjs'].map((ext) => ({ path: path.resolve(filename + ext), ext: ext, - module: extensions[ext], + module: interpret.extensions[ext], })), ) .reduce((accumulator, currentValue) => accumulator.concat(currentValue), []); @@ -961,10 +1253,14 @@ class WebpackCLI { let foundDefaultConfigFile; for (const defaultConfigFile of defaultConfigFiles) { - if (existsSync(defaultConfigFile.path)) { - foundDefaultConfigFile = defaultConfigFile; - break; + try { + await fs.promises.access(defaultConfigFile.path, fs.constants.F_OK); + } catch (error) { + continue; } + + foundDefaultConfigFile = defaultConfigFile; + break; } if (foundDefaultConfigFile) { @@ -1011,6 +1307,8 @@ class WebpackCLI { } if (options.merge) { + const { merge } = require('webpack-merge'); + // we can only merge when there are multiple configurations // either by passing multiple configs by flags or passing a // single config exporting an array @@ -1057,34 +1355,33 @@ class WebpackCLI { process.exit(2); } - if (Object.keys(options).length === 0 && !process.env.NODE_ENV) { - return config; - } - - if (cli) { + if (this.webpack.cli) { const processArguments = (configOptions) => { - const coreFlagMap = flags + const args = this.getBuiltInOptions() .filter((flag) => flag.group === 'core') .reduce((accumulator, flag) => { accumulator[flag.name] = flag; return accumulator; }, {}); - const CLIoptions = Object.keys(options).reduce((accumulator, name) => { + + const values = Object.keys(options).reduce((accumulator, name) => { + if (name === 'argv') { + return accumulator; + } + const kebabName = toKebabCase(name); - if (coreFlagMap[kebabName]) { + if (args[kebabName]) { accumulator[kebabName] = options[name]; } return accumulator; }, {}); - const problems = cli.processArguments(coreFlagMap, configOptions, CLIoptions); + + const problems = this.webpack.cli.processArguments(args, configOptions, values); if (problems) { - const capitalizeFirstLetter = (string) => { - return string.charAt(0).toUpperCase() + string.slice(1); - }; const groupBy = (xs, key) => { return xs.reduce((rv, x) => { (rv[x[key]] = rv[x[key]] || []).push(x); @@ -1214,7 +1511,7 @@ class WebpackCLI { // Apply `stats` and `stats.colors` options const applyStatsColors = (configOptions) => { // TODO remove after drop webpack@4 - const statsForWebpack4 = webpack.Stats && webpack.Stats.presetToOptions; + const statsForWebpack4 = this.webpack.Stats && this.webpack.Stats.presetToOptions; if (statsForWebpack4) { if (typeof configOptions.stats === 'undefined') { @@ -1232,7 +1529,7 @@ class WebpackCLI { return configOptions; } - configOptions.stats = webpack.Stats.presetToOptions(configOptions.stats); + configOptions.stats = this.webpack.Stats.presetToOptions(configOptions.stats); } } else { if (typeof configOptions.stats === 'undefined') { @@ -1309,7 +1606,7 @@ class WebpackCLI { const isValidationError = (error) => { // https://github.com/webpack/webpack/blob/master/lib/index.js#L267 // https://github.com/webpack/webpack/blob/v4.44.2/lib/webpack.js#L90 - const ValidationError = webpack.ValidationError || webpack.WebpackOptionsValidationError; + const ValidationError = this.webpack.ValidationError || this.webpack.WebpackOptionsValidationError; return error instanceof ValidationError; }; @@ -1322,7 +1619,7 @@ class WebpackCLI { let compiler; try { - compiler = webpack( + compiler = this.webpack( config.options, callback ? (error, stats) => { @@ -1353,7 +1650,7 @@ class WebpackCLI { return compiler; } - async bundleCommand(options) { + async buildCommand(options) { let compiler; const callback = (error, stats) => { @@ -1377,7 +1674,7 @@ class WebpackCLI { : undefined; // TODO webpack@4 doesn't support `{ children: [{ colors: true }, { colors: true }] }` for stats - const statsForWebpack4 = webpack.Stats && webpack.Stats.presetToOptions; + const statsForWebpack4 = this.webpack.Stats && this.webpack.Stats.presetToOptions; if (compiler.compilers && statsForWebpack4) { statsOptions.colors = statsOptions.children.some((child) => child.colors); @@ -1399,7 +1696,7 @@ class WebpackCLI { } else { createJsonStringifyStream(stats.toJson(statsOptions)) .on('error', handleWriteError) - .pipe(createWriteStream(options.json)) + .pipe(fs.createWriteStream(options.json)) .on('error', handleWriteError) // Use stderr to logging .on('close', () => diff --git a/test/build/basic/basic.test.js b/test/build/basic/basic.test.js index a758c3b523c..01481187f45 100644 --- a/test/build/basic/basic.test.js +++ b/test/build/basic/basic.test.js @@ -3,7 +3,63 @@ const { run } = require('../../utils/test-utils'); describe('bundle command', () => { - it('should work', async () => { + it('should work without command (default command)', async () => { + const { exitCode, stderr, stdout } = run(__dirname, [], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + }); + + it('should work without command and options (default command)', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['--mode', 'development'], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + }); + + it('should work with multiple entries syntax without command (default command)', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['./src/index.js', './src/other.js'], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + }); + + it('should work with multiple entries syntax without command with options (default command)', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['./src/index.js', './src/other.js', '--mode', 'development'], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + }); + + it('should work with multiple entries syntax without command with options #2 (default command)', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['--mode', 'development', './src/index.js', './src/other.js'], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + }); + + it('should work with multiple entries syntax without command with options #3 (default command)', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['./src/index.js', './src/other.js', '--entry', './src/again.js'], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + }); + + it('should work with and override entries from the configuration', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['./src/index.js', './src/other.js', '--config', './entry.config.js'], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + }); + + it('should work with the "build" alias', async () => { const { exitCode, stderr, stdout } = run(__dirname, ['build'], false); expect(exitCode).toBe(0); @@ -11,6 +67,14 @@ describe('bundle command', () => { expect(stdout).toBeTruthy(); }); + it('should work with "bundle" alias', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['bundle'], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + }); + it('should work with the "b" alias', async () => { const { exitCode, stderr, stdout } = run(__dirname, ['b'], false); @@ -19,40 +83,60 @@ describe('bundle command', () => { expect(stdout).toBeTruthy(); }); - it('should work with "bundle" alias', async () => { - const { exitCode, stderr, stdout } = run(__dirname, ['bundle'], false); + it('should work with entries syntax using the "build" alias', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['build', './src/index.js'], false); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); expect(stdout).toBeTruthy(); }); - it('should log error and suggest right name on the "buil" command', async () => { - const { exitCode, stderr, stdout } = run(__dirname, ['buil'], false); + it('should work with entries syntax using the "bundle" alias', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['bundle', './src/index.js'], false); - expect(exitCode).toBe(2); - expect(stderr).toContain("Unknown command 'buil'"); - expect(stderr).toContain("Did you mean 'build' (alias 'bundle, b')?"); - expect(stderr).toContain("Run 'webpack --help' to see available commands and options"); - expect(stdout).toBeFalsy(); + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); }); - it('should log error with multi commands', async () => { - const { exitCode, stderr, stdout } = run(__dirname, ['bundle', 'info'], false); + it('should work with entries syntax using the "b" alias', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['b', './src/index.js'], false); - expect(exitCode).toBe(2); - expect(stderr).toContain('Running multiple commands at the same time is not possible'); - expect(stderr).toContain("Found commands: 'build', 'info'"); - expect(stderr).toContain("Run 'webpack --help' to see available commands and options"); - expect(stdout).toBeFalsy(); + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + }); + + it('should work with multiple entries syntax using the "build" alias', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['build', './src/index.js', './src/other.js'], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + }); + + it('should work with multiple entries syntax using the "build" alias and options', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['build', './src/index.js', './src/other.js', '--mode', 'development'], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); }); - it('should log error with multi commands', async () => { - const { exitCode, stderr, stdout } = run(__dirname, ['b', 'i'], false); + it('should work with multiple entries syntax using the "build" alias and options', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['build', '--mode', 'development', './src/index.js', './src/other.js'], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); + }); + + it('should log error and suggest right name on the "buil" command', async () => { + const { exitCode, stderr, stdout } = run(__dirname, ['buil'], false); expect(exitCode).toBe(2); - expect(stderr).toContain('Running multiple commands at the same time is not possible'); - expect(stderr).toContain("Found commands: 'build', 'i'"); + expect(stderr).toContain("Unknown command or entry 'buil'"); + expect(stderr).toContain("Did you mean 'build' (alias 'bundle, b')?"); expect(stderr).toContain("Run 'webpack --help' to see available commands and options"); expect(stdout).toBeFalsy(); }); diff --git a/test/build/basic/entry.config.js b/test/build/basic/entry.config.js new file mode 100644 index 00000000000..431945f5225 --- /dev/null +++ b/test/build/basic/entry.config.js @@ -0,0 +1,4 @@ +module.exports = { + mode: 'development', + entry: './src/entry.js', +}; diff --git a/test/build/basic/src/again.js b/test/build/basic/src/again.js new file mode 100644 index 00000000000..eec159b09dc --- /dev/null +++ b/test/build/basic/src/again.js @@ -0,0 +1 @@ +console.log('again'); diff --git a/test/build/basic/src/entry.js b/test/build/basic/src/entry.js new file mode 100644 index 00000000000..6daf0ddddd1 --- /dev/null +++ b/test/build/basic/src/entry.js @@ -0,0 +1 @@ +console.log('CONFIG'); diff --git a/test/build/basic/src/other.js b/test/build/basic/src/other.js new file mode 100644 index 00000000000..6be02374db1 --- /dev/null +++ b/test/build/basic/src/other.js @@ -0,0 +1 @@ +console.log('hello world'); diff --git a/test/config/absent/config-absent.test.js b/test/config/absent/config-absent.test.js index 2a10305273c..1e22b4a67a0 100644 --- a/test/config/absent/config-absent.test.js +++ b/test/config/absent/config-absent.test.js @@ -1,16 +1,16 @@ 'use strict'; -const { resolve } = require('path'); +const path = require('path'); const { run } = require('../../utils/test-utils'); describe('Config:', () => { it('supplied config file is absent', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['-c', resolve(__dirname, 'webpack.config.js')], false); + const { exitCode, stderr, stdout } = run(__dirname, ['-c', path.resolve(__dirname, 'webpack.config.js')], false); // should throw with correct exit code expect(exitCode).toBe(2); // Should contain the correct error message - expect(stderr).toContain(`The specified config file doesn't exist in '${resolve(__dirname, 'webpack.config.js')}'`); + expect(stderr).toContain(`Failed to load '${path.resolve(__dirname, 'webpack.config.js')}' config`); expect(stdout).toBeFalsy(); }); }); diff --git a/test/config/invalid-path/invalid-path.test.js b/test/config/invalid-path/invalid-path.test.js index b571740a71f..fe94a8826d9 100644 --- a/test/config/invalid-path/invalid-path.test.js +++ b/test/config/invalid-path/invalid-path.test.js @@ -1,13 +1,13 @@ 'use strict'; -const { resolve } = require('path'); +const path = require('path'); const { run } = require('../../utils/test-utils'); describe('basic config file', () => { it('is able to understand and parse a very basic configuration file', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['-c', resolve(__dirname, 'invalid-webpack.config.js')], false); + const { exitCode, stderr, stdout } = run(__dirname, ['-c', path.resolve(__dirname, 'invalid-webpack.config.js')], false); expect(exitCode).toBe(2); - expect(stderr).toContain(`The specified config file doesn't exist in '${resolve(__dirname, 'invalid-webpack.config.js')}'`); + expect(stderr).toContain(`Failed to load '${path.resolve(__dirname, 'invalid-webpack.config.js')}' config`); expect(stdout).toBeFalsy(); }); }); diff --git a/test/configtest/with-config-path/with-config-path.test.js b/test/configtest/with-config-path/with-config-path.test.js index 2e4775868c0..ca2ae90524a 100644 --- a/test/configtest/with-config-path/with-config-path.test.js +++ b/test/configtest/with-config-path/with-config-path.test.js @@ -44,7 +44,7 @@ describe("'configtest' command with the configuration path option", () => { const { exitCode, stderr, stdout } = run(__dirname, ['configtest', './a.js'], false); expect(exitCode).toBe(2); - expect(stderr).toContain(`The specified config file doesn't exist`); + expect(stderr).toContain(`Failed to load '${path.resolve(__dirname, './a.js')}' config`); expect(stdout).toBeFalsy(); }); }); diff --git a/test/core-flags/experiments-flag.test.js b/test/core-flags/experiments-flag.test.js index b7554dff33e..f0bf72803eb 100644 --- a/test/core-flags/experiments-flag.test.js +++ b/test/core-flags/experiments-flag.test.js @@ -1,9 +1,10 @@ 'use strict'; const { run, hyphenToUpperCase } = require('../utils/test-utils'); -const { flags } = require('../../packages/webpack-cli/lib/utils/cli-flags'); +const CLI = require('../../packages/webpack-cli/lib/index'); -const experimentsFlags = flags.filter(({ name }) => name.startsWith('experiments-')); +const cli = new CLI(); +const experimentsFlags = cli.getBuiltInOptions().filter(({ name }) => name.startsWith('experiments-')); describe('experiments option related flag', () => { experimentsFlags.forEach((flag) => { diff --git a/test/core-flags/externals-flags.test.js b/test/core-flags/externals-flags.test.js index f402a194691..66b2e549c2d 100644 --- a/test/core-flags/externals-flags.test.js +++ b/test/core-flags/externals-flags.test.js @@ -1,9 +1,10 @@ 'use strict'; const { run, hyphenToUpperCase } = require('../utils/test-utils'); -const { flags } = require('../../packages/webpack-cli/lib/utils/cli-flags'); +const CLI = require('../../packages/webpack-cli/lib/index'); -const externalsPresetsFlags = flags.filter(({ name }) => name.startsWith('externals-presets-')); +const cli = new CLI(); +const externalsPresetsFlags = cli.getBuiltInOptions().filter(({ name }) => name.startsWith('externals-presets-')); describe('externals related flag', () => { it('should set externals properly', () => { diff --git a/test/core-flags/module-flags.test.js b/test/core-flags/module-flags.test.js index bc7ccd1faa6..5a3759573fe 100644 --- a/test/core-flags/module-flags.test.js +++ b/test/core-flags/module-flags.test.js @@ -1,9 +1,10 @@ 'use strict'; const { run, hyphenToUpperCase } = require('../utils/test-utils'); -const { flags } = require('../../packages/webpack-cli/lib/utils/cli-flags'); +const CLI = require('../../packages/webpack-cli/lib/index'); -const moduleFlags = flags.filter(({ name }) => name.startsWith('module-')); +const cli = new CLI(); +const moduleFlags = cli.getBuiltInOptions().filter(({ name }) => name.startsWith('module-')); describe('module config related flag', () => { moduleFlags.forEach((flag) => { diff --git a/test/core-flags/optimization-flags.test.js b/test/core-flags/optimization-flags.test.js index 72cd235c357..079f95929ab 100644 --- a/test/core-flags/optimization-flags.test.js +++ b/test/core-flags/optimization-flags.test.js @@ -1,9 +1,10 @@ 'use strict'; const { run, hyphenToUpperCase } = require('../utils/test-utils'); -const { flags } = require('../../packages/webpack-cli/lib/utils/cli-flags'); +const CLI = require('../../packages/webpack-cli/lib/index'); -const optimizationFlags = flags.filter(({ name }) => name.startsWith('optimization-')); +const cli = new CLI(); +const optimizationFlags = cli.getBuiltInOptions().filter(({ name }) => name.startsWith('optimization-')); describe('optimization config related flag', () => { optimizationFlags.forEach((flag) => { diff --git a/test/core-flags/output-flags.test.js b/test/core-flags/output-flags.test.js index fb08268a5a1..3829a5e3a21 100644 --- a/test/core-flags/output-flags.test.js +++ b/test/core-flags/output-flags.test.js @@ -1,9 +1,10 @@ 'use strict'; const { run, hyphenToUpperCase } = require('../utils/test-utils'); -const { flags } = require('../../packages/webpack-cli/lib/utils/cli-flags'); +const CLI = require('../../packages/webpack-cli/lib/index'); -const outputFlags = flags.filter(({ name }) => name.startsWith('output-')); +const cli = new CLI(); +const outputFlags = cli.getBuiltInOptions().filter(({ name }) => name.startsWith('output-')); describe('output config related flag', () => { outputFlags.forEach((flag) => { diff --git a/test/core-flags/performance-flags.test.js b/test/core-flags/performance-flags.test.js index f13d1ca0221..9d113fc7f22 100644 --- a/test/core-flags/performance-flags.test.js +++ b/test/core-flags/performance-flags.test.js @@ -1,9 +1,10 @@ 'use strict'; const { run, hyphenToUpperCase } = require('../utils/test-utils'); -const { flags } = require('../../packages/webpack-cli/lib/utils/cli-flags'); +const CLI = require('../../packages/webpack-cli/lib/index'); -const performanceFlags = flags.filter(({ name }) => name.startsWith('performance-')); +const cli = new CLI(); +const performanceFlags = cli.getBuiltInOptions().filter(({ name }) => name.startsWith('performance-')); describe('module config related flag', () => { it(`should config --performance option correctly`, () => { diff --git a/test/core-flags/resolve-flags.test.js b/test/core-flags/resolve-flags.test.js index e2c069ff02c..6b0318fcbf6 100644 --- a/test/core-flags/resolve-flags.test.js +++ b/test/core-flags/resolve-flags.test.js @@ -1,9 +1,10 @@ 'use strict'; const { run, hyphenToUpperCase } = require('../utils/test-utils'); -const { flags } = require('../../packages/webpack-cli/lib/utils/cli-flags'); +const CLI = require('../../packages/webpack-cli/lib/index'); -const resolveFlags = flags.filter(({ name }) => name.startsWith('resolve')); +const cli = new CLI(); +const resolveFlags = cli.getBuiltInOptions().filter(({ name }) => name.startsWith('resolve')); describe('resolve config related flags', () => { resolveFlags.forEach((flag) => { diff --git a/test/core-flags/snapshot-flags.test.js b/test/core-flags/snapshot-flags.test.js index 5860127cb3b..9cc9077db49 100644 --- a/test/core-flags/snapshot-flags.test.js +++ b/test/core-flags/snapshot-flags.test.js @@ -1,9 +1,10 @@ 'use strict'; const { run, hyphenToUpperCase } = require('../utils/test-utils'); -const { flags } = require('../../packages/webpack-cli/lib/utils/cli-flags'); +const CLI = require('../../packages/webpack-cli/lib/index'); -const snapshotFlags = flags.filter(({ name }) => name.startsWith('snapshot')); +const cli = new CLI(); +const snapshotFlags = cli.getBuiltInOptions().filter(({ name }) => name.startsWith('snapshot')); describe('snapshot config related flags', () => { snapshotFlags.forEach((flag) => { diff --git a/test/core-flags/stats-flags.test.js b/test/core-flags/stats-flags.test.js index 3dccbbea5b5..77d7ff11edd 100644 --- a/test/core-flags/stats-flags.test.js +++ b/test/core-flags/stats-flags.test.js @@ -1,9 +1,10 @@ 'use strict'; const { run, hyphenToUpperCase } = require('../utils/test-utils'); -const { flags } = require('../../packages/webpack-cli/lib/utils/cli-flags'); +const CLI = require('../../packages/webpack-cli/lib/index'); -const statsFlags = flags.filter(({ name }) => name.startsWith('stats-')); +const cli = new CLI(); +const statsFlags = cli.getBuiltInOptions().filter(({ name }) => name.startsWith('stats-')); describe('stats config related flag', () => { statsFlags.forEach((flag) => { diff --git a/test/core-flags/watch-flags.test.js b/test/core-flags/watch-flags.test.js index ddb47635f17..34656cccf7d 100644 --- a/test/core-flags/watch-flags.test.js +++ b/test/core-flags/watch-flags.test.js @@ -1,9 +1,10 @@ 'use strict'; const { run, hyphenToUpperCase } = require('../utils/test-utils'); -const { flags } = require('../../packages/webpack-cli/lib/utils/cli-flags'); +const CLI = require('../../packages/webpack-cli/lib/index'); -const watchFlags = flags.filter(({ name }) => name.startsWith('watch')); +const cli = new CLI(); +const watchFlags = cli.getBuiltInOptions().filter(({ name }) => name.startsWith('watch')); describe('watch config related flag', () => { watchFlags.forEach((flag) => { diff --git a/test/core-flags/webpack.test.config.js b/test/core-flags/webpack.test.config.js deleted file mode 100644 index cd24476ff4c..00000000000 --- a/test/core-flags/webpack.test.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = { mode: 'development' }; diff --git a/test/help/help.test.js b/test/help/help.test.js index 9b40070f5ab..4f1b703ede1 100644 --- a/test/help/help.test.js +++ b/test/help/help.test.js @@ -8,111 +8,124 @@ const helpDefaultHeader = 'The build tool for modern web applications.'; describe('help', () => { it('should show help information using the "--help" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['--help'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['--help']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('webpack [options]'); - expect(stdout).toContain(helpDefaultHeader); - expect(stdout).toContain('Options:'); - expect(stdout).toContain('--merge'); // minimum - expect(stdout).not.toContain('--cache-type'); // verbose - expect(stdout).toContain('Global options:'); - expect(stdout).toContain('Commands:'); - expect(stdout.match(/build\|bundle\|b/g)).toHaveLength(1); - expect(stdout.match(/watch\|w/g)).toHaveLength(1); - expect(stdout.match(/version\|v/g)).toHaveLength(1); - expect(stdout.match(/help\|h/g)).toHaveLength(1); - expect(stdout.match(/serve\|s/g)).toHaveLength(1); - expect(stdout.match(/info\|i/g)).toHaveLength(1); - expect(stdout.match(/init\|c/g)).toHaveLength(1); - expect(stdout.match(/loader\|l/g)).toHaveLength(1); - expect(stdout.match(/plugin\|p/g)).toHaveLength(1); - expect(stdout.match(/migrate\|m/g)).toHaveLength(1); - expect(stdout.match(/configtest\|t/g)).toHaveLength(1); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); - // TODO buggy on windows - // expect(coloretteEnabled ? stripAnsi(stdout) : stdout).toContain('Made with ♥ by the webpack team.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('webpack [entries...] [options]'); + expect(pureStdout).toContain('webpack [command] [options]'); + expect(pureStdout).toContain(helpDefaultHeader); + expect(pureStdout).toContain('Options:'); + expect(pureStdout).toContain('--merge'); // minimum + expect(pureStdout).not.toContain('--cache-type'); // verbose + expect(pureStdout).toContain('Global options:'); + expect(pureStdout).toContain('Commands:'); + expect(pureStdout.match(/build\|bundle\|b/g)).toHaveLength(1); + expect(pureStdout.match(/watch\|w/g)).toHaveLength(1); + expect(pureStdout.match(/version\|v/g)).toHaveLength(1); + expect(pureStdout.match(/help\|h/g)).toHaveLength(1); + expect(pureStdout.match(/serve\|s/g)).toHaveLength(1); + expect(pureStdout.match(/info\|i/g)).toHaveLength(1); + expect(pureStdout.match(/init\|c/g)).toHaveLength(1); + expect(pureStdout.match(/loader\|l/g)).toHaveLength(1); + expect(pureStdout.match(/plugin\|p/g)).toHaveLength(1); + expect(pureStdout.match(/migrate\|m/g)).toHaveLength(1); + expect(pureStdout.match(/configtest\|t/g)).toHaveLength(1); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + expect(pureStdout).toContain('Made with ♥ by the webpack team.'); }); it.skip('should show help information using the "--help" option with the "verbose" value', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['--help', 'verbose'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['--help', 'verbose']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('webpack [options]'); - expect(stdout).toContain(helpDefaultHeader); - expect(stdout).toContain('Options:'); - expect(stdout).toContain('--merge'); // minimum + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('webpack [entries...] [options]'); + expect(pureStdout).toContain('webpack [command] [options]'); + expect(pureStdout).toContain(helpDefaultHeader); + expect(pureStdout).toContain('Options:'); + expect(pureStdout).toContain('--merge'); // minimum if (isWebpack5) { - expect(stdout).toContain('--cache-type'); // verbose + expect(pureStdout).toContain('--cache-type'); // verbose } - expect(stdout).toContain('Global options:'); - expect(stdout).toContain('Commands:'); - expect(stdout.match(/build\|bundle\|b/g)).toHaveLength(1); - expect(stdout.match(/watch\|w/g)).toHaveLength(1); - expect(stdout.match(/version\|v/g)).toHaveLength(1); - expect(stdout.match(/help\|h/g)).toHaveLength(1); - expect(stdout.match(/serve\|s/g)).toHaveLength(1); - expect(stdout.match(/info\|i/g)).toHaveLength(1); - expect(stdout.match(/init\|c/g)).toHaveLength(1); - expect(stdout.match(/loader\|l/g)).toHaveLength(1); - expect(stdout.match(/plugin\|p/g)).toHaveLength(1); - expect(stdout.match(/migrate\|m/g)).toHaveLength(1); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); - expect(coloretteEnabled ? stripAnsi(stdout) : stdout).toContain('Made with ♥ by the webpack team.'); + expect(pureStdout).toContain('Global options:'); + expect(pureStdout).toContain('Commands:'); + expect(pureStdout.match(/build\|bundle\|b/g)).toHaveLength(1); + expect(pureStdout.match(/watch\|w/g)).toHaveLength(1); + expect(pureStdout.match(/version\|v/g)).toHaveLength(1); + expect(pureStdout.match(/help\|h/g)).toHaveLength(1); + expect(pureStdout.match(/serve\|s/g)).toHaveLength(1); + expect(pureStdout.match(/info\|i/g)).toHaveLength(1); + expect(pureStdout.match(/init\|c/g)).toHaveLength(1); + expect(pureStdout.match(/loader\|l/g)).toHaveLength(1); + expect(pureStdout.match(/plugin\|p/g)).toHaveLength(1); + expect(pureStdout.match(/migrate\|m/g)).toHaveLength(1); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + expect(pureStdout).toContain('Made with ♥ by the webpack team.'); }); it.skip('should show help information using the "--help" option with the "verbose" value #2', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['--help=verbose'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['--help=verbose']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('webpack [options]'); - expect(stdout).toContain(helpDefaultHeader); - expect(stdout).toContain('Options:'); - expect(stdout).toContain('--merge'); // minimum + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('webpack [entries...] [options]'); + expect(pureStdout).toContain('webpack [command] [options]'); + expect(pureStdout).toContain(helpDefaultHeader); + expect(pureStdout).toContain('Options:'); + expect(pureStdout).toContain('--merge'); // minimum if (isWebpack5) { - expect(stdout).toContain('--cache-type'); // verbose + expect(pureStdout).toContain('--cache-type'); // verbose } - expect(stdout).toContain('Global options:'); - expect(stdout).toContain('Commands:'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + expect(pureStdout).toContain('Global options:'); + expect(pureStdout).toContain('Commands:'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); expect(coloretteEnabled ? stripAnsi(stdout) : stdout).toContain('Made with ♥ by the webpack team.'); }); it('should show help information using command syntax', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('webpack [options]'); - expect(stdout).toContain(helpDefaultHeader); - expect(stdout).toContain('Options:'); - expect(stdout).toContain('--merge'); // minimum - expect(stdout).not.toContain('--cache-type'); // verbose - expect(stdout).toContain('Global options:'); - expect(stdout).toContain('Commands:'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('webpack [entries...] [options]'); + expect(pureStdout).toContain('webpack [command] [options]'); + expect(pureStdout).toContain(helpDefaultHeader); + expect(pureStdout).toContain('Options:'); + expect(pureStdout).toContain('--merge'); // minimum + expect(pureStdout).not.toContain('--cache-type'); // verbose + expect(pureStdout).toContain('Global options:'); + expect(pureStdout).toContain('Commands:'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); // TODO buggy on windows // expect(coloretteEnabled ? stripAnsi(stdout) : stdout).toContain('Made with ♥ by the webpack team.'); }); it('should show the same information using the "--help" option and command syntax', () => { - const { exitCode: exitCodeFromOption, stderr: stderrFromOption, stdout: stdoutFromOption } = run(__dirname, ['--help'], false); - const { exitCode: exitCodeFromCommandSyntax, stderr: stderrFromCommandSyntax, stdout: stdoutFromCommandSyntax } = run( - __dirname, - ['help'], - false, - ); + const { exitCode: exitCodeFromOption, stderr: stderrFromOption, stdout: stdoutFromOption } = run(__dirname, ['--help']); + const { exitCode: exitCodeFromCommandSyntax, stderr: stderrFromCommandSyntax, stdout: stdoutFromCommandSyntax } = run(__dirname, [ + 'help', + ]); expect(exitCodeFromOption).toBe(0); expect(exitCodeFromCommandSyntax).toBe(0); @@ -122,39 +135,47 @@ describe('help', () => { }); it('should show help information and respect the "--color" flag using the "--help" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['--help', '--color'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['--help', '--color']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('webpack [options]'); - expect(stdout).toContain(helpDefaultHeader); - expect(stdout).toContain('Options:'); - expect(stdout).toContain('--merge'); // minimum - expect(stdout).not.toContain('--cache-type'); // verbose - expect(stdout).toContain('Global options:'); - expect(stdout).toContain('Commands:'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); - expect(stdout).toContain(coloretteEnabled ? bold('Made with ♥ by the webpack team') : 'Made with ♥ by the webpack team'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('webpack [entries...] [options]'); + expect(pureStdout).toContain('webpack [command] [options]'); + expect(pureStdout).toContain(helpDefaultHeader); + expect(pureStdout).toContain('Options:'); + expect(pureStdout).toContain('--merge'); // minimum + expect(pureStdout).not.toContain('--cache-type'); // verbose + expect(pureStdout).toContain('Global options:'); + expect(pureStdout).toContain('Commands:'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + expect(pureStdout).toContain(coloretteEnabled ? bold('Made with ♥ by the webpack team') : 'Made with ♥ by the webpack team'); }); it('should show help information and respect the "--no-color" flag using the "--help" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['--help', '--no-color'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['--help', '--no-color']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('webpack [options]'); - expect(stdout).toContain(helpDefaultHeader); - expect(stdout).toContain('Options:'); - expect(stdout).toContain('--merge'); // minimum - expect(stdout).not.toContain('--cache-type'); // verbose - expect(stdout).toContain('Global options:'); - expect(stdout).toContain('Commands:'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('webpack [entries...] [options]'); + expect(pureStdout).toContain('webpack [command] [options]'); + expect(pureStdout).toContain(helpDefaultHeader); + expect(pureStdout).toContain('Options:'); + expect(pureStdout).toContain('--merge'); // minimum + expect(pureStdout).not.toContain('--cache-type'); // verbose + expect(pureStdout).toContain('Global options:'); + expect(pureStdout).toContain('Commands:'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); // TODO bug in tests // expect(stdout).not.toContain(bold('Made with ♥ by the webpack team')); - expect(stdout).toContain('Made with ♥ by the webpack team'); + expect(pureStdout).toContain('Made with ♥ by the webpack team'); }); const commands = [ @@ -181,7 +202,7 @@ describe('help', () => { commands.forEach((command) => { it(`should show help information for '${command}' command using the "--help" option`, () => { - const { exitCode, stderr, stdout } = run(__dirname, [command, '--help'], false); + const { exitCode, stderr, stdout } = run(__dirname, [command, '--help']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); @@ -189,7 +210,7 @@ describe('help', () => { }); it(`should show help information for '${command}' command using command syntax`, () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', command], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', command]); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); @@ -197,7 +218,7 @@ describe('help', () => { }); it('should show help information and respect the "--color" flag using the "--help" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, [command, '--help', '--color'], false); + const { exitCode, stderr, stdout } = run(__dirname, [command, '--help', '--color']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); @@ -206,7 +227,7 @@ describe('help', () => { }); it('should show help information and respect the "--no-color" flag using the "--help" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, [command, '--help', '--no-color'], false); + const { exitCode, stderr, stdout } = run(__dirname, [command, '--help', '--no-color']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); @@ -218,145 +239,178 @@ describe('help', () => { }); it('should show help information with options for sub commands', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['info', '--help'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['info', '--help']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('webpack info|i [options]'); - expect(stdout).toContain('Options:'); - expect(stdout).toContain('--output '); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); - expect(stdout).toContain('Made with ♥ by the webpack team'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('webpack info|i [options]'); + expect(pureStdout).toContain('Options:'); + expect(pureStdout).toContain('--output '); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + expect(pureStdout).toContain('Made with ♥ by the webpack team'); }); it('should show help information and taking precedence when "--help" and "--version" option using together', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['--help', '--version'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['--help', '--version']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('webpack [options]'); - expect(stdout).toContain(helpDefaultHeader); - expect(stdout).toContain('Options:'); - expect(stdout).toContain('--merge'); // minimum - expect(stdout).not.toContain('--cache-type'); // verbose - expect(stdout).toContain('Global options:'); - expect(stdout).toContain('Commands:'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('webpack [entries...] [options]'); + expect(pureStdout).toContain('webpack [command] [options]'); + expect(pureStdout).toContain(helpDefaultHeader); + expect(pureStdout).toContain('Options:'); + expect(pureStdout).toContain('--merge'); // minimum + expect(pureStdout).not.toContain('--cache-type'); // verbose + expect(pureStdout).toContain('Global options:'); + expect(pureStdout).toContain('Commands:'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); // TODO buggy on windows // expect(coloretteEnabled ? stripAnsi(stdout) : stdout).toContain('Made with ♥ by the webpack team.'); }); it('should show help information using the "help --mode" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', '--mode'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', '--mode']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('Usage: webpack --mode '); - expect(stdout).toContain('Description: Defines the mode to pass to webpack.'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('Usage: webpack --mode '); + expect(pureStdout).toContain('Description: Defines the mode to pass to webpack.'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); }); it('should show help information using the "help --target" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', '--target'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', '--target']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); + const pureStdout = stripAnsi(stdout); + if (isWebpack5) { - expect(stdout).toContain('Usage: webpack --target '); - expect(stdout).toContain('Short: webpack -t '); + expect(pureStdout).toContain('Usage: webpack --target '); + expect(pureStdout).toContain('Short: webpack -t '); } else { - expect(stdout).toContain('Usage: webpack --target '); - expect(stdout).toContain('Short: webpack -t '); + expect(pureStdout).toContain('Usage: webpack --target '); + expect(pureStdout).toContain('Short: webpack -t '); } - expect(stdout).toContain('Description: Sets the build target e.g. node.'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + expect(pureStdout).toContain('Description: Sets the build target e.g. node.'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); }); it('should show help information using the "help --stats" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', '--stats'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', '--stats']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('Usage: webpack --stats [value]'); - expect(stdout).toContain('Description: It instructs webpack on how to treat the stats e.g. verbose.'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('Usage: webpack --stats [value]'); + expect(pureStdout).toContain('Description: It instructs webpack on how to treat the stats e.g. verbose.'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); }); it('should show help information using the "help --no-stats" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', '--no-stats'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', '--no-stats']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('Usage: webpack --no-stats'); - expect(stdout).toContain('Description: Disable stats output.'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('Usage: webpack --no-stats'); + expect(pureStdout).toContain('Description: Disable stats output.'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); }); it('should show help information using the "help --mode" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', '--mode'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', '--mode']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('Usage: webpack --mode '); - expect(stdout).toContain('Description: Defines the mode to pass to webpack.'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('Usage: webpack --mode '); + expect(pureStdout).toContain('Description: Defines the mode to pass to webpack.'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); }); it('should show help information using the "help serve --mode" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', 'serve', '--mode'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', 'serve', '--mode']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('Usage: webpack serve --mode '); - expect(stdout).toContain('Description: Defines the mode to pass to webpack.'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('Usage: webpack serve --mode '); + expect(pureStdout).toContain('Description: Defines the mode to pass to webpack.'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); }); it('should show help information using the "help --color" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', '--color'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', '--color']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('Usage: webpack --color'); - expect(stdout).toContain('Description: Enable colors on console.'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('Usage: webpack --color'); + expect(pureStdout).toContain('Description: Enable colors on console.'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); }); it('should show help information using the "help --no-color" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', '--no-color'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', '--no-color']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('Usage: webpack --no-color'); - expect(stdout).toContain('Description: Disable colors on console.'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('Usage: webpack --no-color'); + expect(pureStdout).toContain('Description: Disable colors on console.'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); }); it('should show help information using the "help serve --color" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', 'serve', '--color'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', 'serve', '--color']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('Usage: webpack serve --color'); - expect(stdout).toContain('Description: Enable colors on console.'); - expect(stdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('Usage: webpack serve --color'); + expect(pureStdout).toContain('Description: Enable colors on console.'); + expect(pureStdout).toContain("To see list of all supported commands and options run 'webpack --help=verbose'."); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); }); it('should show help information using the "help serve --no-color" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', 'serve', '--no-color'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', 'serve', '--no-color']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); @@ -367,33 +421,39 @@ describe('help', () => { }); it('should show help information using the "help --version" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', '--version'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', '--version']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('Usage: webpack --version'); - expect(stdout).toContain('Short: webpack -v'); - expect(stdout).toContain( + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('Usage: webpack --version'); + expect(pureStdout).toContain('Short: webpack -v'); + expect(pureStdout).toContain( "Description: Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands.", ); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); }); it('should show help information using the "help -v" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', '-v'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', '-v']); expect(exitCode).toBe(0); expect(stderr).toBeFalsy(); - expect(stdout).toContain('Usage: webpack --version'); - expect(stdout).toContain('Short: webpack -v'); - expect(stdout).toContain( + + const pureStdout = stripAnsi(stdout); + + expect(pureStdout).toContain('Usage: webpack --version'); + expect(pureStdout).toContain('Short: webpack -v'); + expect(pureStdout).toContain( "Description: Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands.", ); - expect(stdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); + expect(pureStdout).toContain('CLI documentation: https://webpack.js.org/api/cli/.'); }); it('should log error for invalid command using the "--help" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['--help', 'myCommand'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['--help', 'myCommand']); expect(exitCode).toBe(2); expect(stderr).toContain("Unknown value for '--help' option, please use '--help=verbose'"); @@ -401,7 +461,7 @@ describe('help', () => { }); it('should log error for invalid command using the "--help" option #2', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['--flag', '--help'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['--flag', '--help']); expect(exitCode).toBe(2); expect(stderr).toContain('Incorrect use of help'); @@ -411,7 +471,7 @@ describe('help', () => { }); it('should log error for invalid command using the "--help" option #3', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['serve', '--flag', '--help'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['serve', '--flag', '--help']); expect(exitCode).toBe(2); expect(stderr).toContain('Incorrect use of help'); @@ -421,7 +481,7 @@ describe('help', () => { }); it('should log error for unknown command using command syntax', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', 'myCommand'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', 'myCommand']); expect(exitCode).toBe(2); expect(stderr).toContain("Can't find and load command 'myCommand'"); @@ -430,7 +490,7 @@ describe('help', () => { }); it('should log error for unknown command using command syntax #2', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', 'verbose'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', 'verbose']); expect(exitCode).toBe(2); expect(stderr).toContain("Can't find and load command 'verbose'"); @@ -439,7 +499,7 @@ describe('help', () => { }); it('should log error for unknown option using command syntax #2', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', '--made'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', '--made']); expect(exitCode).toBe(2); expect(stderr).toContain("Unknown option '--made'"); @@ -448,7 +508,7 @@ describe('help', () => { }); it('should log error for unknown option using command syntax #3', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', 'serve', '--made'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', 'serve', '--made']); expect(exitCode).toBe(2); expect(stderr).toContain("Unknown option '--made'"); @@ -457,7 +517,7 @@ describe('help', () => { }); it('should log error for unknown option using command syntax #4', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', 'bui', '--mode'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', 'bui', '--mode']); expect(exitCode).toBe(2); expect(stderr).toContain("Can't find and load command 'bui'"); @@ -466,7 +526,7 @@ describe('help', () => { }); it('should log error for invalid command using command syntax #3', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', '--mode', 'serve'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', '--mode', 'serve']); expect(exitCode).toBe(2); expect(stderr).toContain('Incorrect use of help'); @@ -476,7 +536,7 @@ describe('help', () => { }); it('should log error for invalid command using command syntax #4', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['help', 'serve', '--mode', '--mode'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['help', 'serve', '--mode', '--mode']); expect(exitCode).toBe(2); expect(stderr).toContain('Incorrect use of help'); @@ -486,7 +546,7 @@ describe('help', () => { }); it('should log error for invalid flag with the "--help" option', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['--help', '--my-flag'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['--help', '--my-flag']); expect(exitCode).toBe(2); expect(stderr).toContain('Incorrect use of help'); @@ -496,7 +556,7 @@ describe('help', () => { }); it('should log error for invalid flag with the "--help" option #2', () => { - const { exitCode, stderr, stdout } = run(__dirname, ['--help', 'init', 'info'], false); + const { exitCode, stderr, stdout } = run(__dirname, ['--help', 'init', 'info']); expect(exitCode).toBe(2); expect(stderr).toContain("Unknown value for '--help' option, please use '--help=verbose'"); diff --git a/test/merge/config-absent/merge-config-absent.test.js b/test/merge/config-absent/merge-config-absent.test.js index 5c9d6e05d03..623d8259758 100644 --- a/test/merge/config-absent/merge-config-absent.test.js +++ b/test/merge/config-absent/merge-config-absent.test.js @@ -1,5 +1,7 @@ 'use strict'; +const path = require('path'); + const { run } = require('../../utils/test-utils'); describe('merge flag configuration', () => { @@ -11,6 +13,6 @@ describe('merge flag configuration', () => { // Since the process will exit, nothing on stdout expect(stdout).toBeFalsy(); // Confirm that the user is notified - expect(stderr).toContain(`The specified config file doesn't exist`); + expect(stderr).toContain(`Failed to load '${path.resolve(__dirname, './2.js')}' config`); }); }); diff --git a/test/serve/basic/serve-basic.test.js b/test/serve/basic/serve-basic.test.js index 3ad7aa8def4..a57308f0a00 100644 --- a/test/serve/basic/serve-basic.test.js +++ b/test/serve/basic/serve-basic.test.js @@ -7,9 +7,6 @@ const { runServe, isWebpack5, isDevServer4 } = require('../../utils/test-utils') const testPath = path.resolve(__dirname); -const usageText = 'webpack serve|s [options]'; -const descriptionText = 'Run the webpack dev server'; - describe('basic serve usage', () => { let port; @@ -28,7 +25,7 @@ describe('basic serve usage', () => { } it('should work', async () => { - const { stderr, stdout } = await runServe(__dirname, ['']); + const { stderr, stdout } = await runServe(__dirname, []); expect(stderr).toBeFalsy(); expect(stdout).toContain('main.js'); @@ -36,7 +33,7 @@ describe('basic serve usage', () => { }); it('should work with the "--config" option', async () => { - const { stderr, stdout } = await runServe(__dirname, ['serve', '--config', 'webpack.config.js', '--port', port]); + const { stderr, stdout } = await runServe(__dirname, ['--config', 'serve.config.js', '--port', port]); expect(stderr).toBeFalsy(); expect(stdout).toContain('development'); @@ -45,7 +42,6 @@ describe('basic serve usage', () => { it('should work with the "--config" and "--env" options', async () => { const { stderr, stdout } = await runServe(__dirname, [ - 'serve', '--config', 'function-with-env.config.js', '--env', @@ -63,7 +59,6 @@ describe('basic serve usage', () => { it('should work with the "--config" and "--env" options and expose dev server options', async () => { const { stderr, stdout } = await runServe(__dirname, [ - 'serve', '--config', 'function-with-argv.config.js', '--env', @@ -82,7 +77,7 @@ describe('basic serve usage', () => { }); it('should work in multi compiler mode', async () => { - const { stderr, stdout } = await runServe(__dirname, ['serve', '--config', 'multi.config.js', '--port', port]); + const { stderr, stdout } = await runServe(__dirname, ['--config', 'multi.config.js', '--port', port]); expect(stderr).toBeFalsy(); expect(stdout).toContain('one'); @@ -94,7 +89,7 @@ describe('basic serve usage', () => { // TODO need fix in future, edge case it.skip('should work in multi compiler mode with multiple dev servers', async () => { - const { stderr, stdout } = await runServe(__dirname, ['serve', '--config', 'multi-dev-server.config.js']); + const { stderr, stdout } = await runServe(__dirname, ['--config', 'multi-dev-server.config.js']); expect(stderr).toBeFalsy(); expect(stdout).toContain('one'); @@ -163,14 +158,6 @@ describe('basic serve usage', () => { expect(stdout.match(/HotModuleReplacementPlugin/g)).toBeNull(); }); - it('should log help information and respect the "--no-color" option', async () => { - const { stdout, stderr } = await runServe(__dirname, ['--help', '--no-color']); - - expect(stderr).toBeFalsy(); - expect(stdout).toContain(usageText); - expect(stdout).toContain(descriptionText); - }); - it('should work with the "--client-log-level" option', async () => { const { stdout, stderr } = await runServe(testPath, ['--client-log-level', 'info']); @@ -228,7 +215,7 @@ describe('basic serve usage', () => { }); it('should work with the default "publicPath" option', async () => { - const { stderr, stdout } = await runServe(__dirname, ['serve']); + const { stderr, stdout } = await runServe(__dirname, []); expect(stderr).toBeFalsy(); expect(stdout).toContain('main.js'); @@ -237,7 +224,7 @@ describe('basic serve usage', () => { }); it('should work with the "--output-public-path" option', async () => { - const { stderr, stdout } = await runServe(__dirname, ['serve', '--output-public-path', '/my-public-path/']); + const { stderr, stdout } = await runServe(__dirname, ['--output-public-path', '/my-public-path/']); if (isWebpack5) { expect(stderr).toBeFalsy(); @@ -251,7 +238,7 @@ describe('basic serve usage', () => { }); it('should respect the "publicPath" option from configuration', async () => { - const { stderr, stdout } = await runServe(__dirname, ['serve', '--config', 'output-public-path.config.js']); + const { stderr, stdout } = await runServe(__dirname, ['--config', 'output-public-path.config.js']); expect(stderr).toBeFalsy(); expect(stdout).toContain('main.js'); @@ -260,7 +247,7 @@ describe('basic serve usage', () => { }); it('should respect the "publicPath" option from configuration using multi compiler mode', async () => { - const { stderr, stdout } = await runServe(__dirname, ['serve', '--config', 'multi-output-public-path.config.js', '--port', port]); + const { stderr, stdout } = await runServe(__dirname, ['--config', 'multi-output-public-path.config.js', '--port', port]); expect(stderr).toBeFalsy(); expect(stdout).toContain('one'); @@ -272,7 +259,7 @@ describe('basic serve usage', () => { }); it('should respect the "publicPath" option from configuration (from the "devServer" options)', async () => { - const { stderr, stdout } = await runServe(__dirname, ['serve', '--config', 'dev-server-output-public-path.config.js']); + const { stderr, stdout } = await runServe(__dirname, ['--config', 'dev-server-output-public-path.config.js']); expect(stderr).toBeFalsy(); expect(stdout).toContain('main.js'); @@ -289,13 +276,7 @@ describe('basic serve usage', () => { }); it('should respect the "publicPath" option from configuration using multi compiler mode (from the "devServer" options)', async () => { - const { stderr, stdout } = await runServe(__dirname, [ - 'serve', - '--config', - 'multi-dev-server-output-public-path.config.js', - '--port', - port, - ]); + const { stderr, stdout } = await runServe(__dirname, ['--config', 'multi-dev-server-output-public-path.config.js', '--port', port]); expect(stderr).toBeFalsy(); expect(stderr).toBeFalsy(); @@ -307,6 +288,14 @@ describe('basic serve usage', () => { expect(stdout.match(/HotModuleReplacementPlugin/g)).toBeNull(); }); + it('should work with entries syntax', async () => { + const { stderr, stdout } = await runServe(__dirname, ['./src/entry.js', '--port', port]); + + expect(stderr).toBeFalsy(); + expect(stdout).toContain('development'); + expect(stdout.match(/HotModuleReplacementPlugin/g)).toBeNull(); + }); + it('should log and error on unknown flag', async () => { const { exitCode, stdout, stderr } = await runServe(testPath, ['--port', port, '--unknown-flag']); diff --git a/test/serve/basic/serve.config.js b/test/serve/basic/serve.config.js new file mode 100644 index 00000000000..cef4d803dc3 --- /dev/null +++ b/test/serve/basic/serve.config.js @@ -0,0 +1,7 @@ +const WebpackCLITestPlugin = require('../../utils/webpack-cli-test-plugin'); + +module.exports = { + mode: 'development', + devtool: false, + plugins: [new WebpackCLITestPlugin(['mode'], false, 'hooks.compilation.taps')], +}; diff --git a/test/serve/basic/src/entry.js b/test/serve/basic/src/entry.js new file mode 100644 index 00000000000..a136806e8f1 --- /dev/null +++ b/test/serve/basic/src/entry.js @@ -0,0 +1 @@ +console.log('Entry'); diff --git a/test/serve/help/serve-help.test.js b/test/serve/help/serve-help.test.js index c1f4cc840c2..eb8cabef807 100644 --- a/test/serve/help/serve-help.test.js +++ b/test/serve/help/serve-help.test.js @@ -1,11 +1,12 @@ const { runServe, isWebpack5 } = require('../../utils/test-utils'); -const usageText = 'webpack serve|s [options]'; +const usageText = 'webpack serve|s [entries...] [options]'; const descriptionText = 'Run the webpack dev server'; describe('serve help', () => { - it('should output serve help', async () => { + it('should log help information', async () => { const { stderr, stdout, exitCode } = await runServe(__dirname, ['--help']); + expect(stderr).toBeFalsy(); expect(exitCode).toBe(0); expect(stdout).toContain(descriptionText); @@ -18,15 +19,25 @@ describe('serve help', () => { expect(stdout).toContain('--open [value]'); }); - it('should output all flags when verbose help is set', async () => { + it('should log help information using "verbose"', async () => { const { stderr, stdout, exitCode } = await runServe(__dirname, ['--help', 'verbose']); + expect(stderr).toBeFalsy(); expect(exitCode).toBe(0); expect(stdout).toContain(descriptionText); expect(stdout).toContain(usageText); expect(stdout).toContain('Options:'); + if (isWebpack5) { expect(stdout).toContain('--cache-type'); } }); + + it('should log help information and respect the "--no-color" option', async () => { + const { stdout, stderr } = await runServe(__dirname, ['--help', '--no-color']); + + expect(stderr).toBeFalsy(); + expect(stdout).toContain(usageText); + expect(stdout).toContain(descriptionText); + }); }); diff --git a/test/unknown/unknown.test.js b/test/unknown/unknown.test.js index ad3b6c97ec3..c87fbd5bcd2 100644 --- a/test/unknown/unknown.test.js +++ b/test/unknown/unknown.test.js @@ -203,7 +203,7 @@ describe('unknown behaviour', () => { const { exitCode, stderr, stdout } = run(__dirname, ['qqq'], true, [], { TERM_PROGRAM: false }); expect(exitCode).toBe(2); - expect(stripAnsi(stderr)).toContain("Unknown command 'qqq'"); + expect(stripAnsi(stderr)).toContain("Unknown command or entry 'qqq'"); expect(stripAnsi(stderr)).toContain("Run 'webpack --help' to see available commands and options"); expect(stdout).toBeFalsy(); }); @@ -212,7 +212,7 @@ describe('unknown behaviour', () => { const { exitCode, stderr, stdout } = run(__dirname, ['server'], true, [], { TERM_PROGRAM: false }); expect(exitCode).toBe(2); - expect(stripAnsi(stderr)).toContain("Unknown command 'server'"); + expect(stripAnsi(stderr)).toContain("Unknown command or entry 'server'"); expect(stripAnsi(stderr)).toContain("Did you mean 'serve' (alias 's')?"); expect(stripAnsi(stderr)).toContain("Run 'webpack --help' to see available commands and options"); expect(stdout).toBeFalsy(); diff --git a/test/watch/basic/basic.test.js b/test/watch/basic/basic.test.js index 34fee148278..0d24c6dda3a 100644 --- a/test/watch/basic/basic.test.js +++ b/test/watch/basic/basic.test.js @@ -83,6 +83,41 @@ describe('basic', () => { }); }); + it('should recompile upon file change using the `watch` command and entries syntax', (done) => { + const proc = runAndGetWatchProc(__dirname, ['watch', './src/entry.js', '--mode', 'development'], false, '', true); + + let modified = false; + + const wordsInStatsv5Entries = ['asset', 'entry.js', 'compiled successfully']; + + proc.stdout.on('data', (chunk) => { + const data = stripAnsi(chunk.toString()); + + if (data.includes('entry.js')) { + if (isWebpack5) { + for (const word of wordsInStatsv5Entries) { + expect(data).toContain(word); + } + } else { + for (const word of wordsInStatsv4) { + expect(data).toContain(word); + } + } + + if (!modified) { + process.nextTick(() => { + writeFileSync(resolve(__dirname, './src/entry.js'), `console.log('watch flag test');`); + }); + + modified = true; + } else { + proc.kill(); + done(); + } + } + }); + }); + it('should recompile upon file change using the `command` option and the `--watch` option and log warning', (done) => { const proc = runAndGetWatchProc(__dirname, ['watch', '--watch', '--mode', 'development'], false, '', true); diff --git a/test/watch/basic/src/entry.js b/test/watch/basic/src/entry.js new file mode 100644 index 00000000000..1d8734ee1c8 --- /dev/null +++ b/test/watch/basic/src/entry.js @@ -0,0 +1 @@ +console.log('watch flag test');