diff --git a/.eslintignore b/.eslintignore index dd36e09449b..91718ffa7a2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,10 +2,7 @@ coverage .nyc_output node_modules dist -packages/configtest/lib -packages/generators/lib -packages/info/lib -packages/serve/lib +packages/*/lib test/**/dist/ test/**/bin/ test/**/binary/ diff --git a/.gitignore b/.gitignore index e052dc02222..d1c154e5dd0 100644 --- a/.gitignore +++ b/.gitignore @@ -55,7 +55,6 @@ packages/**/*.map # build files packages/**/lib packages/**/yarn.lock -!packages/webpack-cli/lib # test output files test/js/* diff --git a/.prettierignore b/.prettierignore index d85e46cbf30..6f73938b7ae 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,10 +2,7 @@ coverage .nyc_output node_modules dist -packages/configtest/lib -packages/generators/lib -packages/info/lib -packages/serve/lib +packages/*/lib test/**/dist/ test/**/bin/ test/**/binary/ diff --git a/package.json b/package.json index 209cb266dd6..4973c87891a 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "./packages/*" ], "scripts": { - "clean": "del-cli \"*.tsbuildinfo\" \"packages/**/*.tsbuildinfo\" \"packages/!(webpack-cli)/lib/!(*.tpl)\" \"**/.yo-rc.json\"", + "clean": "del-cli \"*.tsbuildinfo\" \"packages/**/*.tsbuildinfo\" \"packages/*/lib/!(*.tpl)\" \"**/.yo-rc.json\"", "prebuild": "yarn clean", "prebuild:ci": "yarn clean && node ./scripts/setupBuild.js", "build": "tsc --build", @@ -54,6 +54,7 @@ "@commitlint/config-conventional": "^16.0.0", "@types/jest": "^27.4.0", "@types/node": "^17.0.12", + "@types/rechoir": "^0.6.1", "@typescript-eslint/eslint-plugin": "^5.10.1", "@typescript-eslint/parser": "^5.10.1", "@webpack-cli/migrate": "^1.1.2", diff --git a/packages/configtest/src/index.ts b/packages/configtest/src/index.ts index 2edaaacc6ad..ab698ef2bb5 100644 --- a/packages/configtest/src/index.ts +++ b/packages/configtest/src/index.ts @@ -1,8 +1,9 @@ +import { IWebpackCLI } from "webpack-cli"; + const WEBPACK_PACKAGE = process.env.WEBPACK_PACKAGE || "webpack"; class ConfigTestCommand { - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any - async apply(cli: any): Promise { + async apply(cli: IWebpackCLI): Promise { await cli.makeCommand( { name: "configtest [config-path]", @@ -19,15 +20,14 @@ class ConfigTestCommand { const configPaths = new Set(); if (Array.isArray(config.options)) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - config.options.forEach((options: any) => { + config.options.forEach((options) => { if (config.path.get(options)) { - configPaths.add(config.path.get(options)); + configPaths.add(config.path.get(options) as string); } }); } else { if (config.path.get(config.options)) { - configPaths.add(config.path.get(config.options)); + configPaths.add(config.path.get(config.options) as string); } } @@ -39,14 +39,16 @@ class ConfigTestCommand { cli.logger.info(`Validate '${Array.from(configPaths).join(" ,")}'.`); try { - const error: Error[] = cli.webpack.validate(config.options); + // @ts-expect-error cli.webpack.validate returns void + const error: Error[] | undefined = cli.webpack.validate(config.options); // TODO remove this after drop webpack@4 if (error && error.length > 0) { + // @ts-expect-error schema argument is missing throw new cli.webpack.WebpackOptionsValidationError(error); } } catch (error) { - if (cli.isValidationError(error)) { + if (cli.isValidationError(error as Error)) { cli.logger.error((error as Error).message); } else { cli.logger.error(error); diff --git a/packages/configtest/tsconfig.json b/packages/configtest/tsconfig.json index 279b3e923cc..1c6d02ef88c 100644 --- a/packages/configtest/tsconfig.json +++ b/packages/configtest/tsconfig.json @@ -4,5 +4,10 @@ "outDir": "./lib", "rootDir": "./src" }, - "include": ["./src"] + "include": ["./src"], + "references": [ + { + "path": "../webpack-cli" + } + ] } diff --git a/packages/generators/src/index.ts b/packages/generators/src/index.ts index ef466daba92..e21f14901d2 100644 --- a/packages/generators/src/index.ts +++ b/packages/generators/src/index.ts @@ -4,10 +4,10 @@ import pluginGenerator from "./plugin-generator"; import addonGenerator from "./addon-generator"; import initGenerator from "./init-generator"; import type { InitOptions, LoaderOptions, PluginOptions } from "./types"; +import { IWebpackCLI } from "webpack-cli"; class GeneratorsCommand { - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any - async apply(cli: any): Promise { + async apply(cli: IWebpackCLI): Promise { await cli.makeCommand( { name: "init [generation-path]", diff --git a/packages/generators/src/types/index.ts b/packages/generators/src/types/index.ts index 42817691f0b..a27d7758cb3 100644 --- a/packages/generators/src/types/index.ts +++ b/packages/generators/src/types/index.ts @@ -1,5 +1,6 @@ import Generator from "yeoman-generator"; import path from "path"; +import { IWebpackCLI } from "webpack-cli"; export type InitOptions = { template: string; force?: boolean }; export type LoaderOptions = { template: string }; @@ -16,8 +17,7 @@ export type BaseCustomGeneratorOptions = { }; export type CustomGeneratorOptions = Generator.GeneratorOptions & { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - cli: any; + cli: IWebpackCLI; options: T; }; @@ -25,8 +25,7 @@ export class CustomGenerator< T extends BaseCustomGeneratorOptions = BaseCustomGeneratorOptions, Z extends CustomGeneratorOptions = CustomGeneratorOptions, > extends Generator { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public cli: any; + public cli: IWebpackCLI; public template: string; public dependencies: string[]; public force: boolean; diff --git a/packages/generators/src/utils/helpers.ts b/packages/generators/src/utils/helpers.ts index 871a33fa1ee..fe152bc46c0 100644 --- a/packages/generators/src/utils/helpers.ts +++ b/packages/generators/src/utils/helpers.ts @@ -37,7 +37,7 @@ export async function getInstaller(this: CustomGenerator): Promise { "packager", "Pick a package manager:", installers, - defaultPackager, + defaultPackager as string, this.force, ); return packager; diff --git a/packages/generators/tsconfig.json b/packages/generators/tsconfig.json index 173f75d3f62..991ba583366 100644 --- a/packages/generators/tsconfig.json +++ b/packages/generators/tsconfig.json @@ -5,5 +5,10 @@ "outDir": "lib", "rootDir": "src" }, - "include": ["src"] + "include": ["src"], + "references": [ + { + "path": "../webpack-cli" + } + ] } diff --git a/packages/info/src/index.ts b/packages/info/src/index.ts index 6a00ae3c467..1dad6e8296d 100644 --- a/packages/info/src/index.ts +++ b/packages/info/src/index.ts @@ -1,4 +1,5 @@ import envinfo from "envinfo"; +import { IWebpackCLI } from "webpack-cli"; interface Information { Binaries?: string[]; @@ -10,8 +11,7 @@ interface Information { } class InfoCommand { - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any - async apply(cli: any): Promise { + async apply(cli: IWebpackCLI): Promise { await cli.makeCommand( { name: "info", diff --git a/packages/info/tsconfig.json b/packages/info/tsconfig.json index 279b3e923cc..1c6d02ef88c 100644 --- a/packages/info/tsconfig.json +++ b/packages/info/tsconfig.json @@ -4,5 +4,10 @@ "outDir": "./lib", "rootDir": "./src" }, - "include": ["./src"] + "include": ["./src"], + "references": [ + { + "path": "../webpack-cli" + } + ] } diff --git a/packages/serve/src/index.ts b/packages/serve/src/index.ts index 88533aad4c0..b1ca77b75ba 100644 --- a/packages/serve/src/index.ts +++ b/packages/serve/src/index.ts @@ -1,15 +1,14 @@ // eslint-disable-next-line node/no-extraneous-import import type { Compiler, cli } from "webpack"; -import { devServerOptionsType } from "./types"; +import { IWebpackCLI, WebpackDevServerOptions } from "webpack-cli"; const WEBPACK_PACKAGE = process.env.WEBPACK_PACKAGE || "webpack"; const WEBPACK_DEV_SERVER_PACKAGE = process.env.WEBPACK_DEV_SERVER_PACKAGE || "webpack-dev-server"; type Problem = NonNullable>[0]; - +type PublicPath = WebpackDevServerOptions["output"]["publicPath"]; class ServeCommand { - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any - async apply(cli: any): Promise { + async apply(cli: IWebpackCLI): Promise { const loadDevServerOptions = () => { // TODO simplify this after drop webpack v4 and webpack-dev-server v3 // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -188,8 +187,7 @@ class ServeCommand { process.exit(2); } - const compilers = - typeof compiler.compilers !== "undefined" ? compiler.compilers : [compiler]; + const compilers = cli.isMultipleCompiler(compiler) ? compiler.compilers : [compiler]; const possibleCompilers = compilers.filter( (compiler: Compiler) => compiler.options.devServer, ); @@ -199,7 +197,7 @@ class ServeCommand { const usedPorts: number[] = []; for (const compilerForDevServer of compilersForDevServer) { - let devServerOptions: devServerOptionsType; + let devServerOptions: WebpackDevServerOptions; if (isNewDevServerCLIAPI) { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -260,18 +258,23 @@ class ServeCommand { process.exit(2); } - devServerOptions = result; + devServerOptions = result as WebpackDevServerOptions; } else { // TODO remove in the next major release const mergeOptions = ( - devServerOptions: devServerOptionsType, - devServerCliOptions: devServerOptionsType, - ): devServerOptionsType => { + devServerOptions: Partial, + devServerCliOptions: Partial, + ): WebpackDevServerOptions => { // CLI options should take precedence over devServer options, // and CLI options should have no default values included const options = { ...devServerOptions, ...devServerCliOptions }; - if (devServerOptions.client && devServerCliOptions.client) { + if ( + devServerOptions.client && + devServerCliOptions.client && + typeof devServerOptions.client === "object" && + typeof devServerCliOptions.client === "object" + ) { // the user could set some client options in their devServer config, // then also specify client options on the CLI options.client = { @@ -280,7 +283,7 @@ class ServeCommand { }; } - return options; + return options as WebpackDevServerOptions; }; devServerOptions = mergeOptions( @@ -291,8 +294,8 @@ class ServeCommand { // TODO remove in the next major release if (!isDevServer4) { - const getPublicPathOption = (): string => { - const normalizePublicPath = (publicPath: string): string => + const getPublicPathOption = (): PublicPath => { + const normalizePublicPath = (publicPath: PublicPath): PublicPath => typeof publicPath === "undefined" || publicPath === "auto" ? "/" : publicPath; if (options.outputPublicPath) { @@ -307,7 +310,7 @@ class ServeCommand { return normalizePublicPath(compilerForDevServer.options.output.publicPath); }; - const getStatsOption = (): string | boolean => { + const getStatsOption = (): WebpackDevServerOptions["stats"] => { if (options.stats) { return options.stats; } @@ -361,7 +364,7 @@ class ServeCommand { servers.push(server); } catch (error) { - if (cli.isValidationError(error)) { + if (cli.isValidationError(error as Error)) { cli.logger.error((error as Error).message); } else { cli.logger.error(error); diff --git a/packages/serve/src/types.ts b/packages/serve/src/types.ts deleted file mode 100644 index 62b589a7870..00000000000 --- a/packages/serve/src/types.ts +++ /dev/null @@ -1,111 +0,0 @@ -export type devServerOptionsType = { - allowedHosts?: string[] | allowedHostsEnum; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - bonjour?: boolean | Record; - client?: false | devServerClientOptions; - compress?: boolean; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - dev?: Record; // drop in dev-server v4 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - devMiddleware?: Record; - firewall?: boolean | string[]; - headers?: - | Record // eslint-disable-next-line @typescript-eslint/no-explicit-any - | ((request: any, response: any, middlewareContext: any) => Record); - historyApiFallback?: boolean | Record; - host?: string | null | hostEnum; - hot?: boolean | hotOptionEnum; - http2?: boolean; - https?: boolean | Record; - injectClient?: boolean | (() => void); - injectHot?: boolean | (() => void); - ipc?: string | true; - liveReload?: boolean; - onAfterSetupMiddleware?: () => void; - onBeforeSetupMiddleware?: () => void; - onListening?: () => void; - open?: string | boolean | openOptionObject; - openPage?: string | string[]; - overlay?: boolean | Record; - port?: number | string | null; - profile?: boolean; - progress?: boolean; - proxy?: Record | (Record | (() => void))[]; - public?: string; - setupExitSignals?: boolean; - static?: boolean | string | Record | (string | Record)[]; - transportMode?: Record | string; - useLocalIp?: boolean; - publicPath?: string | (() => void); - stats?: string | boolean; - watchFiles?: string | Record; - webSocketServer?: - | false - | string - | transportModeEnum - // eslint-disable-next-line @typescript-eslint/no-explicit-any - | (() => any) - | Record - | (Record | (() => void))[]; -}; - -enum hotOptionEnum { - only = "only", -} - -enum hostEnum { - LocalIp = "local-ip", - LocalIpv4 = "local-ipv4", - LocalIpv6 = "local-ipv6", -} - -enum allowedHostsEnum { - Auto = "auto", - All = "all", -} - -enum transportModeEnum { - SockJS = "sockjs", - Ws = "ws", -} - -type devServerClientOptions = { - host?: string; - path?: string; - port?: string | number | null; - needClientEntry?: boolean | (() => void); - needHotEntry?: boolean | (() => void); - logging?: devServerClientLogging; - overlay?: boolean | clientOverlay; - progress?: boolean; - webSocketTransport?: string | transportModeEnum; - webSocketURL?: string | webSocketURLOptions; -}; - -type webSocketURLOptions = { - hostname?: string; - pathname?: string; - port?: string | number; - password?: string; - protocol?: string | "auto"; - username?: string; -}; - -type openOptionObject = { - target?: string; - app?: string; -}; - -type clientOverlay = { - errors?: boolean; - warnings?: boolean; -}; - -enum devServerClientLogging { - none = "none", - error = "error", - warn = "warn", - info = "info", - log = "log", - verbose = "verbose", -} diff --git a/packages/serve/tsconfig.json b/packages/serve/tsconfig.json index 279b3e923cc..1c6d02ef88c 100644 --- a/packages/serve/tsconfig.json +++ b/packages/serve/tsconfig.json @@ -4,5 +4,10 @@ "outDir": "./lib", "rootDir": "./src" }, - "include": ["./src"] + "include": ["./src"], + "references": [ + { + "path": "../webpack-cli" + } + ] } diff --git a/packages/webpack-cli/lib/index.js b/packages/webpack-cli/lib/index.js deleted file mode 100644 index 4dfb248b8fb..00000000000 --- a/packages/webpack-cli/lib/index.js +++ /dev/null @@ -1,5 +0,0 @@ -const CLI = require("./webpack-cli"); - -module.exports = CLI; -// TODO remove after drop `@webpack-cli/migrate` -module.exports.utils = { logger: console }; diff --git a/packages/webpack-cli/lib/bootstrap.js b/packages/webpack-cli/src/bootstrap.ts similarity index 51% rename from packages/webpack-cli/lib/bootstrap.js rename to packages/webpack-cli/src/bootstrap.ts index ef63a0b15da..347c65e250c 100644 --- a/packages/webpack-cli/lib/bootstrap.js +++ b/packages/webpack-cli/src/bootstrap.ts @@ -1,8 +1,11 @@ +import { IWebpackCLI } from "./types"; + +// eslint-disable-next-line @typescript-eslint/no-var-requires const WebpackCLI = require("./webpack-cli"); -const runCLI = async (args) => { +const runCLI = async (args: Parameters[0]) => { // Create a new instance of the CLI object - const cli = new WebpackCLI(); + const cli: IWebpackCLI = new WebpackCLI(); try { await cli.run(args); diff --git a/packages/webpack-cli/src/index.ts b/packages/webpack-cli/src/index.ts new file mode 100644 index 00000000000..5f4a8cd0a6c --- /dev/null +++ b/packages/webpack-cli/src/index.ts @@ -0,0 +1,9 @@ +import { IWebpackCLI } from "./types"; +export * from "./types"; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const CLI: IWebpackCLI = require("./webpack-cli"); + +module.exports = CLI; +// TODO remove after drop `@webpack-cli/migrate` +module.exports.utils = { logger: console }; diff --git a/packages/webpack-cli/lib/plugins/CLIPlugin.js b/packages/webpack-cli/src/plugins/CLIPlugin.ts similarity index 73% rename from packages/webpack-cli/lib/plugins/CLIPlugin.js rename to packages/webpack-cli/src/plugins/CLIPlugin.ts index 2b1c08302f1..215049c655e 100644 --- a/packages/webpack-cli/lib/plugins/CLIPlugin.js +++ b/packages/webpack-cli/src/plugins/CLIPlugin.ts @@ -1,9 +1,15 @@ -class CLIPlugin { - constructor(options) { +import { Compiler } from "webpack"; +import { CLIPluginOptions } from "../types"; + +export class CLIPlugin { + logger!: ReturnType; + options: CLIPluginOptions; + + constructor(options: CLIPluginOptions) { this.options = options; } - setupHotPlugin(compiler) { + setupHotPlugin(compiler: Compiler) { const { HotModuleReplacementPlugin } = compiler.webpack || require("webpack"); const hotModuleReplacementPlugin = Boolean( compiler.options.plugins.find((plugin) => plugin instanceof HotModuleReplacementPlugin), @@ -14,14 +20,14 @@ class CLIPlugin { } } - setupPrefetchPlugin(compiler) { + setupPrefetchPlugin(compiler: Compiler) { const { PrefetchPlugin } = compiler.webpack || require("webpack"); new PrefetchPlugin(null, this.options.prefetch).apply(compiler); } - async setupBundleAnalyzerPlugin(compiler) { - // eslint-disable-next-line node/no-extraneous-require + async setupBundleAnalyzerPlugin(compiler: Compiler) { + // eslint-disable-next-line node/no-extraneous-require,@typescript-eslint/no-var-requires const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); const bundleAnalyzerPlugin = Boolean( compiler.options.plugins.find((plugin) => plugin instanceof BundleAnalyzerPlugin), @@ -32,7 +38,7 @@ class CLIPlugin { } } - setupProgressPlugin(compiler) { + setupProgressPlugin(compiler: Compiler) { const { ProgressPlugin } = compiler.webpack || require("webpack"); const progressPlugin = Boolean( compiler.options.plugins.find((plugin) => plugin instanceof ProgressPlugin), @@ -45,10 +51,10 @@ class CLIPlugin { } } - setupHelpfulOutput(compiler) { + setupHelpfulOutput(compiler: Compiler) { const pluginName = "webpack-cli"; const getCompilationName = () => (compiler.name ? `'${compiler.name}'` : ""); - const logCompilation = (message) => { + const logCompilation = (message: string) => { if (process.env.WEBPACK_CLI_START_FINISH_FORCE_LOG) { process.stderr.write(message); } else { @@ -93,20 +99,23 @@ class CLIPlugin { this.logger.log(`Changed time is ${date} (timestamp is ${changeTime})`); }); - (compiler.webpack ? compiler.hooks.afterDone : compiler.hooks.done).tap(pluginName, () => { - const name = getCompilationName(); + ((compiler as Partial).webpack ? compiler.hooks.afterDone : compiler.hooks.done).tap( + pluginName, + () => { + const name = getCompilationName(); - logCompilation(`Compiler${name ? ` ${name}` : ""} finished`); + logCompilation(`Compiler${name ? ` ${name}` : ""} finished`); - process.nextTick(() => { - if (compiler.watchMode) { - this.logger.log(`Compiler${name ? `${name}` : ""} is watching files for updates...`); - } - }); - }); + process.nextTick(() => { + if (compiler.watchMode) { + this.logger.log(`Compiler${name ? `${name}` : ""} is watching files for updates...`); + } + }); + }, + ); } - apply(compiler) { + apply(compiler: Compiler) { this.logger = compiler.getInfrastructureLogger("webpack-cli"); if (this.options.progress) { diff --git a/packages/webpack-cli/src/types.ts b/packages/webpack-cli/src/types.ts new file mode 100644 index 00000000000..3931e96987a --- /dev/null +++ b/packages/webpack-cli/src/types.ts @@ -0,0 +1,356 @@ +import webpack, { + EntryOptions, + Stats, + Configuration, + WebpackError, + StatsOptions, + WebpackOptionsNormalized, + Compiler, + MultiCompiler, + Problem, + Argument, + AssetEmittedInfo, + FileCacheOptions, +} from "webpack"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore extraneous import is intended +// eslint-disable-next-line node/no-extraneous-import +import { ClientConfiguration, Configuration as DevServerConfig } from "webpack-dev-server"; + +import { Colorette } from "colorette"; +import { Command, CommandOptions, OptionConstructor, ParseOptions } from "commander"; +import { prepare } from "rechoir"; +import { stringifyStream } from "@discoveryjs/json-ext"; + +/** + * Webpack CLI + */ + +interface IWebpackCLI { + colors: WebpackCLIColors; + logger: WebpackCLILogger; + isColorSupportChanged: boolean | undefined; + webpack: typeof webpack; + builtInOptionsCache: WebpackCLIBuiltInOption[] | undefined; + program: WebpackCLICommand; + isMultipleCompiler(compiler: WebpackCompiler): compiler is MultiCompiler; + isPromise(value: Promise): value is Promise; + isFunction(value: unknown): value is CallableFunction; + getLogger(): WebpackCLILogger; + createColors(useColors?: boolean): WebpackCLIColors; + toKebabCase: StringFormatter; + capitalizeFirstLetter: StringFormatter; + checkPackageExists(packageName: string): boolean; + getAvailablePackageManagers(): PackageManager[]; + getDefaultPackageManager(): PackageManager | undefined; + doInstall(packageName: string, options?: PackageInstallOptions): Promise; + loadJSONFile(path: Path, handleError: boolean): Promise; + tryRequireThenImport(module: ModuleName, handleError: boolean): Promise; + makeCommand( + commandOptions: WebpackCLIOptions, + options: WebpackCLICommandOptions, + action: CommandAction, + ): Promise; + makeOption(command: WebpackCLICommand, option: WebpackCLIBuiltInOption): void; + run( + args: Parameters[0], + parseOptions?: ParseOptions, + ): Promise; + getBuiltInOptions(): WebpackCLIBuiltInOption[]; + loadWebpack(handleError?: boolean): Promise; + loadConfig(options: Partial): Promise; + buildConfig( + config: WebpackCLIConfig, + options: WebpackDevServerOptions, + ): Promise; + isValidationError(error: Error): error is WebpackError; + createCompiler( + options: Partial, + callback?: Callback<[Error | undefined, WebpackCLIStats | undefined]>, + ): Promise; + needWatchStdin(compiler: Compiler | MultiCompiler): boolean; + runWebpack(options: WebpackRunOptions, isWatchCommand: boolean): Promise; +} + +interface WebpackCLIColors extends Colorette { + isColorSupported: boolean; +} + +interface WebpackCLILogger { + error: LogHandler; + warn: LogHandler; + info: LogHandler; + success: LogHandler; + log: LogHandler; + raw: LogHandler; +} + +interface WebpackCLICommandOption extends CommanderOption { + helpLevel?: "minimum" | "verbose"; +} + +interface WebpackCLIConfig { + options: WebpackConfiguration | WebpackConfiguration[]; + path: WeakMap; +} + +interface WebpackCLICommand extends Command { + pkg: string | undefined; + forHelp: boolean | undefined; + options: WebpackCLICommandOption[]; + _args: WebpackCLICommandOption[]; +} + +interface WebpackCLIStats extends Stats { + presetToOptions?: (item: string | boolean) => StatsOptions; +} + +type WebpackCLIMainOption = Pick< + WebpackCLIBuiltInOption, + "description" | "defaultValue" | "multiple" +> & { + flags: string; + type: Set; +}; + +interface WebpackCLIOptions extends CommandOptions { + name: string; + alias: string | string[]; + description?: string; + usage?: string; + dependencies?: string[]; + pkg?: string; + argsDescription?: { [argName: string]: string }; +} + +type WebpackCLICommandOptions = + | WebpackCLIBuiltInOption[] + | (() => Promise); + +interface WebpackCLIBuiltInFlag { + name: string; + alias?: string; + type?: ( + value: string, + previous: Record, + ) => Record; + configs?: Partial[]; + negative?: boolean; + multiple?: boolean; + description: string; + describe?: string; + negatedDescription?: string; + defaultValue?: string; +} + +interface WebpackCLIBuiltInOption extends WebpackCLIBuiltInFlag { + hidden?: boolean; + group?: "core"; + helpLevel?: "minimum" | "verbose"; +} + +type WebpackCLIExternalCommandInfo = Pick & { + pkg: string; +}; + +/** + * Webpack dev server + */ + +type WebpackDevServerOptions = DevServerConfig & + WebpackConfiguration & + ClientConfiguration & + AssetEmittedInfo & + WebpackOptionsNormalized & + FileCacheOptions & + Argv & { + nodeEnv?: "string"; + watchOptionsStdin?: boolean; + progress?: boolean | "profile" | undefined; + analyze?: boolean; + prefetch?: string; + json?: boolean; + entry: EntryOptions; + merge?: boolean; + config: string[]; + configName?: string[]; + argv: Argv; + }; + +type Callback = (...args: T) => void; + +/** + * Webpack + */ + +type WebpackConfiguration = Configuration; +type ConfigOptions = PotentialPromise; +type CallableOption = (env: Env | undefined, argv: Argv) => WebpackConfiguration; +type WebpackCompiler = Compiler | MultiCompiler; + +type FlagType = boolean | "enum" | "string" | "path" | "number" | "boolean" | "RegExp" | "reset"; + +type FlagConfig = { + negatedDescription: string; + type: FlagType; + values: FlagType[]; +}; + +type FileSystemCacheOptions = WebpackConfiguration & { + cache: FileCacheOptions & { defaultConfig: string[] }; +}; + +type ProcessedArguments = Record; + +type MultipleCompilerStatsOptions = StatsOptions & { children: StatsOptions[] }; +type CommandAction = Parameters[0]; + +interface WebpackRunOptions extends WebpackOptionsNormalized { + json?: boolean; + argv?: Argv; + env: Env; +} + +/** + * Package management + */ + +type PackageManager = "pnpm" | "yarn" | "npm"; +interface PackageInstallOptions { + preMessage?: () => void; +} +interface BasicPackageJsonContent { + name: string; + version: string; + description: string; + license: string; +} + +/** + * Webpack V4 + */ + +type WebpackV4LegacyStats = Required; +interface WebpackV4Compiler extends Compiler { + compiler: Compiler; +} + +/** + * Plugins and util types + */ + +interface CLIPluginOptions { + configPath?: string; + helpfulOutput: boolean; + hot?: boolean | "only"; + progress?: boolean | "profile"; + prefetch?: string; + analyze?: boolean; +} + +type BasicPrimitive = string | boolean | number; +type Instantiable = { + new (...args: ConstructorParameters): InstanceType; +}; +type PotentialPromise = T | Promise; +type ModuleName = string; +type Path = string; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type LogHandler = (value: any) => void; +type StringFormatter = (value: string) => string; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +interface Argv extends Record { + env?: Env; +} + +interface Env { + WEBPACK_BUNDLE?: boolean; + WEBPACK_BUILD?: boolean; + WEBPACK_WATCH?: boolean; + WEBPACK_SERVE?: boolean; + WEBPACK_PACKAGE?: string; + WEBPACK_DEV_SERVER_PACKAGE?: string; +} + +type DynamicImport = (url: string) => Promise<{ default: T }>; + +interface ImportLoaderError extends Error { + code?: string; +} + +/** + * External libraries types + */ + +type CommanderOption = InstanceType; + +interface Rechoir { + prepare: typeof prepare; +} + +interface JsonExt { + stringifyStream: typeof stringifyStream; +} + +interface RechoirError extends Error { + failures: RechoirError[]; + error: Error; +} + +interface PromptOptions { + message: string; + defaultResponse: string; + stream: NodeJS.WritableStream; +} + +export { + IWebpackCLI, + WebpackCLICommandOption, + WebpackCLIBuiltInOption, + WebpackCLIBuiltInFlag, + WebpackCLIColors, + WebpackCLIStats, + WebpackCLIConfig, + WebpackCLIExternalCommandInfo, + WebpackCLIOptions, + WebpackCLICommand, + WebpackCLICommandOptions, + WebpackCLIMainOption, + WebpackCLILogger, + WebpackV4LegacyStats, + WebpackDevServerOptions, + WebpackRunOptions, + WebpackV4Compiler, + WebpackCompiler, + WebpackConfiguration, + Argv, + Argument, + BasicPrimitive, + BasicPackageJsonContent, + CallableOption, + Callback, + CLIPluginOptions, + CommandAction, + CommanderOption, + CommandOptions, + ConfigOptions, + DynamicImport, + FileSystemCacheOptions, + FlagConfig, + ImportLoaderError, + Instantiable, + JsonExt, + ModuleName, + MultipleCompilerStatsOptions, + PackageInstallOptions, + PackageManager, + Path, + ProcessedArguments, + PromptOptions, + Problem, + PotentialPromise, + Rechoir, + RechoirError, +}; diff --git a/packages/webpack-cli/lib/utils/dynamic-import-loader.js b/packages/webpack-cli/src/utils/dynamic-import-loader.ts similarity index 53% rename from packages/webpack-cli/lib/utils/dynamic-import-loader.js rename to packages/webpack-cli/src/utils/dynamic-import-loader.ts index c1221bcaffa..8e9eabaca84 100644 --- a/packages/webpack-cli/lib/utils/dynamic-import-loader.js +++ b/packages/webpack-cli/src/utils/dynamic-import-loader.ts @@ -1,4 +1,6 @@ -function dynamicImportLoader() { +import { DynamicImport } from "../types"; + +function dynamicImportLoader(): DynamicImport | null { let importESM; try { @@ -7,7 +9,7 @@ function dynamicImportLoader() { importESM = null; } - return importESM; + return importESM as DynamicImport; } module.exports = dynamicImportLoader; diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/src/webpack-cli.ts similarity index 78% rename from packages/webpack-cli/lib/webpack-cli.js rename to packages/webpack-cli/src/webpack-cli.ts index b28ba64c1bb..27b1adb51af 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/src/webpack-cli.ts @@ -1,3 +1,60 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +import { + IWebpackCLI, + WebpackCLICommandOption, + WebpackCLIBuiltInOption, + WebpackCLIBuiltInFlag, + WebpackCLIColors, + WebpackCLIStats, + WebpackCLIConfig, + WebpackCLIExternalCommandInfo, + WebpackCLIOptions, + WebpackCLICommand, + WebpackCLICommandOptions, + WebpackCLIMainOption, + WebpackCLILogger, + WebpackV4LegacyStats, + WebpackDevServerOptions, + WebpackRunOptions, + WebpackV4Compiler, + WebpackCompiler, + WebpackConfiguration, + Argv, + BasicPrimitive, + BasicPackageJsonContent, + CallableOption, + Callback, + CLIPluginOptions, + CommandAction, + ConfigOptions, + DynamicImport, + FileSystemCacheOptions, + FlagConfig, + ImportLoaderError, + Instantiable, + JsonExt, + ModuleName, + MultipleCompilerStatsOptions, + PackageInstallOptions, + PackageManager, + Path, + ProcessedArguments, + PromptOptions, + PotentialPromise, + Rechoir, + RechoirError, + Argument, + Problem, +} from "./types"; + +import webpackMerge from "webpack-merge"; +import webpack from "webpack"; +import { Compiler, MultiCompiler, WebpackError, StatsOptions } from "webpack"; +import { stringifyStream } from "@discoveryjs/json-ext"; +import { Help, ParseOptions } from "commander"; + +import { CLIPlugin as CLIPluginClass } from "./plugins/CLIPlugin"; + const fs = require("fs"); const path = require("path"); const { pathToFileURL } = require("url"); @@ -8,7 +65,13 @@ const { program, Option } = require("commander"); const WEBPACK_PACKAGE = process.env.WEBPACK_PACKAGE || "webpack"; const WEBPACK_DEV_SERVER_PACKAGE = process.env.WEBPACK_DEV_SERVER_PACKAGE || "webpack-dev-server"; -class WebpackCLI { +class WebpackCLI implements IWebpackCLI { + colors: WebpackCLIColors; + logger: WebpackCLILogger; + isColorSupportChanged: boolean | undefined; + builtInOptionsCache: WebpackCLIBuiltInOption[] | undefined; + webpack!: typeof webpack; + program: WebpackCLICommand; constructor() { this.colors = this.createColors(); this.logger = this.getLogger(); @@ -23,7 +86,17 @@ class WebpackCLI { }); } - capitalizeFirstLetter(str) { + isMultipleCompiler(compiler: WebpackCompiler): compiler is MultiCompiler { + return (compiler as MultiCompiler).compilers as unknown as boolean; + } + isPromise(value: Promise): value is Promise { + return typeof (value as unknown as Promise).then === "function"; + } + isFunction(value: unknown): value is CallableFunction { + return typeof value === "function"; + } + + capitalizeFirstLetter(str: string | unknown): string { if (typeof str !== "string") { return ""; } @@ -31,11 +104,11 @@ class WebpackCLI { return str.charAt(0).toUpperCase() + str.slice(1); } - toKebabCase(str) { + toKebabCase(str: string): string { return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); } - createColors(useColor) { + createColors(useColor?: boolean): WebpackCLIColors { const { createColors, isColorSupported } = require("colorette"); let shouldUseColor; @@ -49,7 +122,7 @@ class WebpackCLI { return { ...createColors({ useColor: shouldUseColor }), isColorSupported: shouldUseColor }; } - getLogger() { + getLogger(): WebpackCLILogger { return { error: (val) => console.error(`[webpack-cli] ${this.colors.red(util.format(val))}`), warn: (val) => console.warn(`[webpack-cli] ${this.colors.yellow(val)}`), @@ -60,7 +133,7 @@ class WebpackCLI { }; } - checkPackageExists(packageName) { + checkPackageExists(packageName: string): boolean { if (process.versions.pnp) { return true; } @@ -80,10 +153,10 @@ class WebpackCLI { return false; } - getAvailablePackageManagers() { + getAvailablePackageManagers(): PackageManager[] { const { sync } = require("execa"); - const installers = ["npm", "yarn", "pnpm"]; - const hasPackageManagerInstalled = (packageManager) => { + const installers: PackageManager[] = ["npm", "yarn", "pnpm"]; + const hasPackageManagerInstalled = (packageManager: PackageManager) => { try { sync(packageManager, ["--version"]); @@ -105,7 +178,7 @@ class WebpackCLI { return availableInstallers; } - getDefaultPackageManager() { + getDefaultPackageManager(): PackageManager | undefined { const { sync } = require("execa"); const hasLocalNpm = fs.existsSync(path.resolve(process.cwd(), "package-lock.json")); @@ -158,7 +231,7 @@ class WebpackCLI { } } - async doInstall(packageName, options = {}) { + async doInstall(packageName: string, options: PackageInstallOptions = {}): Promise { const packageManager = this.getDefaultPackageManager(); if (!packageManager) { @@ -178,7 +251,7 @@ class WebpackCLI { packageName, ].join(" ")}`; - const prompt = ({ message, defaultResponse, stream }) => { + const prompt = ({ message, defaultResponse, stream }: PromptOptions) => { const readline = require("readline"); const rl = readline.createInterface({ input: process.stdin, @@ -186,7 +259,7 @@ class WebpackCLI { }); return new Promise((resolve) => { - rl.question(`${message} `, (answer) => { + rl.question(`${message} `, (answer: string) => { // Close the stream rl.close(); @@ -217,7 +290,7 @@ class WebpackCLI { } catch (error) { this.logger.error(error); - process.exit(error); + process.exit(error as number); } if (needInstall) { @@ -237,15 +310,17 @@ class WebpackCLI { process.exit(2); } - async tryRequireThenImport(module, handleError = true) { + async tryRequireThenImport(module: ModuleName, handleError = true): Promise { let result; try { result = require(module); } catch (error) { - const dynamicImportLoader = require("./utils/dynamic-import-loader")(); + const dynamicImportLoader: null | DynamicImport = + require("./utils/dynamic-import-loader")(); if ( - (error.code === "ERR_REQUIRE_ESM" || process.env.WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG) && + ((error as ImportLoaderError).code === "ERR_REQUIRE_ESM" || + process.env.WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG) && pathToFileURL && dynamicImportLoader ) { @@ -273,7 +348,7 @@ class WebpackCLI { return result || {}; } - loadJSONFile(pathToFile, handleError = true) { + loadJSONFile(pathToFile: Path, handleError = true): T { let result; try { @@ -290,11 +365,15 @@ class WebpackCLI { return result; } - async makeCommand(commandOptions, options, action) { + async makeCommand( + commandOptions: WebpackCLIOptions, + options: WebpackCLICommandOptions, + action: CommandAction, + ): Promise { const alreadyLoaded = this.program.commands.find( (command) => command.name() === commandOptions.name.split(" ")[0] || - command.aliases().includes(commandOptions.alias), + command.aliases().includes(commandOptions.alias as string), ); if (alreadyLoaded) { @@ -305,7 +384,7 @@ class WebpackCLI { noHelp: commandOptions.noHelp, hidden: commandOptions.hidden, isDefault: commandOptions.isDefault, - }); + }) as WebpackCLICommand; if (commandOptions.description) { command.description(commandOptions.description, commandOptions.argsDescription); @@ -318,7 +397,7 @@ class WebpackCLI { if (Array.isArray(commandOptions.alias)) { command.aliases(commandOptions.alias); } else { - command.alias(commandOptions.alias); + command.alias(commandOptions.alias as string); } if (commandOptions.pkg) { @@ -372,7 +451,7 @@ class WebpackCLI { if (options) { if (typeof options === "function") { - if (forHelp && !allDependenciesInstalled) { + if (forHelp && !allDependenciesInstalled && commandOptions.dependencies) { command.description( `${ commandOptions.description @@ -396,17 +475,16 @@ class WebpackCLI { return command; } - makeOption(command, option) { - let mainOption; + makeOption(command: WebpackCLICommand, option: WebpackCLIBuiltInOption) { + let mainOption: WebpackCLIMainOption; let negativeOption; if (option.configs) { let needNegativeOption = false; let negatedDescription; - const mainOptionType = new Set(); + const mainOptionType: WebpackCLIMainOption["type"] = new Set(); option.configs.forEach((config) => { - // Possible value: "enum" | "string" | "path" | "number" | "boolean" | "RegExp" | "reset" switch (config.type) { case "reset": mainOptionType.add(Boolean); @@ -430,7 +508,7 @@ class WebpackCLI { case "enum": { let hasFalseEnum = false; - const enumTypes = config.values.map((value) => { + const enumTypes = (config.values || []).map((value) => { switch (typeof value) { case "string": mainOptionType.add(String); @@ -506,14 +584,19 @@ class WebpackCLI { if (mainOption.type.has(Number)) { let skipDefault = true; - const optionForCommand = new Option(mainOption.flags, mainOption.description) - .argParser((value, prev = []) => { + const optionForCommand: WebpackCLICommandOption = new Option( + mainOption.flags, + mainOption.description, + ) + .argParser((value: string, prev = []) => { if (mainOption.defaultValue && mainOption.multiple && skipDefault) { prev = []; skipDefault = false; } - return mainOption.multiple ? [].concat(prev).concat(Number(value)) : Number(value); + return mainOption.multiple + ? ([] as number[]).concat(prev).concat(Number(value)) + : Number(value); }) .default(mainOption.defaultValue); @@ -523,14 +606,17 @@ class WebpackCLI { } else if (mainOption.type.has(String)) { let skipDefault = true; - const optionForCommand = new Option(mainOption.flags, mainOption.description) - .argParser((value, prev = []) => { + const optionForCommand: WebpackCLICommandOption = new Option( + mainOption.flags, + mainOption.description, + ) + .argParser((value: string, prev = []) => { if (mainOption.defaultValue && mainOption.multiple && skipDefault) { prev = []; skipDefault = false; } - return mainOption.multiple ? [].concat(prev).concat(value) : value; + return mainOption.multiple ? ([] as string[]).concat(prev).concat(value) : value; }) .default(mainOption.defaultValue); @@ -562,7 +648,7 @@ class WebpackCLI { mainOption.description, mainOption.defaultValue, ) - .argParser((value, prev = []) => { + .argParser((value: string, prev = []) => { if (mainOption.defaultValue && mainOption.multiple && skipDefault) { prev = []; skipDefault = false; @@ -572,12 +658,14 @@ class WebpackCLI { const numberValue = Number(value); if (!isNaN(numberValue)) { - return mainOption.multiple ? [].concat(prev).concat(numberValue) : numberValue; + return mainOption.multiple + ? ([] as number[]).concat(prev).concat(numberValue) + : numberValue; } } if (mainOption.type.has(String)) { - return mainOption.multiple ? [].concat(prev).concat(value) : value; + return mainOption.multiple ? ([] as string[]).concat(prev).concat(value) : value; } return value; @@ -606,7 +694,7 @@ class WebpackCLI { } } - getBuiltInOptions() { + getBuiltInOptions(): WebpackCLIBuiltInOption[] { if (this.builtInOptionsCache) { return this.builtInOptionsCache; } @@ -630,7 +718,7 @@ class WebpackCLI { "node-env", ]; - const builtInFlags = [ + const builtInFlags: WebpackCLIBuiltInFlag[] = [ // For configs { name: "config", @@ -667,7 +755,10 @@ class WebpackCLI { // Complex configs { name: "env", - type: (value, previous = {}) => { + type: ( + value: string, + previous: Record = {}, + ): Record => { // for https://github.com/webpack/webpack-cli/issues/2642 if (value.endsWith("=")) { value.concat('""'); @@ -696,7 +787,7 @@ class WebpackCLI { } } - prevRef = prevRef[someKey]; + prevRef = prevRef[someKey] as Record; }); return previous; @@ -895,6 +986,7 @@ class WebpackCLI { if (inBuiltIn) { return { ...meta, + // @ts-expect-error this might be overwritten name: flag, group: "core", ...inBuiltIn, @@ -906,17 +998,18 @@ class WebpackCLI { }) : []; - const options = [] + const options: WebpackCLIBuiltInOption[] = ([] as WebpackCLIBuiltInFlag[]) .concat( builtInFlags.filter( (builtInFlag) => !coreFlags.find((coreFlag) => builtInFlag.name === coreFlag.name), ), ) .concat(coreFlags) - .map((option) => { - option.helpLevel = minimumHelpFlags.includes(option.name) ? "minimum" : "verbose"; - - return option; + .map((option): WebpackCLIBuiltInOption => { + (option as WebpackCLIBuiltInOption).helpLevel = minimumHelpFlags.includes(option.name) + ? "minimum" + : "verbose"; + return option as WebpackCLIBuiltInOption; }); this.builtInOptionsCache = options; @@ -925,10 +1018,10 @@ class WebpackCLI { } async loadWebpack(handleError = true) { - return this.tryRequireThenImport(WEBPACK_PACKAGE, handleError); + return this.tryRequireThenImport(WEBPACK_PACKAGE, handleError); } - async run(args, parseOptions) { + async run(args: Parameters[0], parseOptions: ParseOptions) { // Built-in internal commands const buildCommandOptions = { name: "build [entries...]", @@ -956,7 +1049,7 @@ class WebpackCLI { description: "Display help for commands and options.", }; // Built-in external commands - const externalBuiltInCommandsInfo = [ + const externalBuiltInCommandsInfo: WebpackCLIExternalCommandInfo[] = [ { name: "serve [entries...]", alias: ["server", "s"], @@ -1001,14 +1094,14 @@ class WebpackCLI { helpCommandOptions, ...externalBuiltInCommandsInfo, ]; - const getCommandName = (name) => name.split(" ")[0]; - const isKnownCommand = (name) => + const getCommandName = (name: string) => name.split(" ")[0]; + const isKnownCommand = (name: string) => knownCommands.find( (command) => getCommandName(command.name) === name || (Array.isArray(command.alias) ? command.alias.includes(name) : command.alias === name), ); - const isCommand = (input, commandOptions) => { + const isCommand = (input: string, commandOptions: WebpackCLIOptions) => { const longName = getCommandName(commandOptions.name); if (input === longName) { @@ -1025,12 +1118,12 @@ class WebpackCLI { return false; }; - const findCommandByName = (name) => + const findCommandByName = (name: string) => this.program.commands.find( (command) => name === command.name() || command.aliases().includes(name), ); - const isOption = (value) => value.startsWith("-"); - const isGlobalOption = (value) => + const isOption = (value: string): boolean => value.startsWith("-"); + const isGlobalOption = (value: string) => value === "--color" || value === "--no-color" || value === "-v" || @@ -1038,7 +1131,10 @@ class WebpackCLI { value === "-h" || value === "--help"; - const loadCommandByName = async (commandName, allowToInstall = false) => { + const loadCommandByName = async ( + commandName: WebpackCLIExternalCommandInfo["name"], + allowToInstall = false, + ) => { const isBuildCommandUsed = isCommand(commandName, buildCommandOptions); const isWatchCommandUsed = isCommand(commandName, watchCommandOptions); @@ -1062,9 +1158,11 @@ class WebpackCLI { ); } else if (isCommand(commandName, helpCommandOptions)) { // Stub for the `help` command + // eslint-disable-next-line @typescript-eslint/no-empty-function this.makeCommand(helpCommandOptions, [], () => {}); } else if (isCommand(commandName, versionCommandOptions)) { // Stub for the `version` command + // eslint-disable-next-line @typescript-eslint/no-empty-function this.makeCommand(versionCommandOptions, [], () => {}); } else { const builtInExternalCommandInfo = externalBuiltInCommandsInfo.find( @@ -1075,7 +1173,7 @@ class WebpackCLI { : externalBuiltInCommandInfo.alias === commandName), ); - let pkg; + let pkg: string; if (builtInExternalCommandInfo) { ({ pkg } = builtInExternalCommandInfo); @@ -1100,7 +1198,7 @@ class WebpackCLI { let loadedCommand; try { - loadedCommand = await this.tryRequireThenImport(pkg, false); + loadedCommand = await this.tryRequireThenImport void>>(pkg, false); } catch (error) { // Ignore, command is not installed @@ -1136,7 +1234,7 @@ class WebpackCLI { } if (error.code === "commander.unknownOption") { - let name = error.message.match(/'(.+)'/); + let name = error.message.match(/'(.+)'/) as string | null; if (name) { name = name[1].substr(2); @@ -1162,8 +1260,8 @@ class WebpackCLI { const levenshtein = require("fastest-levenshtein"); - command.options.forEach((option) => { - if (!option.hidden && levenshtein.distance(name, option.long.slice(2)) < 3) { + (command as WebpackCLICommand).options.forEach((option) => { + if (!option.hidden && levenshtein.distance(name, option.long?.slice(2)) < 3) { this.logger.error(`Did you mean '--${option.name()}'?`); } }); @@ -1182,9 +1280,11 @@ class WebpackCLI { }); // Default `--color` and `--no-color` options - const cli = this; + // eslint-disable-next-line @typescript-eslint/no-this-alias + const cli: IWebpackCLI = this; this.program.option("--color", "Enable colors on console."); this.program.on("option:color", function () { + // @ts-expect-error shadowing 'this' is intended const { color } = this.opts(); cli.isColorSupportChanged = color; @@ -1192,6 +1292,7 @@ class WebpackCLI { }); this.program.option("--no-color", "Disable colors on console."); this.program.on("option:no-color", function () { + // @ts-expect-error shadowing 'this' is intended const { color } = this.opts(); cli.isColorSupportChanged = color; @@ -1200,7 +1301,7 @@ class WebpackCLI { // Make `-v, --version` options // Make `version|v [commands...]` command - const outputVersion = async (options) => { + const outputVersion = async (options: string[]) => { // Filter `bundle`, `watch`, `version` and `help` commands const possibleCommandNames = options.filter( (option) => @@ -1226,7 +1327,7 @@ class WebpackCLI { ); for (const possibleCommandName of possibleCommandNames) { - const foundCommand = findCommandByName(possibleCommandName); + const foundCommand = findCommandByName(possibleCommandName) as WebpackCLICommand; if (!foundCommand) { this.logger.error(`Unknown command '${possibleCommandName}'`); @@ -1235,7 +1336,9 @@ class WebpackCLI { } try { - const { name, version } = this.loadJSONFile(`${foundCommand.pkg}/package.json`); + const { name, version } = this.loadJSONFile( + `${foundCommand.pkg}/package.json`, + ); this.logger.raw(`${name} ${version}`); } catch (e) { @@ -1255,14 +1358,17 @@ class WebpackCLI { this.logger.raw(`webpack: ${webpack ? webpack.version : "not installed"}`); - const pkgJSON = this.loadJSONFile("../package.json"); + const pkgJSON = this.loadJSONFile("../package.json"); this.logger.raw(`webpack-cli: ${pkgJSON.version}`); let devServer; try { - devServer = await this.loadJSONFile("webpack-dev-server/package.json", false); + devServer = await this.loadJSONFile( + "webpack-dev-server/package.json", + false, + ); } catch (_error) { // Nothing } @@ -1276,7 +1382,12 @@ class WebpackCLI { "Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands.", ); - const outputHelp = async (options, isVerbose, isHelpCommandSyntax, program) => { + const outputHelp = async ( + options: string[], + isVerbose: boolean, + isHelpCommandSyntax: boolean, + program: WebpackCLICommand, + ) => { const { bold } = this.colors; const outputIncorrectUsageOfHelp = () => { this.logger.error("Incorrect use of help"); @@ -1294,7 +1405,7 @@ class WebpackCLI { program.configureHelp({ sortSubcommands: true, // Support multiple aliases - commandUsage: (command) => { + commandUsage: (command: WebpackCLICommand) => { let parentCmdNames = ""; for (let parentCmd = command.parent; parentCmd; parentCmd = parentCmd.parent) { @@ -1312,20 +1423,24 @@ class WebpackCLI { .join("|")} ${command.usage()}`; }, // Support multiple aliases - subcommandTerm: (command) => { - const humanReadableArgumentName = (argument) => { + subcommandTerm: (command: WebpackCLICommand) => { + const humanReadableArgumentName = (argument: WebpackCLICommandOption) => { const nameOutput = argument.name + (argument.variadic === true ? "..." : ""); return argument.required ? "<" + nameOutput + ">" : "[" + nameOutput + "]"; }; - const args = command._args.map((arg) => humanReadableArgumentName(arg)).join(" "); + const args = command._args + .map((arg: WebpackCLICommandOption) => humanReadableArgumentName(arg)) + .join(" "); return `${command.name()}|${command.aliases().join("|")}${args ? ` ${args}` : ""}${ command.options.length > 0 ? " [options]" : "" }`; }, - visibleOptions: function visibleOptions(command) { - return command.options.filter((option) => { + visibleOptions: function visibleOptions( + command: WebpackCLICommand, + ): WebpackCLICommandOption[] { + return command.options.filter((option: WebpackCLICommandOption) => { if (option.hidden) { return false; } @@ -1339,7 +1454,7 @@ class WebpackCLI { } }); }, - padWidth(command, helper) { + padWidth(command: WebpackCLICommand, helper: Help) { return Math.max( helper.longestArgumentTermLength(command, helper), helper.longestOptionTermLength(command, helper), @@ -1348,13 +1463,14 @@ class WebpackCLI { helper.longestSubcommandTermLength(isGlobalHelp ? program : command, helper), ); }, - formatHelp: (command, helper) => { + formatHelp: (command: WebpackCLICommand, helper: Help) => { const termWidth = helper.padWidth(command, helper); - const helpWidth = helper.helpWidth || process.env.WEBPACK_CLI_HELP_WIDTH || 80; + const helpWidth = + helper.helpWidth || (process.env.WEBPACK_CLI_HELP_WIDTH as unknown as number) || 80; const itemIndentWidth = 2; const itemSeparatorWidth = 2; // between term and description - const formatItem = (term, description) => { + const formatItem = (term: string, description: string) => { if (description) { const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`; @@ -1368,7 +1484,7 @@ class WebpackCLI { return term; }; - const formatList = (textArray) => + const formatList = (textArray: string[]) => textArray.join("\n").replace(/^/gm, " ".repeat(itemIndentWidth)); // Usage @@ -1404,7 +1520,7 @@ class WebpackCLI { } // Global options - const globalOptionList = program.options.map((option) => + const globalOptionList = program.options.map((option: WebpackCLICommandOption) => formatItem(helper.optionTerm(option), helper.optionDescription(option)), ); @@ -1436,7 +1552,7 @@ class WebpackCLI { const buildCommand = findCommandByName(getCommandName(buildCommandOptions.name)); - this.logger.raw(buildCommand.helpInformation()); + buildCommand && this.logger.raw(buildCommand.helpInformation()); } else { const name = options[0]; @@ -1464,7 +1580,7 @@ class WebpackCLI { } else if (isHelpCommandSyntax) { let isCommandSpecified = false; let commandName = getCommandName(buildCommandOptions.name); - let optionName; + let optionName = ""; if (options.length === 1) { optionName = options[0]; @@ -1490,7 +1606,7 @@ class WebpackCLI { process.exit(2); } - const option = command.options.find( + const option = (command as WebpackCLICommand).options.find( (option) => option.short === optionName || option.long === optionName, ); @@ -1540,7 +1656,7 @@ class WebpackCLI { } else { return accumulator; } - }, []); + }, []); if (possibleValues.length > 0) { this.logger.raw( @@ -1574,7 +1690,7 @@ class WebpackCLI { // Default action this.program.usage("[options]"); this.program.allowUnknownOption(true); - this.program.action(async (options, program) => { + this.program.action(async (options, program: WebpackCLICommand) => { if (!isInternalActionCalled) { isInternalActionCalled = true; } else { @@ -1606,7 +1722,7 @@ class WebpackCLI { this.program.forHelp = true; - const optionsForHelp = [] + const optionsForHelp = ([] as string[]) .concat(isHelpOption && hasOperand ? [operand] : []) // Syntax `webpack help [command]` .concat(operands.slice(1)) @@ -1628,12 +1744,12 @@ class WebpackCLI { const isVersionCommandSyntax = isCommand(operand, versionCommandOptions); if (isVersionOption || isVersionCommandSyntax) { - const optionsForVersion = [] + const optionsForVersion = ([] as string[]) .concat(isVersionOption ? [operand] : []) .concat(operands.slice(1)) .concat(unknown); - await outputVersion(optionsForVersion, program); + await outputVersion(optionsForVersion); } let commandToRun = operand; @@ -1679,22 +1795,22 @@ class WebpackCLI { await this.program.parseAsync(args, parseOptions); } - async loadConfig(options) { + async loadConfig(options: Partial) { const interpret = require("interpret"); - const loadConfigByPath = async (configPath, argv = {}) => { + const loadConfigByPath = async (configPath: string, argv: Argv = {}) => { const ext = path.extname(configPath); const interpreted = Object.keys(interpret.jsVariants).find((variant) => variant === ext); if (interpreted) { - const rechoir = require("rechoir"); + const rechoir: Rechoir = require("rechoir"); try { rechoir.prepare(interpret.extensions, configPath); } catch (error) { - if (error.failures) { + if ((error as RechoirError)?.failures) { this.logger.error(`Unable load '${configPath}'`); - this.logger.error(error.message); - error.failures.forEach((failure) => { + this.logger.error((error as RechoirError).message); + (error as RechoirError).failures.forEach((failure) => { this.logger.error(failure.error.message); }); this.logger.error("Please install one of them"); @@ -1706,11 +1822,17 @@ class WebpackCLI { } } - let options; + let options: ConfigOptions | ConfigOptions[]; + + type LoadConfigOption = PotentialPromise; try { - options = await this.tryRequireThenImport(configPath, false); - } catch (error) { + options = await this.tryRequireThenImport( + configPath, + false, + ); + // @ts-expect-error error type assertion + } catch (error: Error) { this.logger.error(`Failed to load '${configPath}' config`); if (this.isValidationError(error)) { @@ -1723,32 +1845,39 @@ class WebpackCLI { } if (Array.isArray(options)) { + // reassign the value to assert type + const optionsArray: ConfigOptions[] = options; await Promise.all( - options.map(async (_, i) => { - if (typeof options[i].then === "function") { - options[i] = await options[i]; + optionsArray.map(async (_, i) => { + if ( + this.isPromise( + optionsArray[i] as Promise, + ) + ) { + optionsArray[i] = await optionsArray[i]; } - // `Promise` may return `Function` - if (typeof options[i] === "function") { + if (this.isFunction(optionsArray[i])) { // when config is a function, pass the env from args to the config function - options[i] = await options[i](argv.env, argv); + optionsArray[i] = await (optionsArray[i] as CallableOption)(argv.env, argv); } }), ); + options = optionsArray; } else { - if (typeof options.then === "function") { + if (this.isPromise(options as Promise)) { options = await options; } // `Promise` may return `Function` - if (typeof options === "function") { + if (this.isFunction(options)) { // when config is a function, pass the env from args to the config function options = await options(argv.env, argv); } } - const isObject = (value) => typeof value === "object" && value !== null; + const isObject = (value: unknown): value is object => + typeof value === "object" && value !== null; if (!isObject(options) && !Array.isArray(options)) { this.logger.error(`Invalid configuration in '${configPath}'`); @@ -1759,11 +1888,14 @@ class WebpackCLI { return { options, path: configPath }; }; - const config = { options: {}, path: new WeakMap() }; + const config: WebpackCLIConfig = { + options: {} as WebpackConfiguration, + path: new WeakMap(), + }; if (options.config && options.config.length > 0) { const loadedConfigs = await Promise.all( - options.config.map((configPath) => + options.config.map((configPath: string) => loadConfigByPath(path.resolve(configPath), options.argv), ), ); @@ -1774,24 +1906,24 @@ class WebpackCLI { const isArray = Array.isArray(loadedConfig.options); // TODO we should run webpack multiple times when the `--config` options have multiple values with `--merge`, need to solve for the next major release - if (config.options.length === 0) { - config.options = loadedConfig.options; + if ((config.options as ConfigOptions[]).length === 0) { + config.options = loadedConfig.options as WebpackConfiguration; } else { if (!Array.isArray(config.options)) { config.options = [config.options]; } if (isArray) { - loadedConfig.options.forEach((item) => { - config.options.push(item); + (loadedConfig.options as ConfigOptions[]).forEach((item) => { + (config.options as ConfigOptions[]).push(item); }); } else { - config.options.push(loadedConfig.options); + config.options.push(loadedConfig.options as WebpackConfiguration); } } if (isArray) { - loadedConfig.options.forEach((options) => { + (loadedConfig.options as ConfigOptions[]).forEach((options) => { config.path.set(options, loadedConfig.path); }); } else { @@ -1831,7 +1963,7 @@ class WebpackCLI { if (foundDefaultConfigFile) { const loadedConfig = await loadConfigByPath(foundDefaultConfigFile.path, options.argv); - config.options = loadedConfig.options; + config.options = loadedConfig.options as WebpackConfiguration[]; if (Array.isArray(config.options)) { config.options.forEach((item) => { @@ -1844,9 +1976,9 @@ class WebpackCLI { } if (options.configName) { - const notFoundConfigNames = []; + const notFoundConfigNames: string[] = []; - config.options = options.configName.map((configName) => { + config.options = options.configName.map((configName: string) => { let found; if (Array.isArray(config.options)) { @@ -1860,7 +1992,7 @@ class WebpackCLI { } return found; - }); + }) as WebpackConfiguration[]; if (notFoundConfigNames.length > 0) { this.logger.error( @@ -1873,7 +2005,7 @@ class WebpackCLI { } if (options.merge) { - const merge = await this.tryRequireThenImport("webpack-merge"); + const merge = await this.tryRequireThenImport("webpack-merge"); // we can only merge when there are multiple configurations // either by passing multiple configs by flags or passing a @@ -1883,24 +2015,30 @@ class WebpackCLI { process.exit(2); } - const mergedConfigPaths = []; + const mergedConfigPaths: string[] = []; - config.options = config.options.reduce((accumulator, options) => { + config.options = config.options.reduce((accumulator: object, options) => { const configPath = config.path.get(options); const mergedOptions = merge(accumulator, options); - mergedConfigPaths.push(configPath); + mergedConfigPaths.push(configPath as string); return mergedOptions; }, {}); - config.path.set(config.options, mergedConfigPaths); + config.path.set(config.options, mergedConfigPaths as unknown as string); } return config; } - async buildConfig(config, options) { - const runFunctionOnEachConfig = (options, fn) => { + async buildConfig( + config: WebpackCLIConfig, + options: Partial, + ): Promise { + const runFunctionOnEachConfig = ( + options: ConfigOptions | ConfigOptions[], + fn: CallableFunction, + ) => { if (Array.isArray(options)) { for (let item of options) { item = fn(item); @@ -1942,9 +2080,11 @@ class WebpackCLI { process.exit(2); } - const CLIPlugin = await this.tryRequireThenImport("./plugins/CLIPlugin"); + const CLIPlugin = await this.tryRequireThenImport< + Instantiable + >("./plugins/CLIPlugin"); - const internalBuildConfig = (item) => { + const internalBuildConfig = (item: WebpackConfiguration) => { // Output warnings if ( item.watch && @@ -1965,32 +2105,35 @@ class WebpackCLI { // Apply options if (this.webpack.cli) { - const args = this.getBuiltInOptions() + const args: Record = this.getBuiltInOptions() .filter((flag) => flag.group === "core") - .reduce((accumulator, flag) => { - accumulator[flag.name] = flag; - + .reduce((accumulator: Record, flag) => { + accumulator[flag.name] = flag as unknown as Argument; return accumulator; }, {}); - const values = Object.keys(options).reduce((accumulator, name) => { - if (name === "argv") { - return accumulator; - } + const values: ProcessedArguments = Object.keys(options).reduce( + (accumulator: ProcessedArguments, name) => { + if (name === "argv") { + return accumulator; + } - const kebabName = this.toKebabCase(name); + const kebabName = this.toKebabCase(name); - if (args[kebabName]) { - accumulator[kebabName] = options[name]; - } + if (args[kebabName]) { + accumulator[kebabName] = options[name as keyof typeof options as string]; + } - return accumulator; - }, {}); + return accumulator; + }, + {}, + ); - const problems = this.webpack.cli.processArguments(args, item, values); + const problems: Problem[] | null = this.webpack.cli.processArguments(args, item, values); if (problems) { - const groupBy = (xs, key) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const groupBy = (xs: Record[], key: string) => { return xs.reduce((rv, x) => { (rv[x[key]] = rv[x[key]] || []).push(x); @@ -2002,7 +2145,7 @@ class WebpackCLI { for (const path in problemsByPath) { const problems = problemsByPath[path]; - problems.forEach((problem) => { + problems.forEach((problem: Problem) => { this.logger.error( `${this.capitalizeFirstLetter(problem.type.replace(/-/g, " "))}${ problem.value ? ` '${problem.value}'` : "" @@ -2020,8 +2163,16 @@ class WebpackCLI { process.exit(2); } + const isFileSystemCacheOptions = ( + config: WebpackConfiguration, + ): config is FileSystemCacheOptions => { + return ( + Boolean(config.cache) && (config as FileSystemCacheOptions).cache.type === "filesystem" + ); + }; + // Setup default cache options - if (item.cache && item.cache.type === "filesystem") { + if (isFileSystemCacheOptions(item)) { const configPath = config.path.get(item); if (configPath) { @@ -2035,7 +2186,11 @@ class WebpackCLI { if (Array.isArray(configPath)) { configPath.forEach((oneOfConfigPath) => { - item.cache.buildDependencies.defaultConfig.push(oneOfConfigPath); + ( + item.cache.buildDependencies as NonNullable< + FileSystemCacheOptions["cache"]["buildDependencies"] + > + ).defaultConfig.push(oneOfConfigPath); }); } else { item.cache.buildDependencies.defaultConfig.push(configPath); @@ -2097,13 +2252,17 @@ class WebpackCLI { // Setup stats // TODO remove after drop webpack@4 - const statsForWebpack4 = this.webpack.Stats && this.webpack.Stats.presetToOptions; + const statsForWebpack4 = + this.webpack.Stats && + (this.webpack.Stats as unknown as Partial).presetToOptions; if (statsForWebpack4) { if (typeof item.stats === "undefined") { item.stats = {}; } else if (typeof item.stats === "boolean") { - item.stats = this.webpack.Stats.presetToOptions(item.stats); + item.stats = (this.webpack.Stats as unknown as WebpackV4LegacyStats).presetToOptions( + item.stats, + ); } else if ( typeof item.stats === "string" && (item.stats === "none" || @@ -2114,7 +2273,9 @@ class WebpackCLI { item.stats === "errors-only" || item.stats === "errors-warnings") ) { - item.stats = this.webpack.Stats.presetToOptions(item.stats); + item.stats = (this.webpack.Stats as unknown as WebpackV4LegacyStats).presetToOptions( + item.stats, + ); } } else { if (typeof item.stats === "undefined") { @@ -2133,8 +2294,8 @@ class WebpackCLI { colors = Boolean(this.isColorSupportChanged); } // From stats - else if (typeof item.stats.colors !== "undefined") { - colors = item.stats.colors; + else if (typeof (item.stats as StatsOptions).colors !== "undefined") { + colors = (item.stats as StatsOptions).colors; } // Default else { @@ -2170,7 +2331,7 @@ class WebpackCLI { return config; } - isValidationError(error) { + isValidationError(error: Error): error is WebpackError { // 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 = @@ -2179,7 +2340,10 @@ class WebpackCLI { return error instanceof ValidationError || error.name === "ValidationError"; } - async createCompiler(options, callback) { + async createCompiler( + options: Partial, + callback?: Callback<[Error | undefined, WebpackCLIStats | undefined]>, + ): Promise { if (typeof options.nodeEnv === "string") { process.env.NODE_ENV = options.nodeEnv; } @@ -2187,11 +2351,10 @@ class WebpackCLI { let config = await this.loadConfig(options); config = await this.buildConfig(config, options); - let compiler; - + let compiler: WebpackCompiler; try { compiler = this.webpack( - config.options, + config.options as WebpackConfiguration, callback ? (error, stats) => { if (error && this.isValidationError(error)) { @@ -2203,7 +2366,8 @@ class WebpackCLI { } : callback, ); - } catch (error) { + // @ts-expect-error error type assertion + } catch (error: Error) { if (this.isValidationError(error)) { this.logger.error(error.message); } else { @@ -2214,49 +2378,52 @@ class WebpackCLI { } // TODO webpack@4 return Watching and MultiWatching instead Compiler and MultiCompiler, remove this after drop webpack@4 - if (compiler && compiler.compiler) { - compiler = compiler.compiler; + if (compiler && (compiler as WebpackV4Compiler).compiler) { + compiler = (compiler as WebpackV4Compiler).compiler; } return compiler; } - needWatchStdin(compiler) { - if (compiler.compilers) { - return compiler.compilers.some( - (compiler) => compiler.options.watchOptions && compiler.options.watchOptions.stdin, + needWatchStdin(compiler: Compiler | MultiCompiler): boolean { + if (this.isMultipleCompiler(compiler)) { + return Boolean( + (compiler as MultiCompiler).compilers.some( + (compiler: Compiler) => + compiler.options.watchOptions && compiler.options.watchOptions.stdin, + ), ); } - return compiler.options.watchOptions && compiler.options.watchOptions.stdin; + return Boolean(compiler.options.watchOptions && compiler.options.watchOptions.stdin); } - async runWebpack(options, isWatchCommand) { + async runWebpack(options: WebpackRunOptions, isWatchCommand: boolean): Promise { // eslint-disable-next-line prefer-const - let compiler; - let createJsonStringifyStream; + let compiler: Compiler | MultiCompiler; + let createJsonStringifyStream: typeof stringifyStream; if (options.json) { - const jsonExt = await this.tryRequireThenImport("@discoveryjs/json-ext"); + const jsonExt = await this.tryRequireThenImport("@discoveryjs/json-ext"); createJsonStringifyStream = jsonExt.stringifyStream; } - const callback = (error, stats) => { + const callback = (error: Error | undefined, stats: WebpackCLIStats | undefined): void => { if (error) { this.logger.error(error); process.exit(2); } - if (stats.hasErrors()) { + if (stats && stats.hasErrors()) { process.exitCode = 1; } - if (!compiler) { + if (!compiler || !stats) { return; } - const statsOptions = compiler.compilers + const statsOptions = this.isMultipleCompiler(compiler) ? { children: compiler.compilers.map((compiler) => compiler.options ? compiler.options.stats : undefined, @@ -2267,26 +2434,30 @@ class WebpackCLI { : undefined; // TODO webpack@4 doesn't support `{ children: [{ colors: true }, { colors: true }] }` for stats - const statsForWebpack4 = this.webpack.Stats && this.webpack.Stats.presetToOptions; - - if (compiler.compilers && statsForWebpack4) { - statsOptions.colors = statsOptions.children.some((child) => child.colors); + const statsForWebpack4 = + this.webpack.Stats && + (this.webpack.Stats as unknown as WebpackV4LegacyStats).presetToOptions; + + if (this.isMultipleCompiler(compiler) && statsForWebpack4) { + (statsOptions as StatsOptions).colors = ( + statsOptions as MultipleCompilerStatsOptions + ).children.some((child) => child.colors); } if (options.json && createJsonStringifyStream) { - const handleWriteError = (error) => { + const handleWriteError = (error: WebpackError) => { this.logger.error(error); process.exit(2); }; if (options.json === true) { - createJsonStringifyStream(stats.toJson(statsOptions)) + createJsonStringifyStream(stats.toJson(statsOptions as StatsOptions)) .on("error", handleWriteError) .pipe(process.stdout) .on("error", handleWriteError) .on("close", () => process.stdout.write("\n")); } else { - createJsonStringifyStream(stats.toJson(statsOptions)) + createJsonStringifyStream(stats.toJson(statsOptions as StatsOptions)) .on("error", handleWriteError) .pipe(fs.createWriteStream(options.json)) .on("error", handleWriteError) @@ -2301,7 +2472,6 @@ class WebpackCLI { } } else { const printedStats = stats.toString(statsOptions); - // Avoid extra empty line when `stats: 'none'` if (printedStats) { this.logger.raw(printedStats); @@ -2320,16 +2490,18 @@ class WebpackCLI { options.watch = true; } - compiler = await this.createCompiler(options, callback); + compiler = await this.createCompiler(options as WebpackDevServerOptions, callback); if (!compiler) { return; } - const isWatch = (compiler) => - compiler.compilers - ? compiler.compilers.some((compiler) => compiler.options.watch) - : compiler.options.watch; + const isWatch = (compiler: WebpackCompiler): boolean => + Boolean( + this.isMultipleCompiler(compiler) + ? compiler.compilers.some((compiler) => compiler.options.watch) + : compiler.options.watch, + ); if (isWatch(compiler) && this.needWatchStdin(compiler)) { process.stdin.on("end", () => { diff --git a/packages/webpack-cli/tsconfig.json b/packages/webpack-cli/tsconfig.json new file mode 100644 index 00000000000..99cbaf6e6c3 --- /dev/null +++ b/packages/webpack-cli/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src", + "typeRoots": ["./src/types.ts"] + }, + + "include": ["./src"] +} diff --git a/test/api/CLI.test.js b/test/api/CLI.test.js index faf32be9355..aaffdf26137 100644 --- a/test/api/CLI.test.js +++ b/test/api/CLI.test.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line node/no-unpublished-require const CLI = require("../../packages/webpack-cli/lib/webpack-cli"); describe("CLI API", () => { diff --git a/test/api/capitalizeFirstLetter.test.js b/test/api/capitalizeFirstLetter.test.js index c00a6e45691..5643125789a 100755 --- a/test/api/capitalizeFirstLetter.test.js +++ b/test/api/capitalizeFirstLetter.test.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line node/no-unpublished-require const CLI = require("../../packages/webpack-cli/lib/webpack-cli"); describe("capitalizeFirstLetter", () => { diff --git a/test/api/do-install.test.js b/test/api/do-install.test.js index 5df7cfc0fc8..fab8ac17c3a 100644 --- a/test/api/do-install.test.js +++ b/test/api/do-install.test.js @@ -1,5 +1,6 @@ "use strict"; +// eslint-disable-next-line node/no-unpublished-require const CLI = require("../../packages/webpack-cli/lib/webpack-cli"); // eslint-disable-next-line node/no-unpublished-require diff --git a/test/api/generators/helpers.test.js b/test/api/generators/helpers.test.js index 4dc1b8c87b8..9b5d59bfc70 100644 --- a/test/api/generators/helpers.test.js +++ b/test/api/generators/helpers.test.js @@ -1,4 +1,5 @@ const path = require("path"); +// eslint-disable-next-line node/no-unpublished-require const CLI = require("../../../packages/webpack-cli/lib/webpack-cli"); const utilsDirectory = { diff --git a/test/api/get-default-package-manager.test.js b/test/api/get-default-package-manager.test.js index b1c55bd4a8a..f2866227381 100644 --- a/test/api/get-default-package-manager.test.js +++ b/test/api/get-default-package-manager.test.js @@ -1,5 +1,6 @@ const fs = require("fs"); const path = require("path"); +// eslint-disable-next-line node/no-unpublished-require const CLI = require("../../packages/webpack-cli/lib/webpack-cli"); const syncMock = jest.fn(() => { diff --git a/test/api/resolveConfig/resolveConfig.test.js b/test/api/resolveConfig/resolveConfig.test.js index 60ca2cdf574..364849f3a47 100644 --- a/test/api/resolveConfig/resolveConfig.test.js +++ b/test/api/resolveConfig/resolveConfig.test.js @@ -1,4 +1,5 @@ const { resolve } = require("path"); +// eslint-disable-next-line node/no-unpublished-require const WebpackCLI = require("../../../packages/webpack-cli/lib/webpack-cli"); const config1 = require("./webpack.config1.cjs"); const config2 = require("./webpack.config2.cjs"); diff --git a/tsconfig.json b/tsconfig.json index a14e54af0f4..796ecc1c96e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,6 +32,9 @@ }, { "path": "packages/serve" + }, + { + "path": "packages/webpack-cli" } ] } diff --git a/yarn.lock b/yarn.lock index e1026148c4d..83b0e7a1c9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2130,6 +2130,13 @@ "@types/through" "*" rxjs "^7.2.0" +"@types/interpret@*": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/interpret/-/interpret-1.1.1.tgz#b1bf85b0420e2414b989ce237658ad20dc03719b" + integrity sha512-HZ4d0m2Ebl8DmrOdYZHgYyipj/8Ftq1/ssB/oQR7fqfUrwtTP7IW3BDi2V445nhPBLzZjEkApaPVp83moSCXlA== + dependencies: + "@types/node" "*" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -2239,6 +2246,13 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== +"@types/rechoir@^0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@types/rechoir/-/rechoir-0.6.1.tgz#e7589df255d2638db48b0dfc3294c87dc9db19ba" + integrity sha512-HbMQqyZC8W9NxE3R89rW+hFwFXeIdmCT7x91NQjzB4+0CI42K/CJfRak5/jAQ7L5qi1cGcQQdo+GI9pqqUhbKQ== + dependencies: + "@types/interpret" "*" + "@types/responselike@*", "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"