diff --git a/packages/aws-cdk/lib/cli/parse-command-line-arguments.ts b/packages/aws-cdk/lib/cli/parse-command-line-arguments.ts index 0dbec7f7c2859..927025daeaf60 100644 --- a/packages/aws-cdk/lib/cli/parse-command-line-arguments.ts +++ b/packages/aws-cdk/lib/cli/parse-command-line-arguments.ts @@ -24,7 +24,6 @@ export function parseCommandLineArguments(args: Array): any { desc: 'Command-line for a pre-synth build', }) .option('context', { - default: [], type: 'array', alias: 'c', desc: 'Add contextual string parameter (KEY=VALUE)', @@ -32,7 +31,6 @@ export function parseCommandLineArguments(args: Array): any { 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', @@ -151,9 +149,9 @@ export function parseCommandLineArguments(args: Array): 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, }) @@ -237,10 +235,10 @@ export function parseCommandLineArguments(args: Array): 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, }) @@ -250,30 +248,30 @@ export function parseCommandLineArguments(args: Array): 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, }) @@ -356,10 +354,10 @@ export function parseCommandLineArguments(args: Array): 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, }) @@ -382,7 +380,6 @@ export function parseCommandLineArguments(args: Array): 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)', @@ -420,9 +417,9 @@ export function parseCommandLineArguments(args: Array): 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, }) @@ -524,9 +521,9 @@ export function parseCommandLineArguments(args: Array): 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, }), @@ -578,10 +575,10 @@ export function parseCommandLineArguments(args: Array): 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, }) @@ -796,7 +793,6 @@ export function parseCommandLineArguments(args: Array): 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, diff --git a/packages/aws-cdk/lib/cli/user-input.ts b/packages/aws-cdk/lib/cli/user-input.ts index f8973df29828a..04b3b27b27147 100644 --- a/packages/aws-cdk/lib/cli/user-input.ts +++ b/packages/aws-cdk/lib/cli/user-input.ts @@ -143,14 +143,14 @@ export interface GlobalOptions { /** * Add contextual string parameter (KEY=VALUE) * - * @default - [] + * @default - undefined */ readonly context?: Array; /** * Name or path of a node package that extend the CDK features. Can be specified multiple times * - * @default - [] + * @default - undefined */ readonly plugin?: Array; @@ -632,7 +632,7 @@ export interface DeployOptions { * * aliases: t * - * @default - [] + * @default - undefined */ readonly tags?: Array; @@ -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; diff --git a/packages/aws-cdk/test/cli/cli-arguments.test.ts b/packages/aws-cdk/test/cli/cli-arguments.test.ts index 0f62282a2e364..39df1c6c57ef0 100644 --- a/packages/aws-cdk/test/cli/cli-arguments.test.ts +++ b/packages/aws-cdk/test/cli/cli-arguments.test.ts @@ -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, @@ -60,7 +60,7 @@ describe('yargs', () => { progress: undefined, requireApproval: undefined, rollback: false, - tags: [], + tags: undefined, toolkitStackName: undefined, watch: undefined, }, diff --git a/packages/aws-cdk/test/cli/user-config.test.ts b/packages/aws-cdk/test/cli/user-config.test.ts index 7956c6bb1457c..0c5621d944647 100644 --- a/packages/aws-cdk/test/cli/user-config.test.ts +++ b/packages/aws-cdk/test/cli/user-config.test.ts @@ -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'); @@ -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 = 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']); +}); diff --git a/tools/@aws-cdk/user-input-gen/lib/user-input-gen.ts b/tools/@aws-cdk/user-input-gen/lib/user-input-gen.ts index 2401603c2cc0e..084750b7ced41 100644 --- a/tools/@aws-cdk/user-input-gen/lib/user-input-gen.ts +++ b/tools/@aws-cdk/user-input-gen/lib/user-input-gen.ts @@ -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 { @@ -46,7 +46,7 @@ export async function renderUserInputType(config: CliConfig): Promise { 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, }, @@ -81,7 +81,7 @@ export async function renderUserInputType(config: CliConfig): Promise { 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, @@ -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': @@ -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'; } } diff --git a/tools/@aws-cdk/user-input-gen/lib/util.ts b/tools/@aws-cdk/user-input-gen/lib/util.ts index 6d7d050315738..babc21eb61ab0 100644 --- a/tools/@aws-cdk/user-input-gen/lib/util.ts +++ b/tools/@aws-cdk/user-input-gen/lib/util.ts @@ -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: diff --git a/tools/@aws-cdk/user-input-gen/lib/yargs-gen.ts b/tools/@aws-cdk/user-input-gen/lib/yargs-gen.ts index 89fdbe4523cb1..fd237a4cf7c98 100644 --- a/tools/@aws-cdk/user-input-gen/lib/yargs-gen.ts +++ b/tools/@aws-cdk/user-input-gen/lib/yargs-gen.ts @@ -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 @@ -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); diff --git a/tools/@aws-cdk/user-input-gen/test/user-input-gen.test.ts b/tools/@aws-cdk/user-input-gen/test/user-input-gen.test.ts index d02d2b3aef812..bf217b7a7b77e 100644 --- a/tools/@aws-cdk/user-input-gen/test/user-input-gen.test.ts +++ b/tools/@aws-cdk/user-input-gen/test/user-input-gen.test.ts @@ -110,7 +110,7 @@ describe('render', () => { /** * plugins to load * - * @default - [] + * @default - undefined */ readonly plugin?: Array; } @@ -205,7 +205,7 @@ describe('render', () => { /** * Other array * - * @default - [] + * @default - undefined */ readonly otherArray?: Array; } diff --git a/tools/@aws-cdk/user-input-gen/test/yargs-gen.test.ts b/tools/@aws-cdk/user-input-gen/test/yargs-gen.test.ts index 75540361fcc3a..ee0e48070be83 100644 --- a/tools/@aws-cdk/user-input-gen/test/yargs-gen.test.ts +++ b/tools/@aws-cdk/user-input-gen/test/yargs-gen.test.ts @@ -50,7 +50,6 @@ describe('render', () => { desc: 'text for two', }) .option('three', { - default: [], type: 'array', alias: 't', desc: 'text for three', @@ -198,7 +197,6 @@ describe('render', () => { requiresArg: true, }) .option('other-array', { - default: [], type: 'array', desc: 'Other array', nargs: 1,