Skip to content

feat(cli): warn of non-existent stacks in cdk destroy#984

Open
go-to-k wants to merge 10 commits into
aws:mainfrom
go-to-k:destroy
Open

feat(cli): warn of non-existent stacks in cdk destroy#984
go-to-k wants to merge 10 commits into
aws:mainfrom
go-to-k:destroy

Conversation

@go-to-k

@go-to-k go-to-k commented Dec 10, 2025

Copy link
Copy Markdown
Contributor

Fixes aws/aws-cdk#32836, aws/aws-cdk#32545, aws/aws-cdk#27179, aws/aws-cdk#22240

Reason for this change

This PR implements the feature that warns users when non-existent stacks are specified in cdk destroy.

  • It does not display the message Are you sure you want to delete: if there is no matching stack.
  • Even if the stack does not exist, cdk destroy will not fail, it will just print a warning.

For examples (that have Stacka, StackA, StackX):

destroy2
destroy3
destroy4

Difference from previous PR

The previous PR was reverted in aws/aws-cdk#32839 due to a regression with only nested stage stacks. So this version addresses that regression with comprehensive tests.

Description of changes

The original implementation added warnings for non-existent stacks in cdk destroy, but it failed when applications had only nested stage stacks (no top-level stacks). This happened because the code used allTopLevel: true, which only searched for stacks directly under the App, ignoring stacks within nested Stages.

Fixed the regression by changing suggestStacks method to use DefaultSelection.AllStacks instead of allTopLevel: true, ensuring the warning feature works for all stack configurations (top-level only, nested only, or both). Added regression tests to verify the fix for the nested stage scenario.

  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,
+      defaultBehavior: DefaultSelection.AllStacks,
    });

Additional Information

When running cdk destroy --all or cdk deploy --all against a configuration with no top-level stacks (nested stages only), the following error occurs.

However, this behavior existed before this PR and is unrelated to the changes made here. Since it is outside the scope of this PR, no fix has been implemented for this behavior.

FYI: I have submitted an issue and a PR about this behavior.

const app = new cdk.App();

new MyStage(app, 'MyStage'); // This has CdkSampleStack
> cdk deploy
Since this app includes more than a single stack, specify which stacks to use (wildcards are supported) or specify `--all`
Stacks: MyStage/CdkSampleStack

> cdk deploy --all
...
No stack found in the main cloud assembly. Use "list" to print manifest

> cdk destroy --all
...
No stack found in the main cloud assembly. Use "list" to print manifest

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@aws-cdk-automation aws-cdk-automation requested a review from a team December 10, 2025 12:11
@github-actions github-actions Bot added the p2 label Dec 10, 2025
auto-merge was automatically disabled December 10, 2025 12:18

Head branch was pushed to by a user without write access

@codecov-commenter

codecov-commenter commented Dec 10, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.55556% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.13%. Comparing base (fff4c93) to head (9c1083c).

Files with missing lines Patch % Lines
packages/aws-cdk/lib/cli/cdk-toolkit.ts 95.55% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #984      +/-   ##
==========================================
- Coverage   89.14%   89.13%   -0.02%     
==========================================
  Files          77       77              
  Lines       11539    11528      -11     
  Branches     1605     1602       -3     
==========================================
- Hits        10287    10276      -11     
  Misses       1221     1221              
  Partials       31       31              
Flag Coverage Δ
suite.unit 89.13% <95.55%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The `refactor` command only operates on the stacks that are relevant for
the target CDK application. However, it gets all the templates of the
deployed stacks before filtering them, which is wasteful.

Invert the order, so that the filtering happens before getting the
templates.

---
By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache-2.0 license
@mrgrain

mrgrain commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

@go-to-k I am looking to get this in soon (hence the rebase), but first want to change destroy to use toolkit-lib code internally, so we can avoid duplicate and complicated code.

go-to-k added 5 commits June 24, 2026 12:35
toolkit-lib: add `force` to DestroyOptions; add W7010/W7011 warnings; move the
stack-name suggestion + no-match warning + force-skip into `_destroy`; expose
`destroyForAction` as protected so the CLI can keep `deploy` message attribution.

aws-cdk: thin destroy delegation with selector -> StackSelector mapping; add
`InternalToolkit.destroyFromDeploy`; remove `fromDeploy`/legacy loop; preserve
graceful abort on a declined confirmation.
Generated output of routing destroy through toolkit-lib: message-registry.md is
regenerated from messages.ts, and the destroy command's __io_snapshots__ reflect
the messages now emitted by toolkit-lib. No hand edits.
Move the destroy logic tests to toolkit-lib (including stage/nested-stage
regression coverage and the force / suggestion / no-match cases) with new stage
fixtures; reduce the CLI tests to delegation/mapping checks; rework the destroy
command snapshot test to spy on Deployments.prototype.
The destroy command snapshot test was non-deterministic across color modes:
toolkit-lib wraps a leading newline inside a chalk color escape (`\n ✅ ...`),
so `withTrimmedWhitespace`'s `.trim()` cannot remove it when colors are on (the
string starts with the ANSI escape, not whitespace). The recorder stripped ANSI
only after receiving the already-(un)trimmed message, so the leading newline
survived with colors on and the snapshot differed between a TTY and CI.

Trim in the recorder's `normalize()` after stripping ANSI so the recorded stream
is the same regardless of whether colors were enabled, and regenerate the
affected snapshots.
@go-to-k

go-to-k commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@mrgrain Done. Destroy output now flows through toolkit-lib, so it's coded and consistent with deploy (adds Synthesis time / Destroy time, and the success line is result level). User-facing text is unchanged. (see the diff for the snapshots: packages/aws-cdk/test/commands/_io_snapshots_/destroy/xx.ndjson.)

The destroy confirmation prompt colored the stack names red. Use blue to match
the color the CLI used before destroy was routed through toolkit-lib.
@go-to-k

go-to-k commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@mrgrain Also restored the confirmation prompt's stack-name color to blue: 4626130

It had turned red because toolkit-lib's destroy used chalk.red, whereas the CLI used chalk.blue before.

Before:
color-diff

After:
after

Align cdk destroy with the confirmation-prompt contract from aws#1667:
declining now throws AbortError('DestroyAborted', 'Deletion cancelled')
and exits non-zero instead of emitting CDK_TOOLKIT_E7010 and exiting 0.
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Comment on lines +1613 to +1616
if (stacks.stackCount === 0) {
await ioHelper.notify(IO.CDK_TOOLKIT_W7011.msg(
`No stacks match the name(s): ${chalk.red((selectStacks.patterns ?? []).join(', '))}`,
));

@go-to-k go-to-k Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note on the exit code: no-match (W7011) exits 0, while a declined confirmation exits 1 (#1667). This is deliberate and keeps the existing behavior. The old selectStacksForDestroy had a // No validation comment and returned an empty selection, so cdk destroy <nonexistent> already exited 0 (deploy throws NoStacksMatched, destroy never did). We keep that contract and add a warning in place of the old empty prompt.

exit

@go-to-k

go-to-k commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Merged your PR and fixed conflicts.

Now:

cancel

Comment on lines +363 to +369
// Trim only after stripping ANSI, so the snapshot is independent of the
// active color mode (TTY vs not). toolkit-lib trims its messages, but a
// leading newline that is wrapped inside a chalk color escape survives that
// trim (the string starts with the escape, not whitespace); once we strip
// the ANSI here the newline is re-exposed, so we trim again to keep the
// recorded stream deterministic regardless of whether colors were enabled.
return out.trim();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Keeps snapshots independent of color mode (TTY vs not): a chalk-wrapped leading newline survives toolkit-lib's own trim, so without this the snapshot differs between color on/off.

Comment on lines -1821 to -1825
/**
* Whether the destroy request came from a deploy.
*/
fromDeploy?: boolean;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not breaking: this DestroyOptions is internal to the aws-cdk package (not in its exports), so it isn't user-facing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cdk destroy: No stack found in the main cloud assembly

5 participants