Skip to content
2 changes: 1 addition & 1 deletion packages/aws-cdk/lib/cli-arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface CliArguments {
/**
* The CLI command name
*/
readonly _: Command;
readonly _?: Command;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needed because cdk.json doesn't have a specific command.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just make it two different interface then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's necessary. CliArguments does/should not do any enforcing. Enforcement comes from sending our CLI input through yargs. Therefore, I think it makes sense that every option on CliArguments is optional. It's just a schema for config options.

Arguably, CliArguments is now a misnomer because it governs both CLI options and cdk.json options. But the goal is to make sure those two are one and the same anyway.

Would it alleviate your concerns if I renamed CliArguments into Arguments?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not CLI anymore, there's no reason to call it _. We only do that because of yargs.

I think we are turning it into structured user input, so maybe something along the lines of UserInput?

Copy link
Contributor Author

@kaizencc kaizencc Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will rename CliArguments to UserInput, and rename _ to command in a separate PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


/**
* Global options available to all CLI commands
Expand Down
196 changes: 195 additions & 1 deletion packages/aws-cdk/lib/convert-to-cli-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { CliArguments, GlobalOptions } from './cli-arguments';
import { Command } from './settings';

// @ts-ignore TS6133
export function convertToCliArgs(args: any): CliArguments {
export function convertYargsToCliArgs(args: any): CliArguments {
const globalOptions: GlobalOptions = {
app: args.app,
build: args.build,
Expand Down Expand Up @@ -254,3 +254,197 @@ export function convertToCliArgs(args: any): CliArguments {

return cliArguments;
}

// @ts-ignore TS6133
export function convertConfigToCliArgs(args: any): CliArguments {
const globalOptions: GlobalOptions = {
app: args.app,
build: args.build,
context: args.context,
plugin: args.plugin,
trace: args.trace,
strict: args.strict,
lookups: args.lookups,
ignoreErrors: args.ignoreErrors,
json: args.json,
verbose: args.verbose,
debug: args.debug,
profile: args.profile,
proxy: args.proxy,
caBundlePath: args.caBundlePath,
ec2creds: args.ec2creds,
versionReporting: args.versionReporting,
pathMetadata: args.pathMetadata,
assetMetadata: args.assetMetadata,
roleArn: args.roleArn,
staging: args.staging,
output: args.output,
notices: args.notices,
noColor: args.noColor,
ci: args.ci,
unstable: args.unstable,
};
const listOptions = {
long: args.list.long,
showDependencies: args.list.showDependencies,
};
const synthesizeOptions = {
exclusively: args.synthesize.exclusively,
validation: args.synthesize.validation,
quiet: args.synthesize.quiet,
};
const bootstrapOptions = {
bootstrapBucketName: args.bootstrap.bootstrapBucketName,
bootstrapKmsKeyId: args.bootstrap.bootstrapKmsKeyId,
examplePermissionsBoundary: args.bootstrap.examplePermissionsBoundary,
customPermissionsBoundary: args.bootstrap.customPermissionsBoundary,
bootstrapCustomerKey: args.bootstrap.bootstrapCustomerKey,
qualifier: args.bootstrap.qualifier,
publicAccessBlockConfiguration: args.bootstrap.publicAccessBlockConfiguration,
tags: args.bootstrap.tags,
execute: args.bootstrap.execute,
trust: args.bootstrap.trust,
trustForLookup: args.bootstrap.trustForLookup,
cloudformationExecutionPolicies: args.bootstrap.cloudformationExecutionPolicies,
force: args.bootstrap.force,
terminationProtection: args.bootstrap.terminationProtection,
showTemplate: args.bootstrap.showTemplate,
toolkitStackName: args.bootstrap.toolkitStackName,
template: args.bootstrap.template,
previousParameters: args.bootstrap.previousParameters,
};
const gcOptions = {
action: args.gc.action,
type: args.gc.type,
rollbackBufferDays: args.gc.rollbackBufferDays,
createdBufferDays: args.gc.createdBufferDays,
confirm: args.gc.confirm,
bootstrapStackName: args.gc.bootstrapStackName,
};
const deployOptions = {
all: args.deploy.all,
buildExclude: args.deploy.buildExclude,
exclusively: args.deploy.exclusively,
requireApproval: args.deploy.requireApproval,
notificationArns: args.deploy.notificationArns,
tags: args.deploy.tags,
execute: args.deploy.execute,
changeSetName: args.deploy.changeSetName,
method: args.deploy.method,
importExistingResources: args.deploy.importExistingResources,
force: args.deploy.force,
parameters: args.deploy.parameters,
outputsFile: args.deploy.outputsFile,
previousParameters: args.deploy.previousParameters,
toolkitStackName: args.deploy.toolkitStackName,
progress: args.deploy.progress,
rollback: args.deploy.rollback,
hotswap: args.deploy.hotswap,
hotswapFallback: args.deploy.hotswapFallback,
watch: args.deploy.watch,
logs: args.deploy.logs,
concurrency: args.deploy.concurrency,
assetParallelism: args.deploy.assetParallelism,
assetPrebuild: args.deploy.assetPrebuild,
ignoreNoStacks: args.deploy.ignoreNoStacks,
};
const rollbackOptions = {
all: args.rollback.all,
toolkitStackName: args.rollback.toolkitStackName,
force: args.rollback.force,
validateBootstrapVersion: args.rollback.validateBootstrapVersion,
orphan: args.rollback.orphan,
};
const importOptions = {
execute: args.import.execute,
changeSetName: args.import.changeSetName,
toolkitStackName: args.import.toolkitStackName,
rollback: args.import.rollback,
force: args.import.force,
recordResourceMapping: args.import.recordResourceMapping,
resourceMapping: args.import.resourceMapping,
};
const watchOptions = {
buildExclude: args.watch.buildExclude,
exclusively: args.watch.exclusively,
changeSetName: args.watch.changeSetName,
force: args.watch.force,
toolkitStackName: args.watch.toolkitStackName,
progress: args.watch.progress,
rollback: args.watch.rollback,
hotswap: args.watch.hotswap,
hotswapFallback: args.watch.hotswapFallback,
logs: args.watch.logs,
concurrency: args.watch.concurrency,
};
const destroyOptions = {
all: args.destroy.all,
exclusively: args.destroy.exclusively,
force: args.destroy.force,
};
const diffOptions = {
exclusively: args.diff.exclusively,
contextLines: args.diff.contextLines,
template: args.diff.template,
strict: args.diff.strict,
securityOnly: args.diff.securityOnly,
fail: args.diff.fail,
processed: args.diff.processed,
quiet: args.diff.quiet,
changeSet: args.diff.changeSet,
};
const metadataOptions = {};
const acknowledgeOptions = {};
const noticesOptions = {
unacknowledged: args.notices.unacknowledged,
};
const initOptions = {
language: args.init.language,
list: args.init.list,
generateOnly: args.init.generateOnly,
};
const migrateOptions = {
stackName: args.migrate.stackName,
language: args.migrate.language,
account: args.migrate.account,
region: args.migrate.region,
fromPath: args.migrate.fromPath,
fromStack: args.migrate.fromStack,
outputPath: args.migrate.outputPath,
fromScan: args.migrate.fromScan,
filter: args.migrate.filter,
compress: args.migrate.compress,
};
const contextOptions = {
reset: args.context.reset,
force: args.context.force,
clear: args.context.clear,
};
const docsOptions = {
browser: args.docs.browser,
};
const doctorOptions = {};
const cliArguments: CliArguments = {
globalOptions,
list: listOptions,
synthesize: synthesizeOptions,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should synth be the main name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that can be done but that would have to be a change to config.ts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the impact here? When are we committing to a specific action name in cdk.json? I think we need to make that call before we are committing to it. WDYT about synth vs synthesize though?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i will create a separate PR that changes the default name of synthesize to synth. I believe we've standardized on that, and up until now yargs allows them to work interchangably. Existing configurations should not change, and then we will allow synth as the keyword in cdk.json to provide default configs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bootstrap: bootstrapOptions,
gc: gcOptions,
deploy: deployOptions,
rollback: rollbackOptions,
import: importOptions,
watch: watchOptions,
destroy: destroyOptions,
diff: diffOptions,
metadata: metadataOptions,
acknowledge: acknowledgeOptions,
notices: noticesOptions,
init: initOptions,
migrate: migrateOptions,
context: contextOptions,
docs: docsOptions,
doctor: doctorOptions,
};

return cliArguments;
}
8 changes: 4 additions & 4 deletions packages/aws-cdk/test/cli-arguments.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { convertToCliArgs } from '../lib/convert-to-cli-args';
import { convertYargsToCliArgs } from '../lib/convert-to-cli-args';
import { parseCommandLineArguments } from '../lib/parse-command-line-arguments';

test('yargs object can be converted to cli arguments', async () => {
const input = await parseCommandLineArguments(['deploy', '-R', '-v', '--ci']);

const result = convertToCliArgs(input);
const result = convertYargsToCliArgs(input);

expect(result).toEqual({
_: 'deploy',
Expand Down Expand Up @@ -69,7 +69,7 @@ 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);
const result = convertYargsToCliArgs(input);

expect(result).toEqual({
_: 'deploy',
Expand All @@ -83,7 +83,7 @@ test('positional argument is correctly passed through -- variadic', async () =>
test('positional argument is correctly passed through -- single', async () => {
const input = await parseCommandLineArguments(['acknowledge', 'id1', '-v', '--ci']);

const result = convertToCliArgs(input);
const result = convertYargsToCliArgs(input);

expect(result).toEqual({
_: 'acknowledge',
Expand Down
54 changes: 51 additions & 3 deletions tools/@aws-cdk/cli-args-gen/lib/cli-args-function-gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function renderCliArgsFunc(config: CliConfig): Promise<string> {
scope.addImport(new SelectiveModuleImport(scope, './settings', ['Command']));

const createCliArguments = new FreeFunction(scope, {
name: 'convertToCliArgs',
name: 'convertYargsToCliArgs',
export: true,
returnType: cliArgType,
parameters: [
Expand All @@ -27,6 +27,16 @@ export async function renderCliArgsFunc(config: CliConfig): Promise<string> {
});
createCliArguments.addBody(code.expr.directCode(buildCliArgsFunction(config)));

const createConfigArguments = new FreeFunction(scope, {
name: 'convertConfigToCliArgs',
export: true,
returnType: cliArgType,
parameters: [
{ name: 'args', type: Type.ANY },
],
});
createConfigArguments.addBody(code.expr.directCode(buildConfigArgsFunction(config)));

const ts = new TypeScriptRenderer({
disabledEsLintRules: [EsLintRules.MAX_LEN], // the default disabled rules result in 'Definition for rule 'prettier/prettier' was not found'
}).render(scope);
Expand All @@ -50,6 +60,17 @@ function buildCliArgsFunction(config: CliConfig): string {
].join('\n');
}

function buildConfigArgsFunction(config: CliConfig): string {
const globalOptions = buildGlobalOptions(config);
const commandList = buildCommandsList(config);
const configArgs = buildConfigArgs(config);
return [
globalOptions,
commandList,
configArgs,
].join('\n');
}

function buildGlobalOptions(config: CliConfig): string {
const globalOptionExprs = ['const globalOptions: GlobalOptions = {'];
for (const optionName of Object.keys(config.globalOptions)) {
Expand All @@ -60,6 +81,16 @@ function buildGlobalOptions(config: CliConfig): string {
return globalOptionExprs.join('\n');
}

function buildCommandsList(config: CliConfig): string {
const commandOptions = [];
for (const commandName of Object.keys(config.commands)) {
commandOptions.push(`const ${kebabToCamelCase(commandName)}Options = {`);
commandOptions.push(...buildCommandOptions(config.commands[commandName], kebabToCamelCase(commandName)));
commandOptions.push('}');
}
return commandOptions.join('\n');
}

function buildCommandSwitch(config: CliConfig): string {
const commandSwitchExprs = ['let commandOptions;', 'switch (args._[0] as Command) {'];
for (const commandName of Object.keys(config.commands)) {
Expand All @@ -76,11 +107,15 @@ function buildCommandSwitch(config: CliConfig): string {
return commandSwitchExprs.join('\n');
}

function buildCommandOptions(options: CliAction): string[] {
function buildCommandOptions(options: CliAction, prefix?: string): string[] {
const commandOptions: string[] = [];
for (const optionName of Object.keys(options.options ?? {})) {
const name = kebabToCamelCase(optionName);
commandOptions.push(`'${name}': args.${name},`);
if (prefix) {
commandOptions.push(`'${name}': args.${prefix}.${name},`);
} else {
commandOptions.push(`'${name}': args.${name},`);
}
}
return commandOptions;
}
Expand All @@ -103,3 +138,16 @@ function buildCliArgs(): string {
'return cliArguments',
].join('\n');
}

function buildConfigArgs(config: CliConfig): string {
return [
'const cliArguments: CliArguments = {',
'globalOptions,',
...(Object.keys(config.commands).map((commandName) => {
return `'${commandName}': ${kebabToCamelCase(commandName)}Options,`;
})),
'}',
'',
'return cliArguments',
].join('\n');
}
1 change: 1 addition & 0 deletions tools/@aws-cdk/cli-args-gen/lib/cli-args-gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export async function renderCliArgsType(config: CliConfig): Promise<string> {
docs: {
summary: 'The CLI command name',
},
optional: true,
});

// add global options
Expand Down
21 changes: 20 additions & 1 deletion tools/@aws-cdk/cli-args-gen/test/cli-args-function-gen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('render', () => {
import { Command } from './settings';

// @ts-ignore TS6133
export function convertToCliArgs(args: any): CliArguments {
export function convertYargsToCliArgs(args: any): CliArguments {
const globalOptions: GlobalOptions = {
app: args.app,
debug: args.debug,
Expand All @@ -76,6 +76,25 @@ describe('render', () => {

return cliArguments;
}

// @ts-ignore TS6133
export function convertConfigToCliArgs(args: any): CliArguments {
const globalOptions: GlobalOptions = {
app: args.app,
debug: args.debug,
context: args.context,
plugin: args.plugin,
};
const deployOptions = {
all: args.all,
};
const cliArguments: CliArguments = {
globalOptions,
deploy: deployOptions,
};

return cliArguments;
}
"
`);
});
Expand Down
Loading
Loading