From 277b624c670669a91b18e69336f7355e877caa48 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 01:07:03 +0900 Subject: [PATCH 01/25] destroy --- packages/aws-cdk/lib/cli/cdk-toolkit.ts | 55 ++++++++++++++++ packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 62 +++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index b22eed419..ef5e1144e 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -7,6 +7,7 @@ import { PermissionChangeType, Toolkit, ToolkitError } from '@aws-cdk/toolkit-li import * as chalk from 'chalk'; import * as chokidar from 'chokidar'; import * as fs from 'fs-extra'; +import { minimatch } from 'minimatch'; import * as uuid from 'uuid'; import { CliIoHost } from './io-host'; import type { Configuration } from './user-configuration'; @@ -68,6 +69,7 @@ import { cdkCliErrorName } from './telemetry/error'; import { CLI_PRIVATE_SPAN } from './telemetry/messages'; import type { ErrorDetails } from './telemetry/schema'; import { FlagOperations } from '../commands/flags/operations'; +import { warning } from '../legacy'; // Must use a require() otherwise esbuild complains about calling a namespace // eslint-disable-next-line @typescript-eslint/no-require-imports,@typescript-eslint/consistent-type-imports @@ -972,6 +974,16 @@ export class CdkToolkit { // The stacks will have been ordered for deployment, so reverse them for deletion. const stacks = (await this.selectStacksForDestroy(options.selector, options.exclusively)).reversed(); + await this.suggestStacks({ + selector: options.selector, + stacks, + exclusively: options.exclusively, + }); + if (stacks.stackArtifacts.length === 0) { + await this.ioHost.asIoHelper().defaults.warn(`No stacks match the name(s): ${chalk.red(options.selector.patterns.join(', '))}`); + return; + } + if (!options.force) { const motivation = 'Destroying stacks is an irreversible action'; const question = `Are you sure you want to delete: ${chalk.blue(stacks.stackArtifacts.map((s) => s.hierarchicalId).join(', '))}`; @@ -1373,6 +1385,49 @@ export class CdkToolkit { return stacks; } + private async suggestStacks(props: { + selector: StackSelector; + stacks: StackCollection; + exclusively?: boolean; + }) { + const assembly = await this.assembly(); + const selectorWithoutPatterns: StackSelector = { + ...props.selector, + allTopLevel: true, + patterns: [], + }; + const stacksWithoutPatterns = await assembly.selectStacks(selectorWithoutPatterns, { + extend: props.exclusively ? ExtendedStackSelection.None : ExtendedStackSelection.Downstream, + defaultBehavior: DefaultSelection.OnlySingle, + }); + + const patterns = props.selector.patterns.map(pattern => { + const notExist = !props.stacks.stackArtifacts.find(stack => + minimatch(stack.hierarchicalId, pattern), + ); + + const closelyMatched = notExist ? stacksWithoutPatterns.stackArtifacts.map(stack => { + if (minimatch(stack.hierarchicalId.toLowerCase(), pattern.toLowerCase())) { + return stack.hierarchicalId; + } + return; + }).filter((stack): stack is string => stack !== undefined) : []; + + return { + pattern, + notExist, + closelyMatched, + }; + }); + + for (const pattern of patterns) { + if (pattern.notExist) { + const closelyMatched = pattern.closelyMatched.length > 0 ? ` Do you mean ${chalk.blue(pattern.closelyMatched.join(', '))}?` : ''; + await this.ioHost.asIoHelper().defaults.warn(`${chalk.red(pattern.pattern)} does not exist.${closelyMatched}`); + } + }; + } + /** * Validate the stacks for errors and warnings according to the CLI's current settings */ diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index 45c611918..95613c2bb 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1089,6 +1089,68 @@ describe('destroy', () => { }); }).resolves; }); + + test('does not throw and warns if there are only non-existent stacks', async () => { + const toolkit = defaultToolkitSetup(); + + await toolkit.destroy({ + selector: { patterns: ['Test-Stack-X', 'Test-Stack-Y'] }, + exclusively: true, + force: true, + fromDeploy: true, + }); + + expect(flatten(notifySpy.mock.calls)).toEqual([ + expectIoMsg(expect.stringMatching(/\x1B\[31mTest-Stack-X\x1B\[39m does not exist./), 'warn'), + expectIoMsg(expect.stringMatching(/\x1B\[31mTest-Stack-Y\x1B\[39m does not exist./), 'warn'), + expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): \x1B\[31mTest-Stack-X, Test-Stack-Y\x1B\[39m/), 'warn'), + ]); + }); + + test('does not throw and warns if there is a non-existent stack and the other exists', async () => { + const toolkit = defaultToolkitSetup(); + + await toolkit.destroy({ + selector: { patterns: ['Test-Stack-X', 'Test-Stack-B'] }, + exclusively: true, + force: true, + fromDeploy: true, + }); + + expect(flatten(notifySpy.mock.calls)).toEqual( + expect.arrayContaining([ + expectIoMsg(expect.stringMatching(/\x1B\[31mTest-Stack-X\x1B\[39m does not exist./), 'warn'), + ]), + ); + expect(flatten(notifySpy.mock.calls)).not.toEqual( + expect.arrayContaining([ + expectIoMsg(expect.stringMatching(/\x1B\[31mTest-Stack-B\x1B\[39m does not exist./), 'warn'), + ]), + ); + expect(flatten(notifySpy.mock.calls)).not.toEqual( + expect.arrayContaining([ + expectIoMsg(expect.stringMatching(/No stacks match the name\(s\)/), 'warn'), + ]), + ); + }); + + test('does not throw and suggests valid names if there is a non-existent but closely matching stack', async () => { + const toolkit = defaultToolkitSetup(); + + await toolkit.destroy({ + selector: { patterns: ['test-stack-b'] }, + exclusively: true, + force: true, + fromDeploy: true, + }); + + expect(flatten(notifySpy.mock.calls)).toEqual( + expect.arrayContaining([ + expectIoMsg(expect.stringMatching(/\x1B\[31mtest-stack-b\x1B\[39m does not exist. Do you mean \x1B\[34mTest-Stack-B\x1B\[39m?/), 'warn'), + expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): \x1B\[31mtest-stack-b\x1B\[39m/), 'warn'), + ]), + ); + }); }); describe('watch', () => { From 15cdc384b02f35928738061b99f0fb4fb4516d08 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 01:37:04 +0900 Subject: [PATCH 02/25] color codes --- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index 95613c2bb..c88a1bbaf 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1101,9 +1101,12 @@ describe('destroy', () => { }); expect(flatten(notifySpy.mock.calls)).toEqual([ - expectIoMsg(expect.stringMatching(/\x1B\[31mTest-Stack-X\x1B\[39m does not exist./), 'warn'), - expectIoMsg(expect.stringMatching(/\x1B\[31mTest-Stack-Y\x1B\[39m does not exist./), 'warn'), - expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): \x1B\[31mTest-Stack-X, Test-Stack-Y\x1B\[39m/), 'warn'), + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Test-Stack-X(\x1B\[39m)? does not exist./), 'warn'), + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Test-Stack-Y(\x1B\[39m)? does not exist./), 'warn'), + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?Test-Stack-X, Test-Stack-Y(\x1B\[39m)?/), 'warn'), ]); }); @@ -1119,12 +1122,14 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - expectIoMsg(expect.stringMatching(/\x1B\[31mTest-Stack-X\x1B\[39m does not exist./), 'warn'), + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Test-Stack-X(\x1B\[39m)? does not exist./), 'warn'), ]), ); expect(flatten(notifySpy.mock.calls)).not.toEqual( expect.arrayContaining([ - expectIoMsg(expect.stringMatching(/\x1B\[31mTest-Stack-B\x1B\[39m does not exist./), 'warn'), + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Test-Stack-B(\x1B\[39m)? does not exist./), 'warn'), ]), ); expect(flatten(notifySpy.mock.calls)).not.toEqual( @@ -1146,8 +1151,10 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - expectIoMsg(expect.stringMatching(/\x1B\[31mtest-stack-b\x1B\[39m does not exist. Do you mean \x1B\[34mTest-Stack-B\x1B\[39m?/), 'warn'), - expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): \x1B\[31mtest-stack-b\x1B\[39m/), 'warn'), + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/(\x1B\[31m)?test-stack-b(\x1B\[39m)? does not exist. Do you mean (\x1B\[34m)?Test-Stack-B(\x1B\[39m)?/), 'warn'), + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?test-stack-b(\x1B\[39m)?/), 'warn'), ]), ); }); From d2e7afd45f7f9198a5621ae5a79f99c676c161c0 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 01:38:12 +0900 Subject: [PATCH 03/25] fix the previous bug --- packages/aws-cdk/lib/cli/cdk-toolkit.ts | 4 +- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index ef5e1144e..e2002167f 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -1392,13 +1392,11 @@ export class CdkToolkit { }) { const assembly = await this.assembly(); const selectorWithoutPatterns: StackSelector = { - ...props.selector, - allTopLevel: true, patterns: [], }; const stacksWithoutPatterns = await assembly.selectStacks(selectorWithoutPatterns, { extend: props.exclusively ? ExtendedStackSelection.None : ExtendedStackSelection.Downstream, - defaultBehavior: DefaultSelection.OnlySingle, + defaultBehavior: DefaultSelection.AllStacks, }); const patterns = props.selector.patterns.map(pattern => { diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index c88a1bbaf..07f35b1d5 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1158,6 +1158,44 @@ describe('destroy', () => { ]), ); }); + + test('does not throw when there are only nested stage stacks and no top-level stacks', async () => { + // Create a cloud executable with only nested stacks (no top-level stacks) + const nestedOnlyExecutable = await MockCloudExecutable.create({ + stacks: [], + nestedAssemblies: [ + { + stacks: [MockStack.MOCK_STACK_C], + }, + ], + }); + + const toolkit = new CdkToolkit({ + ioHost, + cloudExecutable: nestedOnlyExecutable, + configuration: nestedOnlyExecutable.configuration, + sdkProvider: nestedOnlyExecutable.sdkProvider, + deployments: new FakeCloudFormation({ + 'Test-Stack-C': { Baz: 'Zinga!' }, + }), + }); + + await toolkit.destroy({ + selector: { patterns: ['Test-Stack-X'] }, + exclusively: true, + force: true, + fromDeploy: true, + }); + + expect(flatten(notifySpy.mock.calls)).toEqual( + expect.arrayContaining([ + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Test-Stack-X(\x1B\[39m)? does not exist./), 'warn'), + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?Test-Stack-X(\x1B\[39m)?/), 'warn'), + ]), + ); + }); }); describe('watch', () => { From 870ddfd22cb28d24992a0df7976da484a9e66ed7 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 02:29:59 +0900 Subject: [PATCH 04/25] add tests comment --- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index 07f35b1d5..e7aaca58c 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1090,6 +1090,80 @@ describe('destroy', () => { }).resolves; }); + test('can destroy nested stack with wildcard pattern', async () => { + const toolkit = defaultToolkitSetup(); + + expect(() => { + return toolkit.destroy({ + selector: { patterns: ['Test*/*'] }, + exclusively: true, + force: true, + fromDeploy: true, + }); + }).resolves; + }); + + test('can destroy nested stack in nested-only configuration', async () => { + // only nested stacks (no top-level stacks) + const nestedOnlyExecutable = await MockCloudExecutable.create({ + stacks: [], + nestedAssemblies: [ + { + stacks: [MockStack.MOCK_STACK_C], + }, + ], + }); + + const toolkit = new CdkToolkit({ + ioHost, + cloudExecutable: nestedOnlyExecutable, + configuration: nestedOnlyExecutable.configuration, + sdkProvider: nestedOnlyExecutable.sdkProvider, + deployments: new FakeCloudFormation({ + 'Test-Stack-C': { Baz: 'Zinga!' }, + }), + }); + + expect(() => { + return toolkit.destroy({ + selector: { patterns: ['Test-Stack-A/Test-Stack-C'] }, + exclusively: true, + force: true, + fromDeploy: true, + }); + }).resolves; + }); + + test('can destroy with --all flag in nested-only configuration', async () => { + const nestedOnlyExecutable = await MockCloudExecutable.create({ + stacks: [], + nestedAssemblies: [{ + stacks: [ + MockStack.MOCK_STACK_C, + ], + }], + }); + + const toolkit = new CdkToolkit({ + ioHost, + cloudExecutable: nestedOnlyExecutable, + configuration: nestedOnlyExecutable.configuration, + sdkProvider: nestedOnlyExecutable.sdkProvider, + deployments: new FakeCloudFormation({ + 'Test-Stack-C': { Baz: 'Zinga!' }, + }), + }); + + expect(() => { + return toolkit.destroy({ + selector: { patterns: [] }, // --all flag uses empty patterns + exclusively: true, + force: true, + fromDeploy: true, + }); + }).resolves; + }); + test('does not throw and warns if there are only non-existent stacks', async () => { const toolkit = defaultToolkitSetup(); @@ -1159,6 +1233,47 @@ describe('destroy', () => { ); }); + test('does not throw and suggests nested stack names if there is a non-existent but closely matching nested stack', async () => { + const toolkit = defaultToolkitSetup(); + + await toolkit.destroy({ + selector: { patterns: ['test-stack-a/test-stack-c'] }, + exclusively: true, + force: true, + fromDeploy: true, + }); + + expect(flatten(notifySpy.mock.calls)).toEqual( + expect.arrayContaining([ + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/(\x1B\[31m)?test-stack-a\/test-stack-c(\x1B\[39m)? does not exist. Do you mean (\x1B\[34m)?Test-Stack-A\/Test-Stack-C(\x1B\[39m)?/), 'warn'), + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?test-stack-a\/test-stack-c(\x1B\[39m)?/), 'warn'), + ]), + ); + }); + + test('does not suggest nested stack when pattern lacks hierarchy', async () => { + const toolkit = defaultToolkitSetup(); + + await toolkit.destroy({ + selector: { patterns: ['test-stack-c'] }, + exclusively: true, + force: true, + fromDeploy: true, + }); + + expect(flatten(notifySpy.mock.calls)).toEqual( + expect.arrayContaining([ + // Color codes are optional because chalk depends on TTY/TERM + // Should NOT suggest Test-Stack-A/Test-Stack-C because pattern lacks hierarchy + expectIoMsg(expect.stringMatching(/(\x1B\[31m)?test-stack-c(\x1B\[39m)? does not exist\.$/), 'warn'), + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?test-stack-c(\x1B\[39m)?/), 'warn'), + ]), + ); + }); + test('does not throw when there are only nested stage stacks and no top-level stacks', async () => { // Create a cloud executable with only nested stacks (no top-level stacks) const nestedOnlyExecutable = await MockCloudExecutable.create({ From 06bed000bb2434c0830917609780efb430a40e99 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 02:40:31 +0900 Subject: [PATCH 05/25] skip --all --- packages/aws-cdk/lib/cli/cdk-toolkit.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index e2002167f..5b62c1876 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -974,11 +974,14 @@ export class CdkToolkit { // The stacks will have been ordered for deployment, so reverse them for deletion. const stacks = (await this.selectStacksForDestroy(options.selector, options.exclusively)).reversed(); - await this.suggestStacks({ - selector: options.selector, - stacks, - exclusively: options.exclusively, - }); + // Only suggest stacks if patterns are provided (skip for --all flag) + if (options.selector.patterns.length > 0) { + await this.suggestStacks({ + selector: options.selector, + stacks, + exclusively: options.exclusively, + }); + } if (stacks.stackArtifacts.length === 0) { await this.ioHost.asIoHelper().defaults.warn(`No stacks match the name(s): ${chalk.red(options.selector.patterns.join(', '))}`); return; From 35f103b77d624d3b6ab42cdb0aaf431715f264b1 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 02:56:31 +0900 Subject: [PATCH 06/25] add tests --- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index e7aaca58c..a62cae205 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1253,7 +1253,7 @@ describe('destroy', () => { ); }); - test('does not suggest nested stack when pattern lacks hierarchy', async () => { + test('does not throw and does not suggest nested stack when pattern lacks hierarchy', async () => { const toolkit = defaultToolkitSetup(); await toolkit.destroy({ @@ -1274,6 +1274,44 @@ describe('destroy', () => { ); }); + test('does not throw and warns when wildcard pattern does not match any stacks', async () => { + const toolkit = defaultToolkitSetup(); + + await toolkit.destroy({ + selector: { patterns: ['Foo*/*'] }, + exclusively: true, + force: true, + fromDeploy: true, + }); + + expect(flatten(notifySpy.mock.calls)).toEqual( + expect.arrayContaining([ + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Foo\*\/\*(\x1B\[39m)? does not exist\.$/), 'warn'), + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?Foo\*\/\*(\x1B\[39m)?/), 'warn'), + ]), + ); + }); + + test('does not throw and suggests stack with wildcard pattern when only case differs', async () => { + const toolkit = defaultToolkitSetup(); + + await toolkit.destroy({ + selector: { patterns: ['test*/*'] }, + exclusively: true, + force: true, + fromDeploy: true, + }); + + expect(flatten(notifySpy.mock.calls)).toEqual( + expect.arrayContaining([ + // Color codes are optional because chalk depends on TTY/TERM + expectIoMsg(expect.stringMatching(/(\x1B\[31m)?test\*\/\*(\x1B\[39m)? does not exist\. Do you mean (\x1B\[34m)?Test-Stack-A\/Test-Stack-C(\x1B\[39m)?/), 'warn'), + ]), + ); + }); + test('does not throw when there are only nested stage stacks and no top-level stacks', async () => { // Create a cloud executable with only nested stacks (no top-level stacks) const nestedOnlyExecutable = await MockCloudExecutable.create({ From 717e02a9a840e4c4047b4d5ec29844933c5831cd Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 03:02:24 +0900 Subject: [PATCH 07/25] add tests --- packages/aws-cdk/lib/cli/cdk-toolkit.ts | 1 + packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index 5b62c1876..5e3531330 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -982,6 +982,7 @@ export class CdkToolkit { exclusively: options.exclusively, }); } + if (stacks.stackArtifacts.length === 0) { await this.ioHost.asIoHelper().defaults.warn(`No stacks match the name(s): ${chalk.red(options.selector.patterns.join(', '))}`); return; diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index a62cae205..08e44bbbe 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1134,6 +1134,19 @@ describe('destroy', () => { }).resolves; }); + test('can destroy with --all flag', async () => { + const toolkit = defaultToolkitSetup(); + + expect(() => { + return toolkit.destroy({ + selector: { patterns: [] }, // --all flag uses empty patterns + exclusively: true, + force: true, + fromDeploy: true, + }); + }).resolves; + }); + test('can destroy with --all flag in nested-only configuration', async () => { const nestedOnlyExecutable = await MockCloudExecutable.create({ stacks: [], @@ -1312,8 +1325,8 @@ describe('destroy', () => { ); }); - test('does not throw when there are only nested stage stacks and no top-level stacks', async () => { - // Create a cloud executable with only nested stacks (no top-level stacks) + test('does not throw and warns when destroying non-existent stack in nested-only configuration', async () => { + // only nested stacks (no top-level stacks) const nestedOnlyExecutable = await MockCloudExecutable.create({ stacks: [], nestedAssemblies: [ From e3329b7797d0f85ea5334c3436f6e61a26bb13b8 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 03:13:53 +0900 Subject: [PATCH 08/25] integ --- ...cdk-destroy-nonexistent-stack.integtest.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts new file mode 100644 index 000000000..76d28268f --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts @@ -0,0 +1,23 @@ +import { integTest, withDefaultFixture } from '../../../lib'; + +integTest('cdk destroy does not fail even if the stacks do not exist', withDefaultFixture(async (fixture) => { + const nonExistingStackName1 = 'non-existing-stack-1'; + const nonExistingStackName2 = 'non-existing-stack-2'; + + await expect(fixture.cdkDestroy([nonExistingStackName1, nonExistingStackName2])).resolves.not.toThrow(); +})); + +integTest('cdk destroy with no force option exits without prompt if the stacks do not exist', withDefaultFixture(async (fixture) => { + const nonExistingStackName1 = 'non-existing-stack-1'; + const nonExistingStackName2 = 'non-existing-stack-2'; + + await expect(fixture.cdk(['destroy', ...fixture.fullStackName([nonExistingStackName1, nonExistingStackName2])])).resolves.not.toThrow(); +})); + +integTest('cdk destroy does not fail with wildcard pattern that matches no stacks', withDefaultFixture(async (fixture) => { + await expect(fixture.cdkDestroy('NonExistent*')).resolves.not.toThrow(); +})); + +integTest('cdk destroy does not fail with --all when no stacks exist', withDefaultFixture(async (fixture) => { + await expect(fixture.cdkDestroy('--all')).resolves.not.toThrow(); +})); From f27ce6c263e329b1667db60d15cd61f6fc9f48fc Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:56:47 +0900 Subject: [PATCH 09/25] refactor tests test titles test tiltes modify tests allTopLevel in tests modify tests rm test add test title --- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 181 +++++++----------- 1 file changed, 69 insertions(+), 112 deletions(-) diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index 08e44bbbe..7c2364bb6 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -63,6 +63,7 @@ import type { DeploymentMethod } from '@aws-cdk/toolkit-lib'; import type { DestroyStackResult } from '@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack'; import { DescribeStacksCommand, GetTemplateCommand, StackStatus } from '@aws-sdk/client-cloudformation'; import { GetParameterCommand } from '@aws-sdk/client-ssm'; +import * as chalk from 'chalk'; import * as fs from 'fs-extra'; import type { Template, SdkProvider } from '../../lib/api'; import { Bootstrapper, type BootstrapSource } from '../../lib/api/bootstrap'; @@ -1077,34 +1078,41 @@ describe('deploy', () => { }); describe('destroy', () => { - test('destroy correct stack', async () => { + test('destroys correct stack', async () => { const toolkit = defaultToolkitSetup(); - expect(() => { - return toolkit.destroy({ - selector: { patterns: ['Test-Stack-A/Test-Stack-C'] }, - exclusively: true, - force: true, - fromDeploy: true, - }); - }).resolves; + await expect(toolkit.destroy({ + selector: { patterns: ['Test-Stack-A/Test-Stack-C'] }, + exclusively: true, + force: true, + fromDeploy: true, + })).resolves.not.toThrow(); }); - test('can destroy nested stack with wildcard pattern', async () => { + test('destroys with --all flag', async () => { const toolkit = defaultToolkitSetup(); - expect(() => { - return toolkit.destroy({ - selector: { patterns: ['Test*/*'] }, - exclusively: true, - force: true, - fromDeploy: true, - }); - }).resolves; + await expect(toolkit.destroy({ + selector: { allTopLevel: true, patterns: [] }, // --all flag sets allTopLevel: true + exclusively: true, + force: true, + fromDeploy: true, + })).resolves.not.toThrow(); }); - test('can destroy nested stack in nested-only configuration', async () => { - // only nested stacks (no top-level stacks) + test('destroys stack within stage with wildcard pattern', async () => { + const toolkit = defaultToolkitSetup(); + + await expect(toolkit.destroy({ + selector: { patterns: ['Test*/*'] }, + exclusively: true, + force: true, + fromDeploy: true, + })).resolves.not.toThrow(); + }); + + test('destroys stack within stage in stage-only configuration', async () => { + // only stacks within stages (no top-level stacks) const nestedOnlyExecutable = await MockCloudExecutable.create({ stacks: [], nestedAssemblies: [ @@ -1124,37 +1132,23 @@ describe('destroy', () => { }), }); - expect(() => { - return toolkit.destroy({ - selector: { patterns: ['Test-Stack-A/Test-Stack-C'] }, - exclusively: true, - force: true, - fromDeploy: true, - }); - }).resolves; - }); - - test('can destroy with --all flag', async () => { - const toolkit = defaultToolkitSetup(); - - expect(() => { - return toolkit.destroy({ - selector: { patterns: [] }, // --all flag uses empty patterns - exclusively: true, - force: true, - fromDeploy: true, - }); - }).resolves; + await expect(toolkit.destroy({ + selector: { patterns: ['Test-Stack-A/Test-Stack-C'] }, + exclusively: true, + force: true, + fromDeploy: true, + })).resolves.not.toThrow(); }); - test('can destroy with --all flag in nested-only configuration', async () => { + test('destroys stack within stage with wildcard pattern in stage-only configuration', async () => { + // only stacks within stages (no top-level stacks) const nestedOnlyExecutable = await MockCloudExecutable.create({ stacks: [], - nestedAssemblies: [{ - stacks: [ - MockStack.MOCK_STACK_C, - ], - }], + nestedAssemblies: [ + { + stacks: [MockStack.MOCK_STACK_C], + }, + ], }); const toolkit = new CdkToolkit({ @@ -1167,17 +1161,15 @@ describe('destroy', () => { }), }); - expect(() => { - return toolkit.destroy({ - selector: { patterns: [] }, // --all flag uses empty patterns - exclusively: true, - force: true, - fromDeploy: true, - }); - }).resolves; + await expect(toolkit.destroy({ + selector: { patterns: ['Test*/*'] }, + exclusively: true, + force: true, + fromDeploy: true, + })).resolves.not.toThrow(); }); - test('does not throw and warns if there are only non-existent stacks', async () => { + test('warns if there are only non-existent stacks', async () => { const toolkit = defaultToolkitSetup(); await toolkit.destroy({ @@ -1188,16 +1180,13 @@ describe('destroy', () => { }); expect(flatten(notifySpy.mock.calls)).toEqual([ - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Test-Stack-X(\x1B\[39m)? does not exist./), 'warn'), - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Test-Stack-Y(\x1B\[39m)? does not exist./), 'warn'), - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?Test-Stack-X, Test-Stack-Y(\x1B\[39m)?/), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('Test-Stack-X')} does not exist.`), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('Test-Stack-Y')} does not exist.`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('Test-Stack-X, Test-Stack-Y')}`), 'warn'), ]); }); - test('does not throw and warns if there is a non-existent stack and the other exists', async () => { + test('warns if there is a non-existent stack and the other exists', async () => { const toolkit = defaultToolkitSetup(); await toolkit.destroy({ @@ -1209,14 +1198,12 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Test-Stack-X(\x1B\[39m)? does not exist./), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('Test-Stack-X')} does not exist.`), 'warn'), ]), ); expect(flatten(notifySpy.mock.calls)).not.toEqual( expect.arrayContaining([ - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Test-Stack-B(\x1B\[39m)? does not exist./), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('Test-Stack-B')} does not exist.`), 'warn'), ]), ); expect(flatten(notifySpy.mock.calls)).not.toEqual( @@ -1226,7 +1213,7 @@ describe('destroy', () => { ); }); - test('does not throw and suggests valid names if there is a non-existent but closely matching stack', async () => { + test('suggests valid names if there is a non-existent but closely matching stack', async () => { const toolkit = defaultToolkitSetup(); await toolkit.destroy({ @@ -1238,15 +1225,13 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/(\x1B\[31m)?test-stack-b(\x1B\[39m)? does not exist. Do you mean (\x1B\[34m)?Test-Stack-B(\x1B\[39m)?/), 'warn'), - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?test-stack-b(\x1B\[39m)?/), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('test-stack-b')} does not exist. Do you mean ${chalk.blue('Test-Stack-B')}?`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('test-stack-b')}`), 'warn'), ]), ); }); - test('does not throw and suggests nested stack names if there is a non-existent but closely matching nested stack', async () => { + test('suggests stack names within stages if there is a non-existent but closely matching stack', async () => { const toolkit = defaultToolkitSetup(); await toolkit.destroy({ @@ -1258,36 +1243,13 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/(\x1B\[31m)?test-stack-a\/test-stack-c(\x1B\[39m)? does not exist. Do you mean (\x1B\[34m)?Test-Stack-A\/Test-Stack-C(\x1B\[39m)?/), 'warn'), - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?test-stack-a\/test-stack-c(\x1B\[39m)?/), 'warn'), - ]), - ); - }); - - test('does not throw and does not suggest nested stack when pattern lacks hierarchy', async () => { - const toolkit = defaultToolkitSetup(); - - await toolkit.destroy({ - selector: { patterns: ['test-stack-c'] }, - exclusively: true, - force: true, - fromDeploy: true, - }); - - expect(flatten(notifySpy.mock.calls)).toEqual( - expect.arrayContaining([ - // Color codes are optional because chalk depends on TTY/TERM - // Should NOT suggest Test-Stack-A/Test-Stack-C because pattern lacks hierarchy - expectIoMsg(expect.stringMatching(/(\x1B\[31m)?test-stack-c(\x1B\[39m)? does not exist\.$/), 'warn'), - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?test-stack-c(\x1B\[39m)?/), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('test-stack-a/test-stack-c')} does not exist. Do you mean ${chalk.blue('Test-Stack-A/Test-Stack-C')}?`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('test-stack-a/test-stack-c')}`), 'warn'), ]), ); }); - test('does not throw and warns when wildcard pattern does not match any stacks', async () => { + test('warns when wildcard pattern does not match any stacks', async () => { const toolkit = defaultToolkitSetup(); await toolkit.destroy({ @@ -1299,15 +1261,13 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Foo\*\/\*(\x1B\[39m)? does not exist\.$/), 'warn'), - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?Foo\*\/\*(\x1B\[39m)?/), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('Foo*/*')} does not exist.`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('Foo*/*')}`), 'warn'), ]), ); }); - test('does not throw and suggests stack with wildcard pattern when only case differs', async () => { + test('suggests stack with wildcard pattern when only case differs', async () => { const toolkit = defaultToolkitSetup(); await toolkit.destroy({ @@ -1319,14 +1279,13 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/(\x1B\[31m)?test\*\/\*(\x1B\[39m)? does not exist\. Do you mean (\x1B\[34m)?Test-Stack-A\/Test-Stack-C(\x1B\[39m)?/), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('test*/*')} does not exist. Do you mean ${chalk.blue('Test-Stack-A/Test-Stack-C')}?`), 'warn'), ]), ); }); - test('does not throw and warns when destroying non-existent stack in nested-only configuration', async () => { - // only nested stacks (no top-level stacks) + test('warns when destroying non-existent stack in stage-only configuration', async () => { + // only stacks within stages (no top-level stacks) const nestedOnlyExecutable = await MockCloudExecutable.create({ stacks: [], nestedAssemblies: [ @@ -1355,10 +1314,8 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/(\x1B\[31m)?Test-Stack-X(\x1B\[39m)? does not exist./), 'warn'), - // Color codes are optional because chalk depends on TTY/TERM - expectIoMsg(expect.stringMatching(/No stacks match the name\(s\): (\x1B\[31m)?Test-Stack-X(\x1B\[39m)?/), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('Test-Stack-X')} does not exist.`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('Test-Stack-X')}`), 'warn'), ]), ); }); From 5add1b5efc1292380bb62321ecea7ea9e157bcc9 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 18:45:22 +0900 Subject: [PATCH 10/25] modify --- packages/aws-cdk/lib/cli/cdk-toolkit.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index 5e3531330..697735ce1 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -974,8 +974,8 @@ export class CdkToolkit { // The stacks will have been ordered for deployment, so reverse them for deletion. const stacks = (await this.selectStacksForDestroy(options.selector, options.exclusively)).reversed(); - // Only suggest stacks if patterns are provided (skip for --all flag) - if (options.selector.patterns.length > 0) { + // Only suggest stacks when not using --all flag + if (!options.selector.allTopLevel) { await this.suggestStacks({ selector: options.selector, stacks, From 8497125f59a012d0ab3ad24ca76aa3c602c4b4fe Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 18:52:33 +0900 Subject: [PATCH 11/25] refactor --- packages/aws-cdk/lib/cli/cdk-toolkit.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index 697735ce1..51f90e08f 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -974,14 +974,11 @@ export class CdkToolkit { // The stacks will have been ordered for deployment, so reverse them for deletion. const stacks = (await this.selectStacksForDestroy(options.selector, options.exclusively)).reversed(); - // Only suggest stacks when not using --all flag - if (!options.selector.allTopLevel) { - await this.suggestStacks({ - selector: options.selector, - stacks, - exclusively: options.exclusively, - }); - } + await this.suggestStacks({ + selector: options.selector, + stacks, + exclusively: options.exclusively, + }); if (stacks.stackArtifacts.length === 0) { await this.ioHost.asIoHelper().defaults.warn(`No stacks match the name(s): ${chalk.red(options.selector.patterns.join(', '))}`); @@ -1394,6 +1391,11 @@ export class CdkToolkit { stacks: StackCollection; exclusively?: boolean; }) { + // Skip suggestion for --all flag + if (props.selector.allTopLevel) { + return; + } + const assembly = await this.assembly(); const selectorWithoutPatterns: StackSelector = { patterns: [], From 2a4322328f1bd05a9979645cc5cd52aab858cf15 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:05:22 +0900 Subject: [PATCH 12/25] modify --- packages/aws-cdk/lib/cli/cdk-toolkit.ts | 3 +-- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index 51f90e08f..1b96e9050 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -1391,8 +1391,7 @@ export class CdkToolkit { stacks: StackCollection; exclusively?: boolean; }) { - // Skip suggestion for --all flag - if (props.selector.allTopLevel) { + if (props.selector.patterns.length === 0) { return; } diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index 7c2364bb6..1d7a9aeea 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1089,6 +1089,29 @@ describe('destroy', () => { })).resolves.not.toThrow(); }); + test('destroys single stack with automatic selection', async () => { + const singleStackExecutable = await MockCloudExecutable.create({ + stacks: [MockStack.MOCK_STACK_B], + }); + + const toolkit = new CdkToolkit({ + ioHost, + cloudExecutable: singleStackExecutable, + configuration: singleStackExecutable.configuration, + sdkProvider: singleStackExecutable.sdkProvider, + deployments: new FakeCloudFormation({ + 'Test-Stack-B': { Foo: 'Bar' }, + }), + }); + + await expect(toolkit.destroy({ + selector: { patterns: [] }, + exclusively: true, + force: true, + fromDeploy: true, + })).resolves.not.toThrow(); + }); + test('destroys with --all flag', async () => { const toolkit = defaultToolkitSetup(); From 5a9e8730867f5166553667d15aafa2741971246d Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:08:23 +0900 Subject: [PATCH 13/25] tweak --- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index 1d7a9aeea..acc122ed0 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1089,6 +1089,28 @@ describe('destroy', () => { })).resolves.not.toThrow(); }); + test('destroys with --all flag', async () => { + const toolkit = defaultToolkitSetup(); + + await expect(toolkit.destroy({ + selector: { allTopLevel: true, patterns: [] }, // --all flag sets allTopLevel: true + exclusively: true, + force: true, + fromDeploy: true, + })).resolves.not.toThrow(); + }); + + test('destroys stack within stage with wildcard pattern', async () => { + const toolkit = defaultToolkitSetup(); + + await expect(toolkit.destroy({ + selector: { patterns: ['Test*/*'] }, + exclusively: true, + force: true, + fromDeploy: true, + })).resolves.not.toThrow(); + }); + test('destroys single stack with automatic selection', async () => { const singleStackExecutable = await MockCloudExecutable.create({ stacks: [MockStack.MOCK_STACK_B], @@ -1112,22 +1134,23 @@ describe('destroy', () => { })).resolves.not.toThrow(); }); - test('destroys with --all flag', async () => { - const toolkit = defaultToolkitSetup(); - - await expect(toolkit.destroy({ - selector: { allTopLevel: true, patterns: [] }, // --all flag sets allTopLevel: true - exclusively: true, - force: true, - fromDeploy: true, - })).resolves.not.toThrow(); - }); + test('destroys single stack with pattern', async () => { + const singleStackExecutable = await MockCloudExecutable.create({ + stacks: [MockStack.MOCK_STACK_B], + }); - test('destroys stack within stage with wildcard pattern', async () => { - const toolkit = defaultToolkitSetup(); + const toolkit = new CdkToolkit({ + ioHost, + cloudExecutable: singleStackExecutable, + configuration: singleStackExecutable.configuration, + sdkProvider: singleStackExecutable.sdkProvider, + deployments: new FakeCloudFormation({ + 'Test-Stack-B': { Foo: 'Bar' }, + }), + }); await expect(toolkit.destroy({ - selector: { patterns: ['Test*/*'] }, + selector: { patterns: ['Test-Stack-B'] }, exclusively: true, force: true, fromDeploy: true, From b4e39392545feaa33c0482f0e8026ad43adffc9e Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:11:23 +0900 Subject: [PATCH 14/25] title --- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index acc122ed0..45c5e6e56 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1111,7 +1111,7 @@ describe('destroy', () => { })).resolves.not.toThrow(); }); - test('destroys single stack with automatic selection', async () => { + test('destroys stack in single-stack configuration', async () => { const singleStackExecutable = await MockCloudExecutable.create({ stacks: [MockStack.MOCK_STACK_B], }); @@ -1134,7 +1134,7 @@ describe('destroy', () => { })).resolves.not.toThrow(); }); - test('destroys single stack with pattern', async () => { + test('destroys stack with pattern in single-stack configuration', async () => { const singleStackExecutable = await MockCloudExecutable.create({ stacks: [MockStack.MOCK_STACK_B], }); From a48e885b4e3ea21100380bdde5218d1df523aa62 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:16:10 +0900 Subject: [PATCH 15/25] refactor tests to separate methods --- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 124 ++++++------------ 1 file changed, 41 insertions(+), 83 deletions(-) diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index 45c5e6e56..cbc3db0b9 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -151,6 +151,42 @@ function defaultToolkitSetup() { }); } +async function singleStackToolkitSetup() { + const singleStackExecutable = await MockCloudExecutable.create({ + stacks: [MockStack.MOCK_STACK_B], + }); + + return new CdkToolkit({ + ioHost, + cloudExecutable: singleStackExecutable, + configuration: singleStackExecutable.configuration, + sdkProvider: singleStackExecutable.sdkProvider, + deployments: new FakeCloudFormation({ + 'Test-Stack-B': { Foo: 'Bar' }, + }), + }); +} + +// only stacks within stages (no top-level stacks) +async function stageOnlyToolkitSetup() { + const stageOnlyExecutable = await MockCloudExecutable.create({ + stacks: [], + nestedAssemblies: [{ + stacks: [MockStack.MOCK_STACK_C], + }], + }); + + return new CdkToolkit({ + ioHost, + cloudExecutable: stageOnlyExecutable, + configuration: stageOnlyExecutable.configuration, + sdkProvider: stageOnlyExecutable.sdkProvider, + deployments: new FakeCloudFormation({ + 'Test-Stack-C': { Baz: 'Zinga!' }, + }), + }); +} + const mockSdk = new MockSdk(); describe('bootstrap', () => { @@ -1112,19 +1148,7 @@ describe('destroy', () => { }); test('destroys stack in single-stack configuration', async () => { - const singleStackExecutable = await MockCloudExecutable.create({ - stacks: [MockStack.MOCK_STACK_B], - }); - - const toolkit = new CdkToolkit({ - ioHost, - cloudExecutable: singleStackExecutable, - configuration: singleStackExecutable.configuration, - sdkProvider: singleStackExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-B': { Foo: 'Bar' }, - }), - }); + const toolkit = await singleStackToolkitSetup(); await expect(toolkit.destroy({ selector: { patterns: [] }, @@ -1135,19 +1159,7 @@ describe('destroy', () => { }); test('destroys stack with pattern in single-stack configuration', async () => { - const singleStackExecutable = await MockCloudExecutable.create({ - stacks: [MockStack.MOCK_STACK_B], - }); - - const toolkit = new CdkToolkit({ - ioHost, - cloudExecutable: singleStackExecutable, - configuration: singleStackExecutable.configuration, - sdkProvider: singleStackExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-B': { Foo: 'Bar' }, - }), - }); + const toolkit = await singleStackToolkitSetup(); await expect(toolkit.destroy({ selector: { patterns: ['Test-Stack-B'] }, @@ -1158,25 +1170,7 @@ describe('destroy', () => { }); test('destroys stack within stage in stage-only configuration', async () => { - // only stacks within stages (no top-level stacks) - const nestedOnlyExecutable = await MockCloudExecutable.create({ - stacks: [], - nestedAssemblies: [ - { - stacks: [MockStack.MOCK_STACK_C], - }, - ], - }); - - const toolkit = new CdkToolkit({ - ioHost, - cloudExecutable: nestedOnlyExecutable, - configuration: nestedOnlyExecutable.configuration, - sdkProvider: nestedOnlyExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-C': { Baz: 'Zinga!' }, - }), - }); + const toolkit = await stageOnlyToolkitSetup(); await expect(toolkit.destroy({ selector: { patterns: ['Test-Stack-A/Test-Stack-C'] }, @@ -1187,25 +1181,7 @@ describe('destroy', () => { }); test('destroys stack within stage with wildcard pattern in stage-only configuration', async () => { - // only stacks within stages (no top-level stacks) - const nestedOnlyExecutable = await MockCloudExecutable.create({ - stacks: [], - nestedAssemblies: [ - { - stacks: [MockStack.MOCK_STACK_C], - }, - ], - }); - - const toolkit = new CdkToolkit({ - ioHost, - cloudExecutable: nestedOnlyExecutable, - configuration: nestedOnlyExecutable.configuration, - sdkProvider: nestedOnlyExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-C': { Baz: 'Zinga!' }, - }), - }); + const toolkit = await stageOnlyToolkitSetup(); await expect(toolkit.destroy({ selector: { patterns: ['Test*/*'] }, @@ -1331,25 +1307,7 @@ describe('destroy', () => { }); test('warns when destroying non-existent stack in stage-only configuration', async () => { - // only stacks within stages (no top-level stacks) - const nestedOnlyExecutable = await MockCloudExecutable.create({ - stacks: [], - nestedAssemblies: [ - { - stacks: [MockStack.MOCK_STACK_C], - }, - ], - }); - - const toolkit = new CdkToolkit({ - ioHost, - cloudExecutable: nestedOnlyExecutable, - configuration: nestedOnlyExecutable.configuration, - sdkProvider: nestedOnlyExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-C': { Baz: 'Zinga!' }, - }), - }); + const toolkit = await stageOnlyToolkitSetup(); await toolkit.destroy({ selector: { patterns: ['Test-Stack-X'] }, From 313f1751f46d0b09cba9eb6e14db5fe555d2f574 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:17:10 +0900 Subject: [PATCH 16/25] rm unused --- packages/aws-cdk/lib/cli/cdk-toolkit.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index 1b96e9050..0a53466e1 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -69,7 +69,6 @@ import { cdkCliErrorName } from './telemetry/error'; import { CLI_PRIVATE_SPAN } from './telemetry/messages'; import type { ErrorDetails } from './telemetry/schema'; import { FlagOperations } from '../commands/flags/operations'; -import { warning } from '../legacy'; // Must use a require() otherwise esbuild complains about calling a namespace // eslint-disable-next-line @typescript-eslint/no-require-imports,@typescript-eslint/consistent-type-imports From 5f4ed9642103bc30ba2baa061902eba67c698b5a Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 20:30:30 +0900 Subject: [PATCH 17/25] integ tweak --- .../cli-integ/resources/cdk-apps/app/app.js | 4 ++++ ...cdk-destroy-nonexistent-stack.integtest.ts | 8 +++---- .../cdk-destroy-stage-only.integtest.ts | 22 +++++++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js index 8735e727c..8e4ef8fd6 100755 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js @@ -993,6 +993,10 @@ switch (stackSet) { case 'stage-with-no-stacks': break; + case 'stage-only': + new SomeStage(app, `${stackPrefix}-stage`); + break; + default: throw new Error(`Unrecognized INTEG_STACK_SET: '${stackSet}'`); } diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts index 76d28268f..c7cf2076a 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts @@ -11,13 +11,11 @@ integTest('cdk destroy with no force option exits without prompt if the stacks d const nonExistingStackName1 = 'non-existing-stack-1'; const nonExistingStackName2 = 'non-existing-stack-2'; - await expect(fixture.cdk(['destroy', ...fixture.fullStackName([nonExistingStackName1, nonExistingStackName2])])).resolves.not.toThrow(); + await expect(fixture.cdkDestroy([...fixture.fullStackName([nonExistingStackName1, nonExistingStackName2])], { + force: false, + })).resolves.not.toThrow(); })); integTest('cdk destroy does not fail with wildcard pattern that matches no stacks', withDefaultFixture(async (fixture) => { await expect(fixture.cdkDestroy('NonExistent*')).resolves.not.toThrow(); })); - -integTest('cdk destroy does not fail with --all when no stacks exist', withDefaultFixture(async (fixture) => { - await expect(fixture.cdkDestroy('--all')).resolves.not.toThrow(); -})); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts new file mode 100644 index 000000000..12ab7dc31 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts @@ -0,0 +1,22 @@ +import { DescribeStacksCommand } from '@aws-sdk/client-cloudformation'; +import { integTest, withDefaultFixture } from '../../../lib'; + +integTest('cdk destroy can destroy stacks in stage-only configuration', withDefaultFixture(async (fixture) => { + const stageNameSuffix = 'stage'; + const specifiedStackName = `${stageNameSuffix}/*`; + + await fixture.cdkDeploy(specifiedStackName); + + const stackName = `${fixture.fullStackName(stageNameSuffix)}-StackInStage`; + const stack = await fixture.aws.cloudFormation.send(new DescribeStacksCommand({ StackName: stackName })); + expect(stack.Stacks?.length ?? 0).toEqual(1); + + await fixture.cdkDestroy(['stage/*'], { + modEnv: { + INTEG_STACK_SET: 'stage-only', + }, + }); + + await expect(fixture.aws.cloudFormation.send(new DescribeStacksCommand({ StackName: stackName }))) + .rejects.toThrow(/does not exist/); +})); From 7d40e0b5b45e9b03c9c289c27c9334f849c7e8eb Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 20:50:02 +0900 Subject: [PATCH 18/25] fix integ --- .../destroy/cdk-destroy-nonexistent-stack.integtest.ts | 2 +- .../cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts index c7cf2076a..1dddb52f5 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts @@ -11,7 +11,7 @@ integTest('cdk destroy with no force option exits without prompt if the stacks d const nonExistingStackName1 = 'non-existing-stack-1'; const nonExistingStackName2 = 'non-existing-stack-2'; - await expect(fixture.cdkDestroy([...fixture.fullStackName([nonExistingStackName1, nonExistingStackName2])], { + await expect(fixture.cdkDestroy([nonExistingStackName1, nonExistingStackName2], { force: false, })).resolves.not.toThrow(); })); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts index 12ab7dc31..c59921544 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts @@ -11,7 +11,7 @@ integTest('cdk destroy can destroy stacks in stage-only configuration', withDefa const stack = await fixture.aws.cloudFormation.send(new DescribeStacksCommand({ StackName: stackName })); expect(stack.Stacks?.length ?? 0).toEqual(1); - await fixture.cdkDestroy(['stage/*'], { + await fixture.cdkDestroy('stage/*', { modEnv: { INTEG_STACK_SET: 'stage-only', }, From 36200a4d5e2addf5221a01415272b4c0d1dfc666 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 21:18:10 +0900 Subject: [PATCH 19/25] test --- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index cbc3db0b9..bfdcf3b92 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1302,6 +1302,7 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ expectIoMsg(expect.stringContaining(`${chalk.red('test*/*')} does not exist. Do you mean ${chalk.blue('Test-Stack-A/Test-Stack-C')}?`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('test*/*')}`), 'warn'), ]), ); }); From d43bd490cd998447f9f1e3907ab476d2d37d8c4f Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 21:27:55 +0900 Subject: [PATCH 20/25] style --- packages/aws-cdk/lib/cli/cdk-toolkit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index 0a53466e1..1ddc33e7a 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -1427,7 +1427,7 @@ export class CdkToolkit { const closelyMatched = pattern.closelyMatched.length > 0 ? ` Do you mean ${chalk.blue(pattern.closelyMatched.join(', '))}?` : ''; await this.ioHost.asIoHelper().defaults.warn(`${chalk.red(pattern.pattern)} does not exist.${closelyMatched}`); } - }; + } } /** From c3ce6f6b86b3fbd3fd83a45b7e3819ee80615bf2 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:07:59 +0900 Subject: [PATCH 21/25] integ namte --- .../destroy/cdk-destroy-nonexistent-stack.integtest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts index 1dddb52f5..429e6c5be 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-nonexistent-stack.integtest.ts @@ -16,6 +16,6 @@ integTest('cdk destroy with no force option exits without prompt if the stacks d })).resolves.not.toThrow(); })); -integTest('cdk destroy does not fail with wildcard pattern that matches no stacks', withDefaultFixture(async (fixture) => { - await expect(fixture.cdkDestroy('NonExistent*')).resolves.not.toThrow(); +integTest('cdk destroy does not fail even if the stages do not exist', withDefaultFixture(async (fixture) => { + await expect(fixture.cdkDestroy('NonExistent/*')).resolves.not.toThrow(); })); From 32e15562c734119f3c12a6c549607f738ed75f40 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:13:28 +0900 Subject: [PATCH 22/25] add unit test to coverage --- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index bfdcf3b92..a5159fe29 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1208,6 +1208,23 @@ describe('destroy', () => { ]); }); + test('warns if there are only non-existent stacks even when exclusively is false', async () => { + const toolkit = defaultToolkitSetup(); + + await toolkit.destroy({ + selector: { patterns: ['Test-Stack-X', 'Test-Stack-Y'] }, + exclusively: false, + force: true, + fromDeploy: true, + }); + + expect(flatten(notifySpy.mock.calls)).toEqual([ + expectIoMsg(expect.stringContaining(`${chalk.red('Test-Stack-X')} does not exist.`), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('Test-Stack-Y')} does not exist.`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('Test-Stack-X, Test-Stack-Y')}`), 'warn'), + ]); + }); + test('warns if there is a non-existent stack and the other exists', async () => { const toolkit = defaultToolkitSetup(); From ef4e8de4423c2b5d3aab79419902787a875cb321 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:17:41 +0900 Subject: [PATCH 23/25] require exclusively --- packages/aws-cdk/lib/cli/cdk-toolkit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index 1ddc33e7a..e55604f6e 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -1388,7 +1388,7 @@ export class CdkToolkit { private async suggestStacks(props: { selector: StackSelector; stacks: StackCollection; - exclusively?: boolean; + exclusively: boolean; }) { if (props.selector.patterns.length === 0) { return; From 5801b2c51148d105a2bcbaa736336ecb19328514 Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 23:13:09 +0900 Subject: [PATCH 24/25] change integ integ integ integ --- .../destroy/cdk-destroy-stage-only.integtest.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts index c59921544..95a7d1c50 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only.integtest.ts @@ -2,18 +2,24 @@ import { DescribeStacksCommand } from '@aws-sdk/client-cloudformation'; import { integTest, withDefaultFixture } from '../../../lib'; integTest('cdk destroy can destroy stacks in stage-only configuration', withDefaultFixture(async (fixture) => { + const integStackSet = 'stage-only'; + const stageNameSuffix = 'stage'; const specifiedStackName = `${stageNameSuffix}/*`; - await fixture.cdkDeploy(specifiedStackName); + await fixture.cdkDeploy(specifiedStackName, { + modEnv: { + INTEG_STACK_SET: integStackSet, + }, + }); const stackName = `${fixture.fullStackName(stageNameSuffix)}-StackInStage`; const stack = await fixture.aws.cloudFormation.send(new DescribeStacksCommand({ StackName: stackName })); expect(stack.Stacks?.length ?? 0).toEqual(1); - await fixture.cdkDestroy('stage/*', { + await fixture.cdkDestroy(specifiedStackName, { modEnv: { - INTEG_STACK_SET: 'stage-only', + INTEG_STACK_SET: integStackSet, }, }); From c4203ad6e88cf12b12fb118579b70d264754325e Mon Sep 17 00:00:00 2001 From: go-to-k <24818752+go-to-k@users.noreply.github.com> Date: Wed, 10 Dec 2025 23:30:58 +0900 Subject: [PATCH 25/25] refactor tests --- packages/aws-cdk/test/cli/cdk-toolkit.test.ts | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index a5159fe29..d05218d34 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -1252,11 +1252,11 @@ describe('destroy', () => { ); }); - test('suggests valid names if there is a non-existent but closely matching stack', async () => { + test('warns when wildcard pattern does not match any stacks', async () => { const toolkit = defaultToolkitSetup(); await toolkit.destroy({ - selector: { patterns: ['test-stack-b'] }, + selector: { patterns: ['Foo*/*'] }, exclusively: true, force: true, fromDeploy: true, @@ -1264,17 +1264,17 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - expectIoMsg(expect.stringContaining(`${chalk.red('test-stack-b')} does not exist. Do you mean ${chalk.blue('Test-Stack-B')}?`), 'warn'), - expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('test-stack-b')}`), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('Foo*/*')} does not exist.`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('Foo*/*')}`), 'warn'), ]), ); }); - test('suggests stack names within stages if there is a non-existent but closely matching stack', async () => { - const toolkit = defaultToolkitSetup(); + test('warns when destroying non-existent stack in stage-only configuration', async () => { + const toolkit = await stageOnlyToolkitSetup(); await toolkit.destroy({ - selector: { patterns: ['test-stack-a/test-stack-c'] }, + selector: { patterns: ['Foo*/*'] }, exclusively: true, force: true, fromDeploy: true, @@ -1282,17 +1282,17 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - expectIoMsg(expect.stringContaining(`${chalk.red('test-stack-a/test-stack-c')} does not exist. Do you mean ${chalk.blue('Test-Stack-A/Test-Stack-C')}?`), 'warn'), - expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('test-stack-a/test-stack-c')}`), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('Foo*/*')} does not exist.`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('Foo*/*')}`), 'warn'), ]), ); }); - test('warns when wildcard pattern does not match any stacks', async () => { + test('suggests valid names if there is a non-existent but closely matching stack', async () => { const toolkit = defaultToolkitSetup(); await toolkit.destroy({ - selector: { patterns: ['Foo*/*'] }, + selector: { patterns: ['test-stack-b'] }, exclusively: true, force: true, fromDeploy: true, @@ -1300,17 +1300,17 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - expectIoMsg(expect.stringContaining(`${chalk.red('Foo*/*')} does not exist.`), 'warn'), - expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('Foo*/*')}`), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('test-stack-b')} does not exist. Do you mean ${chalk.blue('Test-Stack-B')}?`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('test-stack-b')}`), 'warn'), ]), ); }); - test('suggests stack with wildcard pattern when only case differs', async () => { + test('suggests stack names within stages if there is a non-existent but closely matching stack', async () => { const toolkit = defaultToolkitSetup(); await toolkit.destroy({ - selector: { patterns: ['test*/*'] }, + selector: { patterns: ['test-stack-a/test-stack-c'] }, exclusively: true, force: true, fromDeploy: true, @@ -1318,17 +1318,17 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - expectIoMsg(expect.stringContaining(`${chalk.red('test*/*')} does not exist. Do you mean ${chalk.blue('Test-Stack-A/Test-Stack-C')}?`), 'warn'), - expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('test*/*')}`), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('test-stack-a/test-stack-c')} does not exist. Do you mean ${chalk.blue('Test-Stack-A/Test-Stack-C')}?`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('test-stack-a/test-stack-c')}`), 'warn'), ]), ); }); - test('warns when destroying non-existent stack in stage-only configuration', async () => { - const toolkit = await stageOnlyToolkitSetup(); + test('suggests stack with wildcard pattern when only case differs', async () => { + const toolkit = defaultToolkitSetup(); await toolkit.destroy({ - selector: { patterns: ['Test-Stack-X'] }, + selector: { patterns: ['test*/*'] }, exclusively: true, force: true, fromDeploy: true, @@ -1336,8 +1336,8 @@ describe('destroy', () => { expect(flatten(notifySpy.mock.calls)).toEqual( expect.arrayContaining([ - expectIoMsg(expect.stringContaining(`${chalk.red('Test-Stack-X')} does not exist.`), 'warn'), - expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('Test-Stack-X')}`), 'warn'), + expectIoMsg(expect.stringContaining(`${chalk.red('test*/*')} does not exist. Do you mean ${chalk.blue('Test-Stack-A/Test-Stack-C')}?`), 'warn'), + expectIoMsg(expect.stringContaining(`No stacks match the name(s): ${chalk.red('test*/*')}`), 'warn'), ]), ); });