Skip to content

refactor(cli): scope IoHost listeners via suppressMessages decorator - #1887

Merged
mrgrain merged 1 commit into
mainfrom
mrgrain/refactor/cli/suppress-messages-decorator
Aug 24, 2026
Merged

refactor(cli): scope IoHost listeners via suppressMessages decorator#1887
mrgrain merged 1 commit into
mainfrom
mrgrain/refactor/cli/suppress-messages-decorator

Conversation

@mrgrain

@mrgrain mrgrain commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Several CdkToolkit methods drop specific toolkit-lib messages by registering preventDefault listeners on the CliIoHost and then restoring behavior with a blanket removeAllListeners() in a finally block. That pattern is repetitive, and the blanket removal is a blunt instrument: it tears down every user listener on the shared host, not just the ones the method registered.

This change introduces a @suppressMessages(...codes) method decorator that registers a single method-scoped drop-listener (via matchAny) before the decorated method runs and disposes exactly that listener when the method settles, whether it returns or throws. It is applied to metadata, destroy, and list, and every remaining removeAllListeners() call is replaced with disposal of exactly the listeners the method itself registered.

Scoping the disposal also fixes two latent bugs. When destroy ran as part of a deploy (a stack with no resources triggers a delete mid-deployment), its removeAllListeners() wiped deploy's I5060 approval-rewrite listener for the rest of the deployment. And list's one-shot I2901 formatter leaked with stale options whenever synthesis failed before the listing was emitted.

Tested with new unit tests for the decorator (suppression is active only for the duration of the call, including the throwing path) and a behavioral test on the cdk list path that asserts the synth-time (I1000) and dependency-expansion (I1002) lines are emitted but dropped, using a new IoHostRecorder.entries({ includeDropped: true }) override so dropped messages stay out of the committed NDJSON snapshots. The destroy.test.ts failures visible in this area reproduce identically on a clean main checkout and are unrelated.

Checklist

  • This change contains a major version upgrade for a dependency and I confirm all breaking changes are addressed
    • Release notes for the new version:

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

Several CdkToolkit methods registered preventDefault listeners on the
CliIoHost to drop specific toolkit-lib messages and then restored
behavior with a blanket removeAllListeners() in a finally block.

Introduce a @suppressMessages(...codes) method decorator that registers
a single method-scoped drop-listener (via matchAny) before the method
runs and disposes exactly that listener when the method settles. Apply
it to metadata, destroy, and list, and replace every remaining
removeAllListeners() with disposal of exactly the listeners each method
registered.

This also fixes two latent leaks: destroy running as part of a deploy
(empty-resource stacks) no longer wipes deploy's I5060 rewrite
listener, and list's one-shot I2901 formatter no longer leaks when
synthesis fails before the listing is emitted.

The list suppression is now asserted behaviorally in the list command
tests via IoHostRecorder.entries({ includeDropped: true }), keeping
dropped messages out of the committed snapshots.
@mrgrain
mrgrain deployed to automation August 24, 2026 11:27 — with GitHub Actions Active
@mrgrain
mrgrain deployed to automation August 24, 2026 11:27 — with GitHub Actions Active
@mrgrain
mrgrain deployed to no-approval August 24, 2026 11:27 — with GitHub Actions Active
@aws-cdk-automation
aws-cdk-automation requested a review from a team August 24, 2026 11:27
@github-actions github-actions Bot added the p2 label Aug 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.66667% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.37%. Comparing base (8d772fc) to head (8d85981).

Files with missing lines Patch % Lines
packages/aws-cdk/lib/cli/io-host/cli-io-host.ts 93.02% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1887   +/-   ##
=======================================
  Coverage   91.36%   91.37%           
=======================================
  Files          80       80           
  Lines       12207    12254   +47     
  Branches     1751     1749    -2     
=======================================
+ Hits        11153    11197   +44     
- Misses       1018     1021    +3     
  Partials       36       36           
Flag Coverage Δ
suite.unit 91.37% <96.66%> (+<0.01%) ⬆️

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.

@mrgrain
mrgrain added this pull request to the merge queue Aug 24, 2026
Merged via the queue into main with commit e895513 Aug 24, 2026
46 checks passed
@mrgrain
mrgrain deleted the mrgrain/refactor/cli/suppress-messages-decorator branch August 24, 2026 14:08
mrgrain added a commit to go-to-k/aws-cdk-cli that referenced this pull request Aug 24, 2026
Stacked on aws#1887.

After the `suppressMessages` decorator took over the static message
drops, the remaining dynamic listeners in `CdkToolkit` — deploy's
approval-framing rewrite, destroy's level override, decline-aborts, and
`--force` responder, and the one-shot listing formatters — still
required collecting dispose functions and calling them in a `finally`
block, which is verbose and easy to get wrong.

This change makes the listener registration methods on `CliIoHost`
(`on`, `once`, `rewrite`, `rewriteOnce`, `respond`, `respondOnce`)
return a `DisposeListener`: still a plain callable remover, but also a
`Disposable`, so call sites can bind it with a `using` declaration and
drop the `try`/`finally` entirely. Disposal then happens at scope exit,
including early returns and throws, and conditional listeners come for
free because `using x = cond ? ioHost.respondOnce(...) : undefined`
simply skips disposal for `undefined`. The repository already relies on
TypeScript explicit resource management (`using` for telemetry timers),
so no toolchain change is needed, and the return type change is
backwards compatible for every existing caller. Most of the diff in
`deploy()` is the mechanical de-indent from removing its `try`/`finally`
wrapper.

Tested with a new unit test asserting the remover works as a `using`
resource (listener active inside the block, gone at scope exit), plus
the existing decorator and command suites; 234 tests across the affected
suites pass and lint is clean.

### Checklist
- [ ] This change contains a major version upgrade for a dependency and
I confirm all breaking changes are addressed
  - Release notes for the new version:

---
By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache-2.0 license
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.

3 participants