From caf24150992f84e3b28eb8c02ee7dc2f5b5ca84f Mon Sep 17 00:00:00 2001 From: Kaizen Conroy <36202692+kaizencc@users.noreply.github.com> Date: Sun, 5 Jan 2025 10:51:19 -0500 Subject: [PATCH] chore(cli): model positional arguments in CliArguments (#32718) **This PR does not change CLI functionality (yet)** This PR models positional arguments in `CliArguments`. Currently they are supposed to be modeled as part of the the default property `_: [Command, ...string]`. This means that in `cli.ts` we mean to use it as so: `args._[1]` instead of `args.ID`. This is a downgrade, so I've updated the following: Positional arguments are now modeled in `CliArguments` so if the `deploy` command has `STACKS` argument, it shows up as part of its `DeployOptions`. With this model we will replace `args.ID` with` args.acknowledge.ID` which I believe to be acceptable. Now, the default property is modeled as `_: Command` so that we don't duplicate information. ### Checklist - [d] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk/lib/cli-arguments.ts | 73 +++++++++++++- packages/aws-cdk/lib/convert-to-cli-args.ts | 21 +++- packages/aws-cdk/test/cli-arguments.test.ts | 31 +++++- .../cli-args-gen/lib/cli-args-function-gen.ts | 10 +- .../@aws-cdk/cli-args-gen/lib/cli-args-gen.ts | 17 +++- .../test/cli-args-function-gen.test.ts | 7 +- .../cli-args-gen/test/cli-args-gen.test.ts | 98 ++++++++++++++++++- 7 files changed, 241 insertions(+), 16 deletions(-) diff --git a/packages/aws-cdk/lib/cli-arguments.ts b/packages/aws-cdk/lib/cli-arguments.ts index 126eee3ad4f94..8dc3afcc24c09 100644 --- a/packages/aws-cdk/lib/cli-arguments.ts +++ b/packages/aws-cdk/lib/cli-arguments.ts @@ -12,9 +12,9 @@ import { Command } from './settings'; */ export interface CliArguments { /** - * The CLI command name followed by any properties of the command + * The CLI command name */ - readonly _: [Command, ...string[]]; + readonly _: Command; /** * Global options available to all CLI commands @@ -327,6 +327,11 @@ export interface ListOptions { * @default - false */ readonly showDependencies?: boolean; + + /** + * Positional argument for list + */ + readonly STACKS?: Array; } /** @@ -361,6 +366,11 @@ export interface SynthesizeOptions { * @default - false */ readonly quiet?: boolean; + + /** + * Positional argument for synthesize + */ + readonly STACKS?: Array; } /** @@ -504,6 +514,11 @@ export interface BootstrapOptions { * @default - true */ readonly previousParameters?: boolean; + + /** + * Positional argument for bootstrap + */ + readonly ENVIRONMENTS?: Array; } /** @@ -553,6 +568,11 @@ export interface GcOptions { * @default - undefined */ readonly bootstrapStackName?: string; + + /** + * Positional argument for gc + */ + readonly ENVIRONMENTS?: Array; } /** @@ -748,6 +768,11 @@ export interface DeployOptions { * @default - false */ readonly ignoreNoStacks?: boolean; + + /** + * Positional argument for deploy + */ + readonly STACKS?: Array; } /** @@ -792,6 +817,11 @@ export interface RollbackOptions { * @default - [] */ readonly orphan?: Array; + + /** + * Positional argument for rollback + */ + readonly STACKS?: Array; } /** @@ -854,6 +884,11 @@ export interface ImportOptions { * @default - undefined */ readonly resourceMapping?: string; + + /** + * Positional argument for import + */ + readonly STACK?: string; } /** @@ -944,6 +979,11 @@ export interface WatchOptions { * @default - 1 */ readonly concurrency?: number; + + /** + * Positional argument for watch + */ + readonly STACKS?: Array; } /** @@ -976,6 +1016,11 @@ export interface DestroyOptions { * @default - undefined */ readonly force?: boolean; + + /** + * Positional argument for destroy + */ + readonly STACKS?: Array; } /** @@ -1052,6 +1097,11 @@ export interface DiffOptions { * @default - true */ readonly changeSet?: boolean; + + /** + * Positional argument for diff + */ + readonly STACKS?: Array; } /** @@ -1059,7 +1109,12 @@ export interface DiffOptions { * * @struct */ -export interface MetadataOptions {} +export interface MetadataOptions { + /** + * Positional argument for metadata + */ + readonly STACK?: string; +} /** * Acknowledge a notice so that it does not show up anymore @@ -1068,7 +1123,12 @@ export interface MetadataOptions {} * * @struct */ -export interface AcknowledgeOptions {} +export interface AcknowledgeOptions { + /** + * Positional argument for acknowledge + */ + readonly ID?: string; +} /** * Returns a list of relevant notices @@ -1114,6 +1174,11 @@ export interface InitOptions { * @default - false */ readonly generateOnly?: boolean; + + /** + * Positional argument for init + */ + readonly TEMPLATE?: string; } /** diff --git a/packages/aws-cdk/lib/convert-to-cli-args.ts b/packages/aws-cdk/lib/convert-to-cli-args.ts index 345649e6b20fe..3fc1b9a2bc541 100644 --- a/packages/aws-cdk/lib/convert-to-cli-args.ts +++ b/packages/aws-cdk/lib/convert-to-cli-args.ts @@ -41,6 +41,7 @@ export function convertToCliArgs(args: any): CliArguments { commandOptions = { long: args.long, showDependencies: args.showDependencies, + STACKS: args.STACKS, }; break; @@ -49,6 +50,7 @@ export function convertToCliArgs(args: any): CliArguments { exclusively: args.exclusively, validation: args.validation, quiet: args.quiet, + STACKS: args.STACKS, }; break; @@ -72,6 +74,7 @@ export function convertToCliArgs(args: any): CliArguments { toolkitStackName: args.toolkitStackName, template: args.template, previousParameters: args.previousParameters, + ENVIRONMENTS: args.ENVIRONMENTS, }; break; @@ -83,6 +86,7 @@ export function convertToCliArgs(args: any): CliArguments { createdBufferDays: args.createdBufferDays, confirm: args.confirm, bootstrapStackName: args.bootstrapStackName, + ENVIRONMENTS: args.ENVIRONMENTS, }; break; @@ -113,6 +117,7 @@ export function convertToCliArgs(args: any): CliArguments { assetParallelism: args.assetParallelism, assetPrebuild: args.assetPrebuild, ignoreNoStacks: args.ignoreNoStacks, + STACKS: args.STACKS, }; break; @@ -123,6 +128,7 @@ export function convertToCliArgs(args: any): CliArguments { force: args.force, validateBootstrapVersion: args.validateBootstrapVersion, orphan: args.orphan, + STACKS: args.STACKS, }; break; @@ -135,6 +141,7 @@ export function convertToCliArgs(args: any): CliArguments { force: args.force, recordResourceMapping: args.recordResourceMapping, resourceMapping: args.resourceMapping, + STACK: args.STACK, }; break; @@ -151,6 +158,7 @@ export function convertToCliArgs(args: any): CliArguments { hotswapFallback: args.hotswapFallback, logs: args.logs, concurrency: args.concurrency, + STACKS: args.STACKS, }; break; @@ -159,6 +167,7 @@ export function convertToCliArgs(args: any): CliArguments { all: args.all, exclusively: args.exclusively, force: args.force, + STACKS: args.STACKS, }; break; @@ -173,15 +182,20 @@ export function convertToCliArgs(args: any): CliArguments { processed: args.processed, quiet: args.quiet, changeSet: args.changeSet, + STACKS: args.STACKS, }; break; case 'metadata': - commandOptions = {}; + commandOptions = { + STACK: args.STACK, + }; break; case 'acknowledge': - commandOptions = {}; + commandOptions = { + ID: args.ID, + }; break; case 'notices': @@ -195,6 +209,7 @@ export function convertToCliArgs(args: any): CliArguments { language: args.language, list: args.list, generateOnly: args.generateOnly, + TEMPLATE: args.TEMPLATE, }; break; @@ -232,7 +247,7 @@ export function convertToCliArgs(args: any): CliArguments { break; } const cliArguments: CliArguments = { - _: args._, + _: args._[0], globalOptions, [args._[0]]: commandOptions, }; diff --git a/packages/aws-cdk/test/cli-arguments.test.ts b/packages/aws-cdk/test/cli-arguments.test.ts index d749efb2785bd..3024bfaae524a 100644 --- a/packages/aws-cdk/test/cli-arguments.test.ts +++ b/packages/aws-cdk/test/cli-arguments.test.ts @@ -7,7 +7,7 @@ test('yargs object can be converted to cli arguments', async () => { const result = convertToCliArgs(input); expect(result).toEqual({ - _: ['deploy'], + _: 'deploy', globalOptions: { app: undefined, assetMetadata: undefined, @@ -36,6 +36,7 @@ test('yargs object can be converted to cli arguments', async () => { output: undefined, }, deploy: { + STACKS: undefined, all: false, assetParallelism: undefined, assetPrebuild: true, @@ -64,3 +65,31 @@ test('yargs object can be converted to cli arguments', async () => { }, }); }); + +test('positional argument is correctly passed through -- variadic', async () => { + const input = await parseCommandLineArguments(['deploy', 'stack1', 'stack2', '-R', '-v', '--ci']); + + const result = convertToCliArgs(input); + + expect(result).toEqual({ + _: 'deploy', + deploy: expect.objectContaining({ + STACKS: ['stack1', 'stack2'], + }), + globalOptions: expect.anything(), + }); +}); + +test('positional argument is correctly passed through -- single', async () => { + const input = await parseCommandLineArguments(['acknowledge', 'id1', '-v', '--ci']); + + const result = convertToCliArgs(input); + + expect(result).toEqual({ + _: 'acknowledge', + acknowledge: expect.objectContaining({ + ID: 'id1', + }), + globalOptions: expect.anything(), + }); +}); diff --git a/tools/@aws-cdk/cli-args-gen/lib/cli-args-function-gen.ts b/tools/@aws-cdk/cli-args-gen/lib/cli-args-function-gen.ts index 67aa076528238..cb7e6a6dd22d4 100644 --- a/tools/@aws-cdk/cli-args-gen/lib/cli-args-function-gen.ts +++ b/tools/@aws-cdk/cli-args-gen/lib/cli-args-function-gen.ts @@ -67,6 +67,7 @@ function buildCommandSwitch(config: CliConfig): string { `case '${commandName}':`, 'commandOptions = {', ...buildCommandOptions(config.commands[commandName]), + ...(config.commands[commandName].arg ? [buildPositionalArguments(config.commands[commandName].arg)] : []), '};', `break; `); @@ -84,10 +85,17 @@ function buildCommandOptions(options: CliAction): string[] { return commandOptions; } +function buildPositionalArguments(arg: { name: string; variadic: boolean }): string { + if (arg.variadic) { + return `${arg.name}: args.${arg.name}`; + } + return `${arg.name}: args.${arg.name}`; +} + function buildCliArgs(): string { return [ 'const cliArguments: CliArguments = {', - '_: args._,', + '_: args._[0],', 'globalOptions,', '[args._[0]]: commandOptions', '}', diff --git a/tools/@aws-cdk/cli-args-gen/lib/cli-args-gen.ts b/tools/@aws-cdk/cli-args-gen/lib/cli-args-gen.ts index 139e4dd7a6c9d..859319d028661 100644 --- a/tools/@aws-cdk/cli-args-gen/lib/cli-args-gen.ts +++ b/tools/@aws-cdk/cli-args-gen/lib/cli-args-gen.ts @@ -26,9 +26,9 @@ export async function renderCliArgsType(config: CliConfig): Promise { cliArgType.addProperty({ name: '_', - type: Type.ambient(`[${commandEnum}, ...string[]]`), + type: commandEnum, docs: { - summary: 'The CLI command name followed by any properties of the command', + summary: 'The CLI command name', }, }); @@ -73,6 +73,7 @@ export async function renderCliArgsType(config: CliConfig): Promise { }, }); + // add command level options for (const [optionName, option] of Object.entries(command.options ?? {})) { commandType.addProperty({ name: kebabToCamelCase(optionName), @@ -88,6 +89,18 @@ export async function renderCliArgsType(config: CliConfig): Promise { }); } + // add positional argument associated with the command + if (command.arg) { + commandType.addProperty({ + name: command.arg.name, + type: command.arg.variadic ? Type.arrayOf(Type.STRING) : Type.STRING, + docs: { + summary: `Positional argument for ${commandName}`, + }, + optional: true, + }); + } + cliArgType.addProperty({ name: kebabToCamelCase(commandName), type: Type.fromName(scope, commandType.name), diff --git a/tools/@aws-cdk/cli-args-gen/test/cli-args-function-gen.test.ts b/tools/@aws-cdk/cli-args-gen/test/cli-args-function-gen.test.ts index 2f5f1a96e57ed..24b92c7fe291e 100644 --- a/tools/@aws-cdk/cli-args-gen/test/cli-args-function-gen.test.ts +++ b/tools/@aws-cdk/cli-args-gen/test/cli-args-function-gen.test.ts @@ -26,6 +26,10 @@ describe('render', () => { }, commands: { deploy: { + arg: { + name: 'STACKS', + variadic: true, + }, description: 'Deploy a stack', options: { all: { @@ -60,11 +64,12 @@ describe('render', () => { case 'deploy': commandOptions = { all: args.all, + STACKS: args.STACKS, }; break; } const cliArguments: CliArguments = { - _: args._, + _: args._[0], globalOptions, [args._[0]]: commandOptions, }; diff --git a/tools/@aws-cdk/cli-args-gen/test/cli-args-gen.test.ts b/tools/@aws-cdk/cli-args-gen/test/cli-args-gen.test.ts index cdae7d1a6d967..a323974bee1e8 100644 --- a/tools/@aws-cdk/cli-args-gen/test/cli-args-gen.test.ts +++ b/tools/@aws-cdk/cli-args-gen/test/cli-args-gen.test.ts @@ -53,9 +53,9 @@ describe('render', () => { */ export interface CliArguments { /** - * The CLI command name followed by any properties of the command + * The CLI command name */ - readonly _: [Command, ...string[]]; + readonly _: Command; /** * Global options available to all CLI commands @@ -155,9 +155,9 @@ describe('render', () => { */ export interface CliArguments { /** - * The CLI command name followed by any properties of the command + * The CLI command name */ - readonly _: [Command, ...string[]]; + readonly _: Command; /** * Global options available to all CLI commands @@ -200,4 +200,94 @@ describe('render', () => { " `); }); + + test('positional arguments', async () => { + const config: CliConfig = { + commands: { + deploy: { + arg: { + name: 'STACKS', + variadic: true, + }, + description: 'deploy', + }, + acknowledge: { + arg: { + name: 'ID', + variadic: false, + }, + description: 'acknowledge', + }, + }, + globalOptions: {}, + }; + + expect(await renderCliArgsType(config)).toMatchInlineSnapshot(` + "// ------------------------------------------------------------------------------------------- + // GENERATED FROM packages/aws-cdk/lib/config.ts. + // Do not edit by hand; all changes will be overwritten at build time from the config file. + // ------------------------------------------------------------------------------------------- + /* eslint-disable @stylistic/max-len */ + import { Command } from './settings'; + + /** + * The structure of the CLI configuration, generated from packages/aws-cdk/lib/config.ts + * + * @struct + */ + export interface CliArguments { + /** + * The CLI command name + */ + readonly _: Command; + + /** + * Global options available to all CLI commands + */ + readonly globalOptions?: GlobalOptions; + + /** + * deploy + */ + readonly deploy?: DeployOptions; + + /** + * acknowledge + */ + readonly acknowledge?: AcknowledgeOptions; + } + + /** + * Global options available to all CLI commands + * + * @struct + */ + export interface GlobalOptions {} + + /** + * deploy + * + * @struct + */ + export interface DeployOptions { + /** + * Positional argument for deploy + */ + readonly STACKS?: Array; + } + + /** + * acknowledge + * + * @struct + */ + export interface AcknowledgeOptions { + /** + * Positional argument for acknowledge + */ + readonly ID?: string; + } + " + `); + }); });