From eb7b18937d045261a5b20ca8356e8b4ae4dfcaad Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Wed, 6 Jan 2021 22:24:42 +0530 Subject: [PATCH] feat: new `configtest` command (#2303) --- .eslintignore | 1 + .prettierignore | 1 + OPTIONS.md | 7 ++-- packages/configtest/package.json | 18 ++++++++ packages/configtest/src/index.ts | 54 ++++++++++++++++++++++++ packages/configtest/tsconfig.json | 8 ++++ packages/webpack-cli/README.md | 19 +++++---- packages/webpack-cli/lib/webpack-cli.js | 5 +++ packages/webpack-cli/package.json | 3 ++ test/configtest/configtest.test.js | 55 +++++++++++++++++++++++++ test/configtest/error.config.js | 5 +++ test/configtest/src/index.js | 1 + test/configtest/syntax-error.config.js | 5 +++ test/configtest/webpack.config.js | 5 +++ test/help/help.test.js | 1 + tsconfig.json | 27 +++++++++--- 16 files changed, 197 insertions(+), 18 deletions(-) create mode 100644 packages/configtest/package.json create mode 100644 packages/configtest/src/index.ts create mode 100644 packages/configtest/tsconfig.json create mode 100644 test/configtest/configtest.test.js create mode 100644 test/configtest/error.config.js create mode 100644 test/configtest/src/index.js create mode 100644 test/configtest/syntax-error.config.js create mode 100644 test/configtest/webpack.config.js diff --git a/.eslintignore b/.eslintignore index ec220a0fdec..97edf222a8a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,3 +10,4 @@ test/typescript/webpack.config.ts test/config/error-commonjs/syntax-error.js test/config/error-mjs/syntax-error.mjs test/config/error-array/webpack.config.js +test/configtest/syntax-error.config.js diff --git a/.prettierignore b/.prettierignore index 7b3b7544c17..8f68c1e421c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,3 +9,4 @@ test/config/error-mjs/syntax-error.mjs packages/webpack-cli/__tests__/test-assets/.yo-rc.json test/build-errors/stats.json packages/**/lib +test/configtest/syntax-error.config.js diff --git a/OPTIONS.md b/OPTIONS.md index bf4cd0ba68f..c8b1025d4a8 100644 --- a/OPTIONS.md +++ b/OPTIONS.md @@ -549,7 +549,7 @@ Options: --watch-options-ignored-reset Clear all items provided in configuration. Ignore some files from watching (glob pattern or regexp). --watch-options-poll `number`: use polling with specified interval. `true`: use polling. --watch-options-stdin Stop watching when stdin stream has ended. - --no-watch-options-stdin Negative 'watch-options-stdin' option. + --no-watch-options-stdin Do not stop watching when stdin stream has ended. Global options: --color Enable colors on console. @@ -559,14 +559,15 @@ Global options: Commands: bundle|b [options] Run webpack (default command, can be omitted). - help|h Display help for commands and options. version|v Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands. + help|h 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. - plugin|p [output-path] Scaffold a plugin. migrate|m [new-config-path] Migrate a configuration to a new version. + configtest|t Tests webpack configuration against validation errors. + plugin|p [output-path] Scaffold a plugin. To see list of all supported commands and options run 'webpack --help=verbose'. diff --git a/packages/configtest/package.json b/packages/configtest/package.json new file mode 100644 index 00000000000..6b0857083b0 --- /dev/null +++ b/packages/configtest/package.json @@ -0,0 +1,18 @@ +{ + "name": "@webpack-cli/configtest", + "version": "1.0.0", + "description": "Tests webpack configuration against validation errors.", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "files": [ + "lib" + ], + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } +} diff --git a/packages/configtest/src/index.ts b/packages/configtest/src/index.ts new file mode 100644 index 00000000000..45cd643aacb --- /dev/null +++ b/packages/configtest/src/index.ts @@ -0,0 +1,54 @@ +import webpack from 'webpack'; + +class ConfigTestCommand { + async apply(cli): Promise { + const { logger } = cli; + + await cli.makeCommand( + { + name: 'configtest ', + alias: 't', + description: 'Tests webpack configuration against validation errors.', + usage: '', + pkg: '@webpack-cli/configtest', + }, + [], + async (configPath: string): Promise => { + const config = await cli.resolveConfig({ config: [configPath] }); + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const error: any = webpack.validate(config.options); + + // TODO remove this after drop webpack@4 + if (error && error.length > 0) { + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore + throw new webpack.WebpackOptionsValidationError(error); + } + } catch (error) { + 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 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const ValidationError = (webpack.ValidationError || webpack.WebpackOptionsValidationError) as any; + + return error instanceof ValidationError; + }; + + if (isValidationError(error)) { + logger.error(error.message); + } else { + logger.error(error); + } + + process.exit(2); + } + + logger.success('There are no validation errors in the given webpack configuration.'); + }, + ); + } +} + +export default ConfigTestCommand; diff --git a/packages/configtest/tsconfig.json b/packages/configtest/tsconfig.json new file mode 100644 index 00000000000..279b3e923cc --- /dev/null +++ b/packages/configtest/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src"] +} diff --git a/packages/webpack-cli/README.md b/packages/webpack-cli/README.md index e0193dbd4d9..788802545db 100644 --- a/packages/webpack-cli/README.md +++ b/packages/webpack-cli/README.md @@ -63,15 +63,16 @@ npx webpack-cli --help verbose ### Available Commands ``` - bundle | b Run webpack - help | h Display help for commands and options - version | v Output version number of the 'webpack', 'webpack-cli' and other related packages - init | c Initialize a new webpack configuration - migrate | m Migrate a configuration to a new version - loader | l Scaffold a loader repository - plugin | p Scaffold a plugin repository - info | i Outputs information about your system and dependencies - serve | s Run the webpack Dev Server + bundle | b Run webpack + help | h Display help for commands and options + version | v Output version number of the 'webpack', 'webpack-cli' and other related packages + init | c Initialize a new webpack configuration + migrate | m Migrate a configuration to a new version + loader | l Scaffold a loader repository + plugin | p Scaffold a plugin repository + info | i Outputs information about your system and dependencies + serve | s Run the webpack Dev Server + configtest | t Tests webpack configuration against validation errors. ``` ### webpack 4 diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index e8e83ca8128..78e8d07cdab 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -279,6 +279,11 @@ class WebpackCLI { alias: 'm', pkg: '@webpack-cli/migrate', }, + { + name: 'configtest', + alias: 't', + pkg: '@webpack-cli/configtest', + }, ]; const knownCommands = [bundleCommandOptions, versionCommandOptions, helpCommandOptions, ...externalBuiltInCommandsInfo]; diff --git a/packages/webpack-cli/package.json b/packages/webpack-cli/package.json index d03d1ab2d84..148ac8df7bb 100644 --- a/packages/webpack-cli/package.json +++ b/packages/webpack-cli/package.json @@ -54,6 +54,9 @@ "@webpack-cli/migrate": { "optional": true }, + "@webpack-cli/configtest": { + "optional": true + }, "webpack-bundle-analyzer": { "optional": true }, diff --git a/test/configtest/configtest.test.js b/test/configtest/configtest.test.js new file mode 100644 index 00000000000..8d88f8e62dc --- /dev/null +++ b/test/configtest/configtest.test.js @@ -0,0 +1,55 @@ +'use strict'; + +const { run } = require('../utils/test-utils'); + +describe('basic info usage', () => { + it('should validate webpack config successfully', () => { + const { exitCode, stderr, stdout } = run(__dirname, ['configtest', './webpack.config.js'], false); + + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toContain('There are no validation errors in the given webpack configuration.'); + }); + + it('should throw validation error', () => { + const { exitCode, stderr, stdout } = run(__dirname, ['configtest', './error.config.js'], false); + + expect(exitCode).toBe(2); + expect(stderr).toContain('Invalid configuration object.'); + expect(stderr).toContain('configuration.mode should be one of these:'); + expect(stdout).toBeFalsy(); + }); + + it('should throw syntax error', () => { + const { exitCode, stderr, stdout } = run(__dirname, ['configtest', './syntax-error.config.js'], false); + + expect(exitCode).toBe(2); + expect(stderr).toContain(`SyntaxError: Unexpected token ';'`); + expect(stdout).toBeFalsy(); + }); + + it(`should validate the config with alias 't'`, () => { + const { exitCode, stderr, stdout } = run(__dirname, ['t', './error.config.js'], false); + + expect(exitCode).toBe(2); + expect(stderr).toContain('Invalid configuration object.'); + expect(stderr).toContain('configuration.mode should be one of these:'); + expect(stdout).toBeFalsy(); + }); + + it('should throw error if configuration does not exist', () => { + 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(stdout).toBeFalsy(); + }); + + it('should throw error if no configuration was provided', () => { + const { exitCode, stderr, stdout } = run(__dirname, ['configtest'], false); + + expect(exitCode).toBe(2); + expect(stderr).toContain(`error: missing required argument 'config-path'`); + expect(stdout).toBeFalsy(); + }); +}); diff --git a/test/configtest/error.config.js b/test/configtest/error.config.js new file mode 100644 index 00000000000..8e614cbed23 --- /dev/null +++ b/test/configtest/error.config.js @@ -0,0 +1,5 @@ +module.exports = { + mode: 'dev', // error + target: 'node', + stats: 'normal', +}; diff --git a/test/configtest/src/index.js b/test/configtest/src/index.js new file mode 100644 index 00000000000..95ef2d52163 --- /dev/null +++ b/test/configtest/src/index.js @@ -0,0 +1 @@ +console.log('configtest command'); diff --git a/test/configtest/syntax-error.config.js b/test/configtest/syntax-error.config.js new file mode 100644 index 00000000000..96fc2e63b73 --- /dev/null +++ b/test/configtest/syntax-error.config.js @@ -0,0 +1,5 @@ +module.exports = { + name: 'config-error', + mode: 'development', + target: 'node'; //SyntaxError: Unexpected token ';' +}; diff --git a/test/configtest/webpack.config.js b/test/configtest/webpack.config.js new file mode 100644 index 00000000000..bdaebdb6f26 --- /dev/null +++ b/test/configtest/webpack.config.js @@ -0,0 +1,5 @@ +module.exports = { + mode: 'development', + target: 'node', + stats: 'verbose', +}; diff --git a/test/help/help.test.js b/test/help/help.test.js index 15126bb04bc..41be8093ff5 100644 --- a/test/help/help.test.js +++ b/test/help/help.test.js @@ -28,6 +28,7 @@ describe('help', () => { expect(stdout.match(/loader\|l/g)).toHaveLength(1); expect(stdout.match(/migrate\|m/g)).toHaveLength(1); expect(stdout.match(/plugin\|p/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 diff --git a/tsconfig.json b/tsconfig.json index 2a8277f0cfb..9062547bfb5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,11 +20,26 @@ "declaration": true }, "references": [ - { "path": "packages/generators" }, - { "path": "packages/info" }, - { "path": "packages/init" }, - { "path": "packages/migrate" }, - { "path": "packages/serve" }, - { "path": "packages/utils" } + { + "path": "packages/generators" + }, + { + "path": "packages/info" + }, + { + "path": "packages/init" + }, + { + "path": "packages/migrate" + }, + { + "path": "packages/serve" + }, + { + "path": "packages/utils" + }, + { + "path": "packages/configtest" + } ] }