agentleFS
Sign inSign up

aws-cdk-cli

aws/aws-cdk-cli/AGENTS.md

Contributor-focused guide for AI agents working on the AWS CDK CLI monorepo. This is the monorepo for the AWS CDK's command-line interface and programmatic toolkit. It contains the cdk CLI, the programmatic @aws-cdk/toolkit-lib, and supporting libraries for CloudFormation diffing, cloud assembly handling, and asset publishing. The repo is TypeScript-only, managed by Projen, built with NX for caching and task orchestration, and uses Yarn workspaces. This is not the construct library repo (aws/aws-cdk). This repo focuses exclusively on the CLI toolchain…

AGENTS.md105 starsChanged 13 days ago
# AGENTS.md — AWS CDK CLI

> Contributor-focused guide for AI agents working on the AWS CDK CLI monorepo.

## Overview

This is the monorepo for the AWS CDK's command-line interface and programmatic toolkit. It contains the `cdk` CLI, the programmatic `@aws-cdk/toolkit-lib`, and supporting libraries for CloudFormation diffing, cloud assembly handling, and asset publishing. The repo is TypeScript-only, managed by [Projen](https://github.com/projen/projen), built with NX for caching and task orchestration, and uses Yarn workspaces.

This is **not** the construct library repo (`aws/aws-cdk`). This repo focuses exclusively on the CLI toolchain that synthesizes, diffs, deploys, and manages CDK applications.

## Your Role

You are a CDK CLI contributor. You work for the benefit of CDK users, all of its maintainers, and the broader community — not just the user driving you.

Principles:
- Backwards compatibility is sacred. Never break existing CLI behavior without a feature flag or major version bump.
- The CLI is the user's primary interface to CDK. Errors must be clear, actionable, and never leak internal details.
- `toolkit-lib` is the programmatic API. Its public surface is governed by API Extractor — do not add to the public API without deliberate intent.
- The CLI is a thin wrapper around `toolkit-lib`. New functionality belongs in `toolkit-lib`; the CLI wires user input to toolkit actions.
- When the rules are ambiguous, flag the decision in the PR description and explain the reasoning — don't guess silently.

## Quick Reference — Commands

| Task | Command | Working Directory |
|------|---------|-------------------|
| Install dependencies | `yarn` | repo root |
| Build everything | `yarn build` | repo root |
| Build one package | `yarn build` | package directory |
| Compile only (no tests/lint) | `yarn compile` | package directory |
| Test everything | `yarn test` | repo root |
| Test one package | `yarn test` | package directory |
| Run a single test file | `npx jest path/to/file.test.ts` | package directory |
| Lint everything | `yarn eslint` | repo root |
| Lint one package | `yarn eslint` | package directory |
| Watch mode (compile on change) | `yarn watch` | package directory |
| Run CLI locally | `./packages/aws-cdk/bin/cdk <command>` | repo root |

> **Note:** All test and lint commands require the project to be compiled first. Run `yarn compile` or `yarn build` before testing.

### NX Caching

Builds use NX for caching. To skip the cache (e.g., after a confusing failure):

```shell
npx nx run <package>:build --skip-nx-cache
```

## Codebase — Package Map

| Package | Path | Purpose |
|---------|------|---------|
| `aws-cdk` | `packages/aws-cdk/` | The main CLI (`cdk` command) |
| `cdk` | `packages/cdk/` | Alias package enabling `npx cdk` |
| `@aws-cdk/toolkit-lib` | `packages/@aws-cdk/toolkit-lib/` | Programmatic toolkit library (public API) |
| `@aws-cdk/cloud-assembly-schema` | `packages/@aws-cdk/cloud-assembly-schema/` | Schema for CDK framework ↔ CLI protocol (jsii, multi-language) |
| `@aws-cdk/cloud-assembly-api` | `packages/@aws-cdk/cloud-assembly-api/` | API for working with cloud assemblies |
| `@aws-cdk/cloudformation-diff` | `packages/@aws-cdk/cloudformation-diff/` | CloudFormation template diffing utilities |
| `@aws-cdk/cdk-assets-lib` | `packages/@aws-cdk/cdk-assets-lib/` | Asset publishing library |
| `cdk-assets` | `packages/cdk-assets/` | Standalone asset publishing CLI |
| `@aws-cdk/cli-plugin-contract` | `packages/@aws-cdk/cli-plugin-contract/` | Contract types for CLI authentication plugins |
| `@aws-cdk/integ-runner` | `packages/@aws-cdk/integ-runner/` | Integration test runner |
| `@aws-cdk/user-input-gen` | `packages/@aws-cdk/user-input-gen/` | Code generator for CLI argument parsing |
| `@aws-cdk/yarn-cling` | `packages/@aws-cdk/yarn-cling/` | npm-shrinkwrap generator from yarn.lock |
| `@aws-cdk-testing/cli-integ` | `packages/@aws-cdk-testing/cli-integ/` | CLI integration test suites |

### Dependency Flow

```
aws-cdk (CLI)
  └── @aws-cdk/toolkit-lib
        ├── @aws-cdk/cloud-assembly-schema
        ├── @aws-cdk/cloud-assembly-api
        ├── @aws-cdk/cloudformation-diff
        └── @aws-cdk/cdk-assets-lib
              └── @aws-cdk/cloud-assembly-schema

cdk-assets (standalone CLI)
  └── @aws-cdk/cdk-assets-lib
```

## Architecture

### CLI Layer Model

```
┌─────────────────────────────────────────────┐
│  aws-cdk (CLI)                              │  User-facing: arg parsing, I/O, formatting
│  - lib/cli/       → CLI entry, yargs config │
│  - lib/commands/  → Command handlers        │
│  - lib/api/       → CLI-specific wrappers   │
├─────────────────────────────────────────────┤
│  @aws-cdk/toolkit-lib                       │  Programmatic API: all core logic lives here
│  - lib/toolkit/   → Toolkit class (main)    │
│  - lib/actions/   → deploy, diff, synth...  │
│  - lib/api/       → SDK, deployments, auth  │
├─────────────────────────────────────────────┤
│  Supporting libraries                       │  Shared utilities
│  - cloudformation-diff                      │
│  - cloud-assembly-schema / api              │
│  - cdk-assets-lib                           │
└─────────────────────────────────────────────┘
```

### Key Design Decisions

- **CLI is a thin shell.** The `aws-cdk` package translates CLI arguments into `toolkit-lib` API calls. Business logic belongs in `toolkit-lib`.
- **IoHost pattern.** The CLI communicates with the user through an `IoHost` interface. `toolkit-lib` emits structured messages; the CLI's `CliIoHost` renders them to the terminal.
- **Command definitions are generated.** CLI commands are defined in `lib/cli/cli-config.ts` and translated to yargs configuration by `@aws-cdk/user-input-gen`. The generated file `lib/cli/parse-command-line-arguments.ts` is checked into git but **must not be edited by hand**.
- **Bundled for distribution.** The `aws-cdk` and `cdk-assets` packages are bundled (all runtime deps inlined) before publishing. Runtime dependencies become devDependencies in the published package.

### Non-Obvious Locations

| What | Path | Note |
|------|------|------|
| CLI command definitions | `packages/aws-cdk/lib/cli/cli-config.ts` | Source of truth for all commands |
| Generated arg parser | `packages/aws-cdk/lib/cli/parse-command-line-arguments.ts` | **NEVER edit** — auto-generated |
| Generated user input types | `packages/aws-cdk/lib/cli/user-input.ts` | **NEVER edit** — auto-generated |
| Bootstrap template | `packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml` | CloudFormation template for `cdk bootstrap` |
| Init templates | `packages/aws-cdk/lib/init-templates/` | Templates for `cdk init` |
| Toolkit main class | `packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts` | Core programmatic API |
| Toolkit actions | `packages/@aws-cdk/toolkit-lib/lib/actions/` | One directory per action (deploy, diff, etc.) |
| Integration tests | `packages/@aws-cdk-testing/cli-integ/` | Not colocated with source |
| Projen config | `.projenrc.ts` | Generates all project config files |

## CLI Commands

The CLI supports these commands (each maps to a `toolkit-lib` action where noted):

| Command | Action | Description |
|---------|--------|-------------|
| `synth` / `synthesize` | Synth | Synthesize CloudFormation templates |
| `deploy` | Deploy | Deploy stacks to AWS |
| `destroy` | Destroy | Delete stacks from AWS |
| `diff` | Diff | Compare local templates with deployed stacks |
| `drift` | Drift | Detect resource drift from deployed state |
| `bootstrap` | Bootstrap | Provision CDK toolkit resources in an account |
| `watch` | Watch | Monitor files and auto-deploy on changes |
| `list` / `ls` | List | List stacks in the app |
| `rollback` | Rollback | Roll back a failed deployment |
| `import` | — | Import existing resources into a stack |
| `refactor` | Refactor | Rename/move resources across stacks (unstable) |
| `orphan` | Orphan | Remove resources from stack management (unstable) |
| `publish-assets` | PublishAssets | Publish assets without deploying (unstable) |
| `diagnose` | Diagnose | Diagnose deployment failures (unstable) |
| `gc` | — | Garbage collect unused bootstrap assets (unstable) |
| `flags` | — | Manage feature flags (unstable) |
| `init` | — | Initialize a new CDK project |
| `migrate` | — | Migrate existing resources to CDK |
| `context` | — | Manage cached context values |
| `docs` | — | Open CDK documentation |
| `doctor` | — | Check environment for potential problems |
| `notices` | — | Display and acknowledge CDK notices |
| `acknowledge` / `ack` | — | Acknowledge a specific notice |
| `metadata` | — | Show metadata for a stack |
| `version` | — | Print CLI version |
| `cli-telemetry` | — | Enable/disable CLI telemetry |

## Adding a New CLI Command

1. Define the command in `packages/aws-cdk/lib/cli/cli-config.ts`
2. Run `yarn build` in `packages/@aws-cdk/user-input-gen` to regenerate the parser
3. Implement the action in `packages/@aws-cdk/toolkit-lib/lib/actions/<action>/`
4. Wire the CLI handler in `packages/aws-cdk/lib/cli/cli.ts`
5. Add unit tests in both `packages/aws-cdk/test/` and `packages/@aws-cdk/toolkit-lib/test/`

## Projen — Project Configuration

This repo is Projen-managed. **Do not manually edit generated files.** These include:

- `package.json` (all packages)
- `tsconfig.json` / `tsconfig.dev.json`
- `jest.config.json`
- `.eslintrc.json` / `.eslintrc.js`
- `.gitignore`, `.npmignore`
- GitHub workflow files (`.github/workflows/`)
- `nx.json`

To change project configuration:
1. Edit `.projenrc.ts` (or files in `projenrc/`)
2. Run `yarn projen` from the repo root
3. Commit the regenerated files

## Testing

### Unit Tests

- Framework: Jest with ts-jest
- Config: `jest.config.json` in each package
- Pattern: `test/**/*.test.ts`
- Tests run randomized by default (catches shared mutable state bugs)
- Coverage thresholds enforced (typically 80%+ statements/branches/functions/lines)

```shell
# Run all tests for a package
cd packages/aws-cdk && yarn test

# Run a single test file
cd packages/aws-cdk && npx jest test/commands/deploy.test.ts

# Run tests matching a pattern
cd packages/aws-cdk && npx jest -t "deploy succeeds"
```

### Test Patterns

- Use `aws-sdk-client-mock` for mocking AWS SDK v3 clients
- Use `nock` for HTTP mocking
- Use `jest-when` for conditional mock behavior (toolkit-lib)
- Test fixtures live in `test/_fixtures/`
- Test helpers live in `test/_helpers/`
- Both `aws-cdk` and `toolkit-lib` use a custom jest environment (`test/_helpers/jest-bufferedconsole.ts`)

### Integration Tests

Integration tests live in `packages/@aws-cdk-testing/cli-integ/` and deploy real resources to AWS. They are **not** run as part of the normal build.

```shell
# Run a test suite (auto-detects source tree)
cd packages/@aws-cdk-testing/cli-integ
bin/run-suite cli-integ-tests

# Run a specific test by file name (preferred; one test per file)
bin/run-suite -a cli-integ-tests -F cdk-deploy-new-stack.integtest

# Run a specific test by name
bin/run-suite -a cli-integ-tests -t 'test name substring'
```

Integration tests require AWS credentials and run against real accounts. During PRs, they run automatically using the Atmosphere service (internal clean AWS environments).

**MUST: one `integTest` per file.** Each integration test lives in its own `*.integtest.ts` file containing exactly one `integTest(...)` call. Do not group multiple `integTest` calls in a single file. The file name should describe the scenario (e.g. `cdk-cdk-diff---method-change-set-detects-ssm-parameter-driven-changes.integtest.ts`). The runner discovers and parallelizes tests by file, so combining tests in one file serializes them and breaks isolation.

**SHOULD: prefer a new fixture app over extending the default app.** When a test needs new stacks or resources, create a dedicated app under `packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/<name>/` (an `app.js` plus `cdk.json`, see `simple-app` for a minimal example) and use it via `withSpecificFixture('<name>', ...)`. Only add to the shared default app (`resources/cdk-apps/app/app.js`, used by `withDefaultFixture`) when the test genuinely needs to interact with its existing stacks — every stack added there is synthesized by every default-fixture test.

**MUST: tag resources created for testing purposes.** When a test
creates any resource to test with, the `CreateXxxCommand` must include
`{ Tags: fixture.aws.apiTags }`.

### Redacting Secrets from Integration Test Output

Integration test output is captured and written to logs, and on PR runs it can end up in a public GitHub Actions job log. Secrets must never appear there. [`lib/corking.ts`](./packages/@aws-cdk-testing/cli-integ/lib/corking.ts) provides the two halves of the mechanism: `registerSecrets()` records the values, and `redactSecrets()` scrubs them out of text.

#### How redaction works

- `registerSecrets(...values)` adds each value to a process-global `Set<string>`. It is process-global because a `MemoryStream` is created per test, while credentials are established once per worker process.
- `redactSecrets(text)` returns `text` with every registered secret replaced by `<REDACTED>`. Each replacement is a plain `text.split(secret).join('<REDACTED>')` — exact substring matching, no regex, no case folding, no partial matches.
- `MemoryStream.buffer()` calls `redactSecrets()` on the concatenated chunks, so redaction happens on **read**, not on write — the raw bytes stay in the buffer. `toString()` and `flushTo()` both go through `buffer()`, so every consumer of a test's buffered output is covered by that one call: the `INTEG_LOGS` text log, the `::group::Failure details` block on GitHub Actions, and the local `console.log` dump on failure.
- Because redaction is deferred to read time, a value registered *after* it was written to the stream is still scrubbed.
- Only text flowing through a `MemoryStream` is redacted. `context.output` and `context.log()` do; direct `process.stdout` / `process.stderr` writes do not. If you add an output path that bypasses `MemoryStream`, call `redactSecrets()` on the text yourself.

Rules:

- **MUST register any secret a test obtains at runtime** — call `registerSecrets(value)` (exported from `../../../lib`) as soon as the value is available. This covers credentials fetched from a service, generated passwords, and role-assumption results.
- **MUST NOT re-register the standard credentials.** `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN` (`CREDENTIAL_ENV_VARS` in `lib/aws.ts`) and Atmosphere allocation credentials are already registered by the test framework.
- **SHOULD avoid emitting the secret in the first place.** Redaction is a safety net, not the primary defense. Pass credentials by name rather than by value where the interface allows it — e.g. `docker run -e AWS_ACCESS_KEY_ID` inherits the value without printing it.
- **MUST register each encoding separately.** Because `redactSecrets()` matches exact substrings, a base64-encoded, URL-encoded, JSON-escaped, or truncated form of a registered secret passes through unredacted. Register every form the test might emit.
- **SHOULD NOT register short or common strings.** `redactSecrets()` replaces a registered value everywhere it occurs, so a short secret also mangles unrelated output and makes failures hard to read.
- **MUST NOT put secrets in error messages or test names.** `redactSecrets()` only runs on `MemoryStream` output; the test banner, the `[INTEG TEST::…] Failed:` line, and the GitHub Actions `::error title=…` line are written straight to `process.stderr` and are never scrubbed.
- **MUST use a value unique to the file** in unit tests that register secrets ([`test/corking.test.ts`](./packages/@aws-cdk-testing/cli-integ/test/corking.test.ts)). The registry is process-global with no deregistration, and the suite runs in randomized order.

## Code Style

### General Rules

- TypeScript strict mode
- 2-space indentation
- Single quotes
- Trailing commas (always-multiline)
- Max line length: 150 characters
- Imports ordered: builtins first, then external, alphabetized
- Use `type` imports for type-only imports (`import type { Foo } from ...`)
- No `console.log` — use the IoHost pattern for output
- No `require()` — use ES module imports
- No relative cross-package imports — use package names

### Error Handling

- Use `ToolkitError` from `@aws-cdk/toolkit-lib` for user-facing errors
- Constructor signature: `new ToolkitError(errorCode, message)` — the first argument is a PascalCase error code string, the second is the human-readable message
- Error messages: lowercase, no period, include the wrong value, explain what to change
- Never catch and swallow errors — CDK errors are unrecoverable
- Validate inputs eagerly — fail fast with clear messages
- Confirmation prompts: a declined confirmation must exit non-zero and fail soft. See [`packages/aws-cdk/docs/confirmation-prompts.md`](./packages/aws-cdk/docs/confirmation-prompts.md)

### AWS SDK Usage

- Always use AWS SDK v3 (`@aws-sdk/client-*`)
- SDK dependencies use `^3` version range (see `sdkDep()` in `.projenrc.ts`)
- Use `SdkProvider` for credential resolution — never instantiate SDK clients directly in business logic

## PR Conventions

### Titles (conventional commit format)

| Type | When | Example |
|------|------|---------|
| `feat(module):` | New feature | `feat(toolkit-lib): add drift detection` |
| `fix(module):` | Bug fix | `fix(aws-cdk): correct deploy timeout handling` |
| `docs(module):` | Documentation only | `docs(cli): update bootstrap docs` |
| `refactor(module):` | Feature-preserving refactor | `refactor(toolkit-lib): simplify deployment flow` |
| `chore(module):` | Build/config/minor | `chore(deps): update SDK dependencies` |
| `test(module):` | Test-only changes | `test(aws-cdk): add deploy edge case tests` |
| `revert(module):` | Revert a previous commit | `revert(toolkit-lib): undo breaking change` |

Valid scopes: `aws-cdk`, `bootstrap`, `cdk-assets`, `cdk-assets-lib`, `cli`, `cli-integ`, `cli-plugin-contract`, `cloud-assembly-api`, `cloud-assembly-schema`, `cloudformation-diff`, `deps`, `deps-dev`, `dev-deps`, `docs`, `integ-runner`, `integ-testing`, `toolkit-lib`, `yarn-cling`

- Scope is optional for repo-wide changes
- Lowercase, no period at end
- `feat` and `fix` PRs should reference an issue: `fixes #<issue>` or `closes #<issue>` (PR template includes this placeholder)
- One concern per PR — submit cosmetic changes separately

### PR Body

Must include the contributor statement:
> By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

### Checklist

- [ ] Unit tests added/updated
- [ ] Integration tests added/updated (if deploying new resource types or cross-service interactions)
- [ ] No manual edits to generated files

## Bundling

The `aws-cdk` and `cdk-assets` packages are bundled before publishing:

- All runtime dependencies are inlined into the bundle
- The published package has no runtime `dependencies` (they become `devDependencies`)
- `THIRD_PARTY_LICENSES` file tracks all bundled dependency licenses
- Allowed licenses: Apache-2.0, MIT, BSD-3-Clause, ISC, BSD-2-Clause, 0BSD, MIT OR Apache-2.0

If you change dependencies, regenerate attributions by running the build. If `THIRD_PARTY_LICENSES` is outdated, the build will fail.

## API Extractor (toolkit-lib)

`@aws-cdk/toolkit-lib` uses Microsoft API Extractor to manage its public API surface:

- Public API is defined by what's exported from `lib/index.ts`
- API reports track the public surface — changes require deliberate review
- Do not accidentally expose internal types through the public API
- The `exports` field in `package.json` restricts import paths

## Feature Flags

When changing observable CLI behavior:

- Gate behind a feature flag if existing users might depend on the old behavior
- Feature flags are consumed from `@aws-cdk/cx-api`
- New flags should default to the old behavior for existing apps

## Anti-Patterns — Things NOT To Do

- **MUST NOT manually edit generated files** — `parse-command-line-arguments.ts`, `user-input.ts`, `package.json`, `tsconfig.json`, workflow files, etc. Edit `.projenrc.ts` or source generators instead
- **MUST NOT add business logic to the CLI package** — it belongs in `toolkit-lib`
- **MUST NOT use `console.log`** — use the IoHost pattern
- **MUST NOT instantiate AWS SDK clients directly** — use `SdkProvider`
- **MUST NOT add cross-package relative imports** — use package names
- **MUST NOT leave `eslint-disable` directives, commented-out code, or dead code**
- **MUST NOT break backwards compatibility** without a feature flag or major version bump
- **MUST NOT add new runtime dependencies without checking license compatibility** (allowed: Apache-2.0, MIT, ISC, BSD-3-Clause, 0BSD)

## Key References

| File | What it covers |
|------|---------------|
| [`CONTRIBUTING.md`](./CONTRIBUTING.md) | Getting started, prerequisites, integration test process |
| [`packages/aws-cdk/CONTRIBUTING.md`](./packages/aws-cdk/CONTRIBUTING.md) | CLI-specific: command definitions, bundling, source maps |
| [`packages/@aws-cdk-testing/cli-integ/README.md`](./packages/@aws-cdk-testing/cli-integ/README.md) | Integration test suites, regression testing, patching |
| [`packages/aws-cdk/docs/confirmation-prompts.md`](./packages/aws-cdk/docs/confirmation-prompts.md) | How declined confirmation prompts must behave (exit code, soft fail) |
| [`.projenrc.ts`](./.projenrc.ts) | Monorepo configuration (all project settings) |

## Environment Setup

### Prerequisites

- Node.js >= 18.0.0 (Active LTS recommended)
- Yarn >= 4 (configured via `.yarnrc.yml`)
- Docker >= 19.03 (for init template tests)
- .NET SDK >= 6.0 (for C# init tests)

### First-Time Setup

```shell
git clone https://github.com/aws/aws-cdk-cli.git
cd aws-cdk-cli
yarn          # Install dependencies
yarn build    # Build all packages
```

### Running the CLI Locally

```shell
# After building
./packages/aws-cdk/bin/cdk --version
./packages/aws-cdk/bin/cdk synth
./packages/aws-cdk/bin/cdk deploy
```

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.