Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cli): array arguments in cdk.json are ignored #33107

Merged
merged 3 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 10 additions & 14 deletions packages/aws-cdk/lib/cli/parse-command-line-arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,13 @@ export function parseCommandLineArguments(args: Array<string>): any {
desc: 'Command-line for a pre-synth build',
})
.option('context', {
default: [],
type: 'array',
alias: 'c',
desc: 'Add contextual string parameter (KEY=VALUE)',
nargs: 1,
requiresArg: true,
})
.option('plugin', {
default: [],
type: 'array',
alias: 'p',
desc: 'Name or path of a node package that extend the CDK features. Can be specified multiple times',
Expand Down Expand Up @@ -151,9 +149,9 @@ export function parseCommandLineArguments(args: Array<string>): any {
desc: 'Force CI detection. If CI=true then logs will be sent to stdout instead of stderr',
})
.option('unstable', {
default: [],
type: 'array',
desc: 'Opt in to unstable features. The flag indicates that the scope and API of a feature might still change. Otherwise the feature is generally production ready and fully supported. Can be specified multiple times.',
default: [],
nargs: 1,
requiresArg: true,
})
Expand Down Expand Up @@ -237,10 +235,10 @@ export function parseCommandLineArguments(args: Array<string>): any {
desc: 'Block public access configuration on CDK toolkit bucket (enabled by default) ',
})
.option('tags', {
default: [],
type: 'array',
alias: 't',
desc: 'Tags to add for the stack (KEY=VALUE)',
default: [],
nargs: 1,
requiresArg: true,
})
Expand All @@ -250,30 +248,30 @@ export function parseCommandLineArguments(args: Array<string>): any {
desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)',
})
.option('trust', {
default: [],
type: 'array',
desc: 'The AWS account IDs that should be trusted to perform deployments into this environment (may be repeated, modern bootstrapping only)',
default: [],
nargs: 1,
requiresArg: true,
})
.option('trust-for-lookup', {
default: [],
type: 'array',
desc: 'The AWS account IDs that should be trusted to look up values in this environment (may be repeated, modern bootstrapping only)',
default: [],
nargs: 1,
requiresArg: true,
})
.option('untrust', {
default: [],
type: 'array',
desc: 'The AWS account IDs that should not be trusted by this environment (may be repeated, modern bootstrapping only)',
default: [],
nargs: 1,
requiresArg: true,
})
.option('cloudformation-execution-policies', {
default: [],
type: 'array',
desc: 'The Managed Policy ARNs that should be attached to the role performing deployments into this environment (may be repeated, modern bootstrapping only)',
default: [],
nargs: 1,
requiresArg: true,
})
Expand Down Expand Up @@ -356,10 +354,10 @@ export function parseCommandLineArguments(args: Array<string>): any {
desc: 'Deploy all available stacks',
})
.option('build-exclude', {
default: [],
type: 'array',
alias: 'E',
desc: 'Do not rebuild asset with the given ID. Can be specified multiple times',
default: [],
nargs: 1,
requiresArg: true,
})
Expand All @@ -382,7 +380,6 @@ export function parseCommandLineArguments(args: Array<string>): any {
requiresArg: true,
})
.option('tags', {
default: [],
type: 'array',
alias: 't',
desc: 'Tags to add to the stack (KEY=VALUE), overrides tags from Cloud Assembly (deprecated)',
Expand Down Expand Up @@ -420,9 +417,9 @@ export function parseCommandLineArguments(args: Array<string>): any {
desc: 'Always deploy stack even if templates are identical',
})
.option('parameters', {
default: {},
type: 'array',
desc: 'Additional parameters passed to CloudFormation at deploy time (STACK:KEY=VALUE)',
default: {},
nargs: 1,
requiresArg: true,
})
Expand Down Expand Up @@ -524,9 +521,9 @@ export function parseCommandLineArguments(args: Array<string>): any {
desc: "Whether to validate the bootstrap stack version. Defaults to 'true', disable with --no-validate-bootstrap-version.",
})
.option('orphan', {
default: [],
type: 'array',
desc: 'Orphan the given resources, identified by their logical ID (can be specified multiple times)',
default: [],
nargs: 1,
requiresArg: true,
}),
Expand Down Expand Up @@ -578,10 +575,10 @@ export function parseCommandLineArguments(args: Array<string>): any {
.command('watch [STACKS..]', "Shortcut for 'deploy --watch'", (yargs: Argv) =>
yargs
.option('build-exclude', {
default: [],
type: 'array',
alias: 'E',
desc: 'Do not rebuild asset with the given ID. Can be specified multiple times',
default: [],
nargs: 1,
requiresArg: true,
})
Expand Down Expand Up @@ -796,7 +793,6 @@ export function parseCommandLineArguments(args: Array<string>): any {
desc: 'Determines if a new scan should be created, or the last successful existing scan should be used \n options are "new" or "most-recent"',
})
.option('filter', {
default: [],
type: 'array',
desc: 'Filters the resource scan based on the provided criteria in the following format: "key1=value1,key2=value2"\n This field can be passed multiple times for OR style filtering: \n filtering options: \n resource-identifier: A key-value pair that identifies the target resource. i.e. {"ClusterName", "myCluster"}\n resource-type-prefix: A string that represents a type-name prefix. i.e. "AWS::DynamoDB::"\n tag-key: a string that matches resources with at least one tag with the provided key. i.e. "myTagKey"\n tag-value: a string that matches resources with at least one tag with the provided value. i.e. "myTagValue"',
nargs: 1,
Expand Down
8 changes: 4 additions & 4 deletions packages/aws-cdk/lib/cli/user-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,14 @@ export interface GlobalOptions {
/**
* Add contextual string parameter (KEY=VALUE)
*
* @default - []
* @default - undefined
*/
readonly context?: Array<string>;

/**
* Name or path of a node package that extend the CDK features. Can be specified multiple times
*
* @default - []
* @default - undefined
*/
readonly plugin?: Array<string>;

Expand Down Expand Up @@ -632,7 +632,7 @@ export interface DeployOptions {
*
* aliases: t
*
* @default - []
* @default - undefined
*/
readonly tags?: Array<string>;

Expand Down Expand Up @@ -1264,7 +1264,7 @@ export interface MigrateOptions {
* tag-key: a string that matches resources with at least one tag with the provided key. i.e. "myTagKey"
* tag-value: a string that matches resources with at least one tag with the provided value. i.e. "myTagValue"
*
* @default - []
* @default - undefined
*/
readonly filter?: Array<string>;

Expand Down
6 changes: 3 additions & 3 deletions packages/aws-cdk/test/cli/cli-arguments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ describe('yargs', () => {
assetMetadata: undefined,
build: undefined,
caBundlePath: undefined,
context: [],
context: undefined,
ignoreErrors: false,
noColor: false,
pathMetadata: undefined,
plugin: [],
plugin: undefined,
profile: undefined,
proxy: undefined,
roleArn: undefined,
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('yargs', () => {
progress: undefined,
requireApproval: undefined,
rollback: false,
tags: [],
tags: undefined,
toolkitStackName: undefined,
watch: undefined,
},
Expand Down
31 changes: 31 additions & 0 deletions packages/aws-cdk/test/cli/user-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as os from 'os';
import * as fs_path from 'path';
import * as fs from 'fs-extra';
import { Configuration, PROJECT_CONFIG, PROJECT_CONTEXT } from '../../lib/cli/user-configuration';
import { parseCommandLineArguments } from '../../lib/cli/parse-command-line-arguments';

// mock fs deeply
jest.mock('fs-extra');
Expand Down Expand Up @@ -112,3 +113,33 @@ test('Can specify the `quiet` key in the user config', async () => {

expect(config.settings.get(['quiet'])).toBe(true);
});

test('array settings are not overridden by yarg defaults', async () => {
// GIVEN
const GIVEN_CONFIG: Map<string, any> = new Map([
[PROJECT_CONFIG, {
plugin: ['dummy'],
}],
]);
const argsWithPlugin = await parseCommandLineArguments(['ls', '--plugin', '[]']);
const argsWithoutPlugin = await parseCommandLineArguments(['ls']);

// WHEN
mockedFs.pathExists.mockImplementation(path => {
return GIVEN_CONFIG.has(path);
});
mockedFs.readJSON.mockImplementation(path => {
return GIVEN_CONFIG.get(path);
});

const configWithPlugin = await new Configuration({
commandLineArguments: argsWithPlugin,
}).load();
const configWithoutPlugin = await new Configuration({
commandLineArguments: argsWithoutPlugin,
}).load();

// THEN
expect(configWithPlugin.settings.get(['plugin'])).toEqual(['[]']);
expect(configWithoutPlugin.settings.get(['plugin'])).toEqual(['dummy']);
});
11 changes: 5 additions & 6 deletions tools/@aws-cdk/user-input-gen/lib/user-input-gen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Module, SelectiveModuleImport, StructType, Type, TypeScriptRenderer } from '@cdklabs/typewriter';
import { EsLintRules } from '@cdklabs/typewriter/lib/eslint-rules';
import * as prettier from 'prettier';
import { generateDefault, kebabToCamelCase, kebabToPascal } from './util';
import { kebabToCamelCase, kebabToPascal } from './util';
import { CliConfig } from './yargs-types';

export async function renderUserInputType(config: CliConfig): Promise<string> {
Expand Down Expand Up @@ -46,7 +46,7 @@ export async function renderUserInputType(config: CliConfig): Promise<string> {
name: kebabToCamelCase(optionName),
type: convertType(option.type, option.count),
docs: {
default: normalizeDefault(option.default, option.type),
default: normalizeDefault(option.default),
summary: option.desc,
deprecated: option.deprecated ? String(option.deprecated) : undefined,
},
Expand Down Expand Up @@ -81,7 +81,7 @@ export async function renderUserInputType(config: CliConfig): Promise<string> {
type: convertType(option.type, option.count),
docs: {
// Notification Arns is a special property where undefined and [] mean different things
default: optionName === 'notification-arns' ? 'undefined' : normalizeDefault(option.default, option.type),
default: optionName === 'notification-arns' ? 'undefined' : normalizeDefault(option.default),
summary: option.desc,
deprecated: option.deprecated ? String(option.deprecated) : undefined,
remarks: option.alias ? `aliases: ${Array.isArray(option.alias) ? option.alias.join(' ') : option.alias}` : undefined,
Expand Down Expand Up @@ -140,7 +140,7 @@ function convertType(type: 'string' | 'array' | 'number' | 'boolean' | 'count',
}
}

function normalizeDefault(defaultValue: any, type: string): string {
function normalizeDefault(defaultValue: any): string {
switch (typeof defaultValue) {
case 'boolean':
case 'string':
Expand All @@ -153,7 +153,6 @@ function normalizeDefault(defaultValue: any, type: string): string {
case 'undefined':
case 'function':
default:
const generatedDefault = generateDefault(type);
return generatedDefault ? JSON.stringify(generatedDefault) : 'undefined';
return 'undefined';
}
}
4 changes: 0 additions & 4 deletions tools/@aws-cdk/user-input-gen/lib/util.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { code, Expression } from '@cdklabs/typewriter';

export function generateDefault(type: string) {
return type === 'array' ? [] : undefined;
}

export function lit(value: any): Expression {
switch (value) {
case undefined:
Expand Down
10 changes: 5 additions & 5 deletions tools/@aws-cdk/user-input-gen/lib/yargs-gen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { $E, Expression, ExternalModule, FreeFunction, IScope, Module, SelectiveModuleImport, Statement, ThingSymbol, Type, TypeScriptRenderer, code, expr } from '@cdklabs/typewriter';
import { EsLintRules } from '@cdklabs/typewriter/lib/eslint-rules';
import * as prettier from 'prettier';
import { generateDefault, lit } from './util';
import { lit } from './util';
import { CliConfig, CliOption, YargsOption } from './yargs-types';

// to import lodash.clonedeep properly, we would need to set esModuleInterop: true
Expand Down Expand Up @@ -114,10 +114,10 @@ function makeOptions(prefix: Expression, options: { [optionName: string]: CliOpt
let optionsExpr = prefix;
for (const option of Object.keys(options)) {
const theOption: CliOption = {
// Make the default explicit (overridden if the option includes an actual default)
// 'notification-arns' is a special snowflake that should be defaulted to 'undefined', but https://github.com/yargs/yargs/issues/2443
// prevents us from doing so. This should be changed if the issue is resolved.
...(option === 'notification-arns' ? {} : { default: generateDefault(options[option].type) }),
// https://github.com/yargs/yargs/issues/2443 prevents us from supplying 'undefined' as the default
// for array types, because this turns into ['undefined']. The only way to achieve yargs' default is
// to provide no default.
...(options[option].type == 'array' ? {} : { default: undefined }),
...options[option],
};
const optionProps: YargsOption = cloneDeep(theOption);
Expand Down
4 changes: 2 additions & 2 deletions tools/@aws-cdk/user-input-gen/test/user-input-gen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ describe('render', () => {
/**
* plugins to load
*
* @default - []
* @default - undefined
*/
readonly plugin?: Array<string>;
}
Expand Down Expand Up @@ -205,7 +205,7 @@ describe('render', () => {
/**
* Other array
*
* @default - []
* @default - undefined
*/
readonly otherArray?: Array<string>;
}
Expand Down
2 changes: 0 additions & 2 deletions tools/@aws-cdk/user-input-gen/test/yargs-gen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ describe('render', () => {
desc: 'text for two',
})
.option('three', {
default: [],
type: 'array',
alias: 't',
desc: 'text for three',
Expand Down Expand Up @@ -198,7 +197,6 @@ describe('render', () => {
requiresArg: true,
})
.option('other-array', {
default: [],
type: 'array',
desc: 'Other array',
nargs: 1,
Expand Down
Loading