diff --git a/packages/angular/cli/src/command-builder/command-runner.ts b/packages/angular/cli/src/command-builder/command-runner.ts index 36c4b308ecc6..bacf9ac98626 100644 --- a/packages/angular/cli/src/command-builder/command-runner.ts +++ b/packages/angular/cli/src/command-builder/command-runner.ts @@ -9,56 +9,25 @@ import { logging } from '@angular-devkit/core'; import yargs from 'yargs'; import { Parser } from 'yargs/helpers'; -import { AddCommandModule } from '../commands/add/cli'; -import { AnalyticsCommandModule } from '../commands/analytics/cli'; -import { BuildCommandModule } from '../commands/build/cli'; -import { CacheCommandModule } from '../commands/cache/cli'; -import { CompletionCommandModule } from '../commands/completion/cli'; -import { ConfigCommandModule } from '../commands/config/cli'; -import { DeployCommandModule } from '../commands/deploy/cli'; -import { DocCommandModule } from '../commands/doc/cli'; -import { E2eCommandModule } from '../commands/e2e/cli'; -import { ExtractI18nCommandModule } from '../commands/extract-i18n/cli'; -import { GenerateCommandModule } from '../commands/generate/cli'; -import { LintCommandModule } from '../commands/lint/cli'; -import { AwesomeCommandModule } from '../commands/make-this-awesome/cli'; -import { NewCommandModule } from '../commands/new/cli'; -import { RunCommandModule } from '../commands/run/cli'; -import { ServeCommandModule } from '../commands/serve/cli'; -import { TestCommandModule } from '../commands/test/cli'; -import { UpdateCommandModule } from '../commands/update/cli'; -import { VersionCommandModule } from '../commands/version/cli'; +import { + CommandConfig, + CommandNames, + RootCommands, + RootCommandsAliases, +} from '../commands/command-config'; import { colors } from '../utilities/color'; import { AngularWorkspace, getWorkspace } from '../utilities/config'; import { assertIsError } from '../utilities/error'; import { PackageManagerUtils } from '../utilities/package-manager'; import { CommandContext, CommandModuleError } from './command-module'; -import { addCommandModuleToYargs, demandCommandFailureMessage } from './utilities/command'; +import { + CommandModuleConstructor, + addCommandModuleToYargs, + demandCommandFailureMessage, +} from './utilities/command'; import { jsonHelpUsage } from './utilities/json-help'; import { normalizeOptionsMiddleware } from './utilities/normalize-options-middleware'; -const COMMANDS = [ - VersionCommandModule, - DocCommandModule, - AwesomeCommandModule, - ConfigCommandModule, - AnalyticsCommandModule, - AddCommandModule, - GenerateCommandModule, - BuildCommandModule, - E2eCommandModule, - TestCommandModule, - ServeCommandModule, - ExtractI18nCommandModule, - DeployCommandModule, - LintCommandModule, - NewCommandModule, - UpdateCommandModule, - RunCommandModule, - CacheCommandModule, - CompletionCommandModule, -].sort(); // Will be sorted by class name. - const yargsParser = Parser as unknown as typeof Parser.default; export async function runCommand(args: string[], logger: logging.Logger): Promise { @@ -111,7 +80,7 @@ export async function runCommand(args: string[], logger: logging.Logger): Promis }; let localYargs = yargs(args); - for (const CommandModule of COMMANDS) { + for (const CommandModule of await getCommandsToRegister(positional[0])) { localYargs = addCommandModuleToYargs(localYargs, CommandModule, context); } @@ -168,3 +137,23 @@ export async function runCommand(args: string[], logger: logging.Logger): Promis return process.exitCode ?? 0; } + +/** + * Get the commands that need to be registered. + * @returns One or more command factories that needs to be registered. + */ +async function getCommandsToRegister( + commandName: string | number, +): Promise { + const commands: CommandConfig[] = []; + if (commandName in RootCommands) { + commands.push(RootCommands[commandName as CommandNames]); + } else if (commandName in RootCommandsAliases) { + commands.push(RootCommandsAliases[commandName]); + } else { + // Unknown command, register every possible command. + Object.values(RootCommands).forEach((c) => commands.push(c)); + } + + return Promise.all(commands.map((command) => command.factory().then((m) => m.default))); +} diff --git a/packages/angular/cli/src/command-builder/utilities/command.ts b/packages/angular/cli/src/command-builder/utilities/command.ts index 3c3a1fa566ad..5ba067e38209 100644 --- a/packages/angular/cli/src/command-builder/utilities/command.ts +++ b/packages/angular/cli/src/command-builder/utilities/command.ts @@ -16,13 +16,15 @@ import { } from '../command-module'; export const demandCommandFailureMessage = `You need to specify a command before moving on. Use '--help' to view the available commands.`; +export type CommandModuleConstructor = Partial & { + new (context: CommandContext): Partial & CommandModule; +}; -export function addCommandModuleToYargs< - T extends object, - U extends Partial & { - new (context: CommandContext): Partial & CommandModule; - }, ->(localYargs: Argv, commandModule: U, context: CommandContext): Argv { +export function addCommandModuleToYargs( + localYargs: Argv, + commandModule: U, + context: CommandContext, +): Argv { const cmd = new commandModule(context); const { args: { diff --git a/packages/angular/cli/src/commands/add/cli.ts b/packages/angular/cli/src/commands/add/cli.ts index 16bcc2d9f30a..4c964a422969 100644 --- a/packages/angular/cli/src/commands/add/cli.ts +++ b/packages/angular/cli/src/commands/add/cli.ts @@ -55,7 +55,7 @@ const packageVersionExclusions: Record = { '@angular/material': '7.x', }; -export class AddCommandModule +export default class AddCommadModule extends SchematicsCommandModule implements CommandModuleImplementation { diff --git a/packages/angular/cli/src/commands/analytics/cli.ts b/packages/angular/cli/src/commands/analytics/cli.ts index bdba1ccafd11..8e3753ababb1 100644 --- a/packages/angular/cli/src/commands/analytics/cli.ts +++ b/packages/angular/cli/src/commands/analytics/cli.ts @@ -24,7 +24,10 @@ import { AnalyticsPromptModule, } from './settings/cli'; -export class AnalyticsCommandModule extends CommandModule implements CommandModuleImplementation { +export default class AnalyticsCommandModule + extends CommandModule + implements CommandModuleImplementation +{ command = 'analytics'; describe = 'Configures the gathering of Angular CLI usage metrics.'; longDescriptionPath = join(__dirname, 'long-description.md'); diff --git a/packages/angular/cli/src/commands/build/cli.ts b/packages/angular/cli/src/commands/build/cli.ts index 434ff4f22f84..196585a4b122 100644 --- a/packages/angular/cli/src/commands/build/cli.ts +++ b/packages/angular/cli/src/commands/build/cli.ts @@ -9,14 +9,15 @@ import { join } from 'path'; import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; import { CommandModuleImplementation } from '../../command-builder/command-module'; +import { RootCommands } from '../command-config'; -export class BuildCommandModule +export default class BuildCommandModule extends ArchitectCommandModule implements CommandModuleImplementation { multiTarget = false; command = 'build [project]'; - aliases = ['b']; + aliases = RootCommands['build'].aliases; describe = 'Compiles an Angular application or library into an output directory named dist/ at the given output path.'; longDescriptionPath = join(__dirname, 'long-description.md'); diff --git a/packages/angular/cli/src/commands/cache/cli.ts b/packages/angular/cli/src/commands/cache/cli.ts index f30c4acd3b81..bc4115d8cfde 100644 --- a/packages/angular/cli/src/commands/cache/cli.ts +++ b/packages/angular/cli/src/commands/cache/cli.ts @@ -22,7 +22,10 @@ import { CacheCleanModule } from './clean/cli'; import { CacheInfoCommandModule } from './info/cli'; import { CacheDisableModule, CacheEnableModule } from './settings/cli'; -export class CacheCommandModule extends CommandModule implements CommandModuleImplementation { +export default class CacheCommandModule + extends CommandModule + implements CommandModuleImplementation +{ command = 'cache'; describe = 'Configure persistent disk cache and retrieve cache statistics.'; longDescriptionPath = join(__dirname, 'long-description.md'); diff --git a/packages/angular/cli/src/commands/command-config.ts b/packages/angular/cli/src/commands/command-config.ts new file mode 100644 index 000000000000..4cbe2bbb1a73 --- /dev/null +++ b/packages/angular/cli/src/commands/command-config.ts @@ -0,0 +1,114 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { CommandModuleConstructor } from '../command-builder/utilities/command'; + +export type CommandNames = + | 'add' + | 'analytics' + | 'build' + | 'cache' + | 'completion' + | 'config' + | 'deploy' + | 'doc' + | 'e2e' + | 'extract-i18n' + | 'generate' + | 'lint' + | 'make-this-awesome' + | 'new' + | 'run' + | 'serve' + | 'test' + | 'update' + | 'version'; + +export interface CommandConfig { + aliases?: string[]; + factory: () => Promise<{ default: CommandModuleConstructor }>; +} + +export const RootCommands: Record< + /* Command */ CommandNames & string, + /* Command Config */ CommandConfig +> = { + 'add': { + factory: () => import('./add/cli'), + }, + 'analytics': { + factory: () => import('./analytics/cli'), + }, + 'build': { + factory: () => import('./build/cli'), + aliases: ['b'], + }, + 'cache': { + factory: () => import('./cache/cli'), + }, + 'completion': { + factory: () => import('./completion/cli'), + }, + 'config': { + factory: () => import('./config/cli'), + }, + 'deploy': { + factory: () => import('./deploy/cli'), + }, + 'doc': { + factory: () => import('./doc/cli'), + aliases: ['d'], + }, + 'e2e': { + factory: () => import('./e2e/cli'), + aliases: ['e2e'], + }, + 'extract-i18n': { + factory: () => import('./extract-i18n/cli'), + }, + 'generate': { + factory: () => import('./generate/cli'), + aliases: ['g'], + }, + 'lint': { + factory: () => import('./lint/cli'), + }, + 'make-this-awesome': { + factory: () => import('./make-this-awesome/cli'), + }, + 'new': { + factory: () => import('./new/cli'), + aliases: ['n'], + }, + 'run': { + factory: () => import('./run/cli'), + }, + 'serve': { + factory: () => import('./serve/cli'), + aliases: ['s'], + }, + 'test': { + factory: () => import('./test/cli'), + aliases: ['t'], + }, + 'update': { + factory: () => import('./update/cli'), + }, + 'version': { + factory: () => import('./version/cli'), + aliases: ['v'], + }, +}; + +export const RootCommandsAliases = Object.values(RootCommands).reduce((prev, current) => { + current.aliases?.forEach((alias) => { + prev[alias] = current; + }); + + return prev; +}, {} as Record); diff --git a/packages/angular/cli/src/commands/completion/cli.ts b/packages/angular/cli/src/commands/completion/cli.ts index f6166c28b325..8c777a9b8d32 100644 --- a/packages/angular/cli/src/commands/completion/cli.ts +++ b/packages/angular/cli/src/commands/completion/cli.ts @@ -14,7 +14,10 @@ import { colors } from '../../utilities/color'; import { hasGlobalCliInstall, initializeAutocomplete } from '../../utilities/completion'; import { assertIsError } from '../../utilities/error'; -export class CompletionCommandModule extends CommandModule implements CommandModuleImplementation { +export default class CompletionCommandModule + extends CommandModule + implements CommandModuleImplementation +{ command = 'completion'; describe = 'Set up Angular CLI autocompletion for your terminal.'; longDescriptionPath = join(__dirname, 'long-description.md'); diff --git a/packages/angular/cli/src/commands/config/cli.ts b/packages/angular/cli/src/commands/config/cli.ts index 5977d8cfa02d..bb5cee4e66fd 100644 --- a/packages/angular/cli/src/commands/config/cli.ts +++ b/packages/angular/cli/src/commands/config/cli.ts @@ -25,7 +25,7 @@ interface ConfigCommandArgs { global?: boolean; } -export class ConfigCommandModule +export default class ConfigCommandModule extends CommandModule implements CommandModuleImplementation { diff --git a/packages/angular/cli/src/commands/deploy/cli.ts b/packages/angular/cli/src/commands/deploy/cli.ts index e335b0633e31..a4930680fc5e 100644 --- a/packages/angular/cli/src/commands/deploy/cli.ts +++ b/packages/angular/cli/src/commands/deploy/cli.ts @@ -11,7 +11,7 @@ import { MissingTargetChoice } from '../../command-builder/architect-base-comman import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; import { CommandModuleImplementation } from '../../command-builder/command-module'; -export class DeployCommandModule +export default class DeployCommandModule extends ArchitectCommandModule implements CommandModuleImplementation { diff --git a/packages/angular/cli/src/commands/doc/cli.ts b/packages/angular/cli/src/commands/doc/cli.ts index 73b7826fc066..3f8a6e0e725c 100644 --- a/packages/angular/cli/src/commands/doc/cli.ts +++ b/packages/angular/cli/src/commands/doc/cli.ts @@ -13,6 +13,7 @@ import { CommandModuleImplementation, Options, } from '../../command-builder/command-module'; +import { RootCommands } from '../command-config'; interface DocCommandArgs { keyword: string; @@ -20,12 +21,12 @@ interface DocCommandArgs { version?: string; } -export class DocCommandModule +export default class DocCommandModule extends CommandModule implements CommandModuleImplementation { command = 'doc '; - aliases = ['d']; + aliases = RootCommands['doc'].aliases; describe = 'Opens the official Angular documentation (angular.io) in a browser, and searches for a given keyword.'; longDescriptionPath?: string; diff --git a/packages/angular/cli/src/commands/e2e/cli.ts b/packages/angular/cli/src/commands/e2e/cli.ts index 2aecfb3ac5a6..57cc6370618f 100644 --- a/packages/angular/cli/src/commands/e2e/cli.ts +++ b/packages/angular/cli/src/commands/e2e/cli.ts @@ -9,8 +9,9 @@ import { MissingTargetChoice } from '../../command-builder/architect-base-command-module'; import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; import { CommandModuleImplementation } from '../../command-builder/command-module'; +import { RootCommands } from '../command-config'; -export class E2eCommandModule +export default class E2eCommandModule extends ArchitectCommandModule implements CommandModuleImplementation { @@ -31,7 +32,7 @@ export class E2eCommandModule multiTarget = true; command = 'e2e [project]'; - aliases = ['e']; + aliases = RootCommands['e2e'].aliases; describe = 'Builds and serves an Angular application, then runs end-to-end tests.'; longDescriptionPath?: string; } diff --git a/packages/angular/cli/src/commands/extract-i18n/cli.ts b/packages/angular/cli/src/commands/extract-i18n/cli.ts index 5283204f4e9b..a0d4bc366dfb 100644 --- a/packages/angular/cli/src/commands/extract-i18n/cli.ts +++ b/packages/angular/cli/src/commands/extract-i18n/cli.ts @@ -9,7 +9,7 @@ import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; import { CommandModuleImplementation } from '../../command-builder/command-module'; -export class ExtractI18nCommandModule +export default class ExtractI18nCommandModule extends ArchitectCommandModule implements CommandModuleImplementation { diff --git a/packages/angular/cli/src/commands/generate/cli.ts b/packages/angular/cli/src/commands/generate/cli.ts index c4029e5a7e1f..424d609ed19a 100644 --- a/packages/angular/cli/src/commands/generate/cli.ts +++ b/packages/angular/cli/src/commands/generate/cli.ts @@ -25,17 +25,18 @@ import { } from '../../command-builder/schematics-command-module'; import { demandCommandFailureMessage } from '../../command-builder/utilities/command'; import { Option } from '../../command-builder/utilities/json-schema'; +import { RootCommands } from '../command-config'; interface GenerateCommandArgs extends SchematicsCommandArgs { schematic?: string; } -export class GenerateCommandModule +export default class GenerateCommandModule extends SchematicsCommandModule implements CommandModuleImplementation { command = 'generate'; - aliases = 'g'; + aliases = RootCommands['generate'].aliases; describe = 'Generates and/or modifies files based on a schematic.'; longDescriptionPath?: string | undefined; diff --git a/packages/angular/cli/src/commands/lint/cli.ts b/packages/angular/cli/src/commands/lint/cli.ts index bf145d31db0c..d6072d5549e6 100644 --- a/packages/angular/cli/src/commands/lint/cli.ts +++ b/packages/angular/cli/src/commands/lint/cli.ts @@ -11,7 +11,7 @@ import { MissingTargetChoice } from '../../command-builder/architect-base-comman import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; import { CommandModuleImplementation } from '../../command-builder/command-module'; -export class LintCommandModule +export default class LintCommandModule extends ArchitectCommandModule implements CommandModuleImplementation { diff --git a/packages/angular/cli/src/commands/make-this-awesome/cli.ts b/packages/angular/cli/src/commands/make-this-awesome/cli.ts index fda66b295088..0c258a023f7b 100644 --- a/packages/angular/cli/src/commands/make-this-awesome/cli.ts +++ b/packages/angular/cli/src/commands/make-this-awesome/cli.ts @@ -10,7 +10,10 @@ import { Argv } from 'yargs'; import { CommandModule, CommandModuleImplementation } from '../../command-builder/command-module'; import { colors } from '../../utilities/color'; -export class AwesomeCommandModule extends CommandModule implements CommandModuleImplementation { +export default class AwesomeCommandModule + extends CommandModule + implements CommandModuleImplementation +{ command = 'make-this-awesome'; describe = false as const; deprecated = false; diff --git a/packages/angular/cli/src/commands/new/cli.ts b/packages/angular/cli/src/commands/new/cli.ts index c4f8bdebcece..202dd491bb3c 100644 --- a/packages/angular/cli/src/commands/new/cli.ts +++ b/packages/angular/cli/src/commands/new/cli.ts @@ -20,12 +20,13 @@ import { SchematicsCommandModule, } from '../../command-builder/schematics-command-module'; import { VERSION } from '../../utilities/version'; +import { RootCommands } from '../command-config'; interface NewCommandArgs extends SchematicsCommandArgs { collection?: string; } -export class NewCommandModule +export default class NewCommandModule extends SchematicsCommandModule implements CommandModuleImplementation { @@ -34,7 +35,7 @@ export class NewCommandModule protected override allowPrivateSchematics = true; command = 'new [name]'; - aliases = 'n'; + aliases = RootCommands['new'].aliases; describe = 'Creates a new Angular workspace.'; longDescriptionPath = join(__dirname, 'long-description.md'); diff --git a/packages/angular/cli/src/commands/run/cli.ts b/packages/angular/cli/src/commands/run/cli.ts index de7c185e9f3d..67d5c3a0f4b7 100644 --- a/packages/angular/cli/src/commands/run/cli.ts +++ b/packages/angular/cli/src/commands/run/cli.ts @@ -22,7 +22,7 @@ export interface RunCommandArgs { target: string; } -export class RunCommandModule +export default class RunCommandModule extends ArchitectBaseCommandModule implements CommandModuleImplementation { diff --git a/packages/angular/cli/src/commands/serve/cli.ts b/packages/angular/cli/src/commands/serve/cli.ts index 537345cc568d..48a1103355b2 100644 --- a/packages/angular/cli/src/commands/serve/cli.ts +++ b/packages/angular/cli/src/commands/serve/cli.ts @@ -8,14 +8,15 @@ import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; import { CommandModuleImplementation } from '../../command-builder/command-module'; +import { RootCommands } from '../command-config'; -export class ServeCommandModule +export default class ServeCommandModule extends ArchitectCommandModule implements CommandModuleImplementation { multiTarget = false; command = 'serve [project]'; - aliases = ['s']; + aliases = RootCommands['serve'].aliases; describe = 'Builds and serves your application, rebuilding on file changes.'; longDescriptionPath?: string | undefined; } diff --git a/packages/angular/cli/src/commands/test/cli.ts b/packages/angular/cli/src/commands/test/cli.ts index fd650fee01c9..837d57787eb4 100644 --- a/packages/angular/cli/src/commands/test/cli.ts +++ b/packages/angular/cli/src/commands/test/cli.ts @@ -9,14 +9,15 @@ import { join } from 'path'; import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; import { CommandModuleImplementation } from '../../command-builder/command-module'; +import { RootCommands } from '../command-config'; -export class TestCommandModule +export default class TestCommandModule extends ArchitectCommandModule implements CommandModuleImplementation { multiTarget = true; command = 'test [project]'; - aliases = ['t']; + aliases = RootCommands['test'].aliases; describe = 'Runs unit tests in a project.'; longDescriptionPath = join(__dirname, 'long-description.md'); } diff --git a/packages/angular/cli/src/commands/update/cli.ts b/packages/angular/cli/src/commands/update/cli.ts index c2c7ed05f58c..b691b02f0bb0 100644 --- a/packages/angular/cli/src/commands/update/cli.ts +++ b/packages/angular/cli/src/commands/update/cli.ts @@ -60,7 +60,7 @@ interface UpdateCommandArgs { const ANGULAR_PACKAGES_REGEXP = /^@(?:angular|nguniversal)\//; const UPDATE_SCHEMATIC_COLLECTION = path.join(__dirname, 'schematic/collection.json'); -export class UpdateCommandModule extends CommandModule { +export default class UpdateCommandModule extends CommandModule { override scope = CommandScope.In; protected override shouldReportAnalytics = false; diff --git a/packages/angular/cli/src/commands/version/cli.ts b/packages/angular/cli/src/commands/version/cli.ts index 863b9e2102f4..90dca4fbc271 100644 --- a/packages/angular/cli/src/commands/version/cli.ts +++ b/packages/angular/cli/src/commands/version/cli.ts @@ -11,6 +11,7 @@ import { resolve } from 'path'; import { Argv } from 'yargs'; import { CommandModule, CommandModuleImplementation } from '../../command-builder/command-module'; import { colors } from '../../utilities/color'; +import { RootCommands } from '../command-config'; interface PartialPackageInfo { name: string; @@ -37,9 +38,12 @@ const PACKAGE_PATTERNS = [ /^webpack$/, ]; -export class VersionCommandModule extends CommandModule implements CommandModuleImplementation { +export default class VersionCommandModule + extends CommandModule + implements CommandModuleImplementation +{ command = 'version'; - aliases = ['v']; + aliases = RootCommands['version'].aliases; describe = 'Outputs Angular CLI version.'; longDescriptionPath?: string | undefined;