@go-to-k/cdkd 0.158.1 → 0.159.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,23 +13,6 @@ Drop-in CDK CLI for existing CDK apps — faster deploys via AWS SDK instead of
13
13
  > [!IMPORTANT]
14
14
  > cdkd is for dev/test workflows only — early in development, not yet production-ready.
15
15
 
16
- ## Features
17
-
18
- - **Synthesis orchestration**: CDK app subprocess execution, Cloud Assembly parsing, context provider loop
19
- - **Asset handling**: Self-implemented asset publisher for S3 file assets (ZIP packaging) and Docker images (ECR)
20
- - **Context resolution**: Self-implemented context provider loop for Vpc.fromLookup(), AZ, SSM, HostedZone, etc.
21
- - **Hybrid provisioning**: SDK Providers for fast direct API calls, Cloud Control API fallback for broad resource coverage
22
- - **Diff calculation**: Self-implemented resource/property-level diff between desired template and current state
23
- - **S3-based state management**: No DynamoDB required, uses S3 conditional writes for locking
24
- - **DAG-based parallelization**: Analyze `Ref`/`Fn::GetAtt` dependencies and execute in parallel
25
- - **Rollback on failure**: When a deploy errors mid-stack, cdkd rolls back the resources it just created so the stack state stays consistent (CloudFormation parity — but cdkd does this without round-tripping through CFn). Pass `cdkd deploy --no-rollback` to skip rollback and keep the partial state for Terraform-style inspection / repair. See [Rollback behavior](#rollback-behavior).
26
- - **`--no-wait` for async resources**: Skip the multi-minute wait on CloudFront / RDS / ElastiCache / NAT Gateway and return as soon as the create call returns (CloudFormation always blocks)
27
- - **VPC route DependsOn relaxation (on by default)**: Drop CDK-injected defensive `DependsOn` edges from VPC Lambdas onto private-subnet routes so `CloudFront::Distribution` and `Lambda::Url` start their ~3-min propagation in parallel with NAT Gateway stabilization (~50% faster on VPC + Lambda + CloudFront stacks). Pass `--no-aggressive-vpc-parallel` to opt out.
28
- - **Local execution** (`cdkd local invoke` / `start-api` / `run-task` / `start-service`): run Lambdas, API Gateway routes, ECS tasks and long-running ECS services from your CDK code via Docker. All AWS Lambda runtimes, container Lambdas, REST v1 / HTTP v2 / Function URL routes, Service Connect / Cloud Map. Works for both `cdkd deploy`-managed (`--from-state`) AND `cdk deploy`-managed (`--from-cfn-stack`) stacks. See [Local execution](#local-execution).
29
- - **Bidirectional CloudFormation migration**: `cdkd import --migrate-from-cloudformation` adopts existing CFn stacks (including `cdk deploy`-managed) into cdkd state without re-creating resources; `cdkd export` hands a cdkd stack back to CloudFormation when production-ready. See [Importing](#importing-existing-resources) / [Exporting](#exporting-a-stack-back-to-cloudformation).
30
-
31
- > **Note**: Resource types not covered by either SDK Providers or Cloud Control API cannot be deployed with cdkd. Deployment fails with a clear error message naming the type + a 1-click issue link.
32
-
33
16
  ## Benchmark
34
17
 
35
18
  **cdkd deploys up to 15x faster than AWS CDK (CloudFormation)** on SDK-Provider-handled stacks; the per-stack speedup widens with size and parallelism, and drops to ~1.5-3x on stacks dominated by Cloud Control API fallback resources.
@@ -64,6 +47,23 @@ Stack: SSM Document × 3 + Athena WorkGroup × 2 (no SDK provider — CC API fal
64
47
 
65
48
  Reproduce the first two with `./tests/benchmark/run-benchmark.sh all`. See [tests/benchmark/README.md](tests/benchmark/README.md) for details.
66
49
 
50
+ ## Features
51
+
52
+ - **Synthesis orchestration**: CDK app subprocess execution, Cloud Assembly parsing, context provider loop
53
+ - **Asset handling**: Self-implemented asset publisher for S3 file assets (ZIP packaging) and Docker images (ECR)
54
+ - **Context resolution**: Self-implemented context provider loop for Vpc.fromLookup(), AZ, SSM, HostedZone, etc.
55
+ - **Hybrid provisioning**: SDK Providers for fast direct API calls, Cloud Control API fallback for broad resource coverage
56
+ - **Diff calculation**: Self-implemented resource/property-level diff between desired template and current state
57
+ - **S3-based state management**: No DynamoDB required, uses S3 conditional writes for locking
58
+ - **DAG-based parallelization**: Analyze `Ref`/`Fn::GetAtt` dependencies and execute in parallel
59
+ - **Rollback on failure**: When a deploy errors mid-stack, cdkd rolls back the resources it just created so the stack state stays consistent (CloudFormation parity — but cdkd does this without round-tripping through CFn). Pass `cdkd deploy --no-rollback` to skip rollback and keep the partial state for Terraform-style inspection / repair. See [Rollback behavior](#rollback-behavior).
60
+ - **`--no-wait` for async resources**: Skip the multi-minute wait on CloudFront / RDS / ElastiCache / NAT Gateway and return as soon as the create call returns (CloudFormation always blocks)
61
+ - **VPC route DependsOn relaxation (on by default)**: Drop CDK-injected defensive `DependsOn` edges from VPC Lambdas onto private-subnet routes so `CloudFront::Distribution` and `Lambda::Url` start their ~3-min propagation in parallel with NAT Gateway stabilization (~50% faster on VPC + Lambda + CloudFront stacks). Pass `--no-aggressive-vpc-parallel` to opt out.
62
+ - **Local execution** (`cdkd local invoke` / `start-api` / `run-task` / `start-service`): run Lambdas, API Gateway routes, ECS tasks and long-running ECS services from your CDK code via Docker. All AWS Lambda runtimes, container Lambdas, REST v1 / HTTP v2 / Function URL routes, Service Connect / Cloud Map. Works for both `cdkd deploy`-managed (`--from-state`) AND `cdk deploy`-managed (`--from-cfn-stack`) stacks. See [Local execution](#local-execution).
63
+ - **Bidirectional CloudFormation migration**: `cdkd import --migrate-from-cloudformation` adopts existing CFn stacks (including `cdk deploy`-managed) into cdkd state without re-creating resources; `cdkd export` hands a cdkd stack back to CloudFormation when production-ready. See [Importing](#importing-existing-resources) / [Exporting](#exporting-a-stack-back-to-cloudformation).
64
+
65
+ > **Note**: Resource types not covered by either SDK Providers or Cloud Control API cannot be deployed with cdkd. Deployment fails with a clear error message naming the type + a 1-click issue link.
66
+
67
67
  ## How it works
68
68
 
69
69
  ```
@@ -159,229 +159,57 @@ cdkd has three command families:
159
159
  use them to inspect / clean up state when the source is gone or
160
160
  you don't want to synth. `cdkd state destroy` is the CDK-app-free
161
161
  counterpart of `cdkd destroy`.
162
- - **`cdkd local ...` subcommands** (`local invoke`, `local start-api`,
163
- `local run-task`, `local start-service`) run synthesized workloads
164
- locally inside Docker containers. The Lambda variants (`local invoke` /
165
- `local start-api`) bundle the AWS Lambda Runtime Interface Emulator
166
- (RIE); `local invoke` runs a single Lambda once, and `local start-api`
167
- stands up a long-running HTTP server that maps API Gateway / HTTP API /
168
- Function URL routes to local Lambda invocations. `local run-task` is
169
- the ECS one-shot counterpart — it locates an
170
- `AWS::ECS::TaskDefinition` from the synthesized template and stands
171
- up every container in `dependsOn` order on a per-task docker network
172
- with the AWS-published metadata endpoints sidecar, so containers see
173
- `ECS_CONTAINER_METADATA_URI_V4` (and optionally task-role creds via
174
- `--assume-task-role`) just like they would on Fargate / ECS.
175
- `local start-service` is the long-running counterpart for
176
- `AWS::ECS::Service`: it discovers the service, chains into the same
177
- per-task machinery for each `DesiredCount` replica (clamped by
178
- `--max-tasks`), and keeps every replica running until `^C` — failed
179
- replicas restart per `--restart-policy on-failure|always|none`. It
180
- also accepts multiple service targets in one invocation
181
- (`cdkd local start-service Stack/Orders Stack/Frontend`); per-service
182
- `AWS::ServiceDiscovery::PrivateDnsNamespace` / `Service` +
183
- `ServiceConnectConfiguration` / `ServiceRegistries[]` blocks are
184
- parsed and each booted replica's container IP is published to a
185
- shared in-process Cloud Map registry, then injected into the next
186
- service's containers via docker `--add-host <fqdn>:<ip>` so
187
- `wget http://orders/` from inside the `frontend` container resolves
188
- via `/etc/hosts` to the orders replica (boot targets in
189
- producer-then-consumer order; see [docs/local-emulation.md](docs/local-emulation.md)
190
- for v1 limitations — first-wins for multi-replica routing, no SRV
191
- records, no Envoy L7). No AWS API calls beyond optional STS / Secrets
192
- resolution, no state bucket needed. Local load-balancer emulation and
193
- `--watch` hot-reload for `start-service` are deferred to follow-up
194
- PRs.
162
+ - **`cdkd local ...` subcommands** (`local invoke` / `start-api` /
163
+ `run-task` / `start-service`) run synthesized workloads locally
164
+ inside Docker containers no AWS deploy needed. Modeled on
165
+ `sam local *` but reads CDK state directly via `--from-state`
166
+ (cdkd-managed) or `--from-cfn-stack` (CFn-managed). See
167
+ [Local execution](#local-execution).
195
168
 
196
169
  Options like `--app`, `--state-bucket`, and `--context` can be omitted if configured via `cdk.json` or environment variables (`CDKD_APP`, `CDKD_STATE_BUCKET`).
197
170
 
198
171
  ```bash
199
- # Bootstrap (create S3 bucket for state)
200
- cdkd bootstrap \
201
- --state-bucket my-cdkd-state \
202
- --region us-east-1
203
-
204
- # Synthesize only
205
- cdkd synth --app "node app.ts"
206
-
207
- # List all stacks in the CDK app (alias: ls)
208
- cdkd list
209
- cdkd ls
210
- cdkd list --long # YAML records with id/name/environment
211
- cdkd list --long --json # same, but JSON
212
- cdkd list --show-dependencies # id + dependency list per stack
213
- cdkd list 'MyStage/*' # filter by display path (CDK CLI parity)
214
-
215
- # Deploy from a pre-synthesized cloud assembly directory
216
- cdkd deploy --app cdk.out
217
-
218
- # Deploy (single stack auto-detected, reads --app from cdk.json)
219
- cdkd deploy
220
-
221
- # Deploy specific stack(s)
222
- cdkd deploy MyStack
223
- cdkd deploy Stack1 Stack2
224
-
225
- # Deploy all stacks
172
+ # Synth + deploy
173
+ cdkd synth
174
+ cdkd deploy # single-stack auto-detected
175
+ cdkd deploy MyStack # by name (or 'MyStage/Api' display path)
226
176
  cdkd deploy --all
177
+ cdkd deploy --dry-run # plan only, no changes
178
+ cdkd deploy --no-rollback # Terraform-style: keep partial state on failure
179
+ cdkd deploy --no-wait # skip multi-minute waits (CloudFront / RDS / NAT)
227
180
 
228
- # Deploy with wildcard (matched against the physical CloudFormation stack name)
229
- cdkd deploy 'My*'
230
-
231
- # Deploy stacks under a CDK Stage using the hierarchical path (CDK CLI parity)
232
- # Patterns containing '/' are routed to the CDK display path; both forms work:
233
- cdkd deploy 'MyStage/*' # all stacks under MyStage
234
- cdkd deploy MyStage/Api # specific stack by display path
235
- cdkd deploy MyStage-Api # same stack by physical CloudFormation name
236
-
237
- # Deploy with context values
238
- cdkd deploy -c env=staging -c featureFlag=true
239
-
240
- # Deploy with explicit options
241
- cdkd deploy MyStack \
242
- --app "node app.ts" \
243
- --state-bucket my-cdkd-state \
244
- --verbose
245
-
246
- # Show diff (what would change)
181
+ # Inspect what would change
247
182
  cdkd diff MyStack
248
- cdkd diff MyParent --recursive # also diff every nested-stack child vs its own state (#555 A5)
249
- cdkd diff MyParent --recursive --json # nested {stack, changes, children: [...]} JSON
250
- cdkd diff MyStack --fail # exit 1 when any change is detected (CI gate; matches cdk diff --fail)
251
-
252
- # Detect drift between cdkd state and AWS reality (state-only; no synth)
253
- # Exits 0 with no drift, 1 when drift is detected, 2 on partial revert failure.
254
- cdkd drift MyStack
255
- cdkd drift --all --json
183
+ cdkd diff MyStack --fail # exit 1 on any change (CI gate)
256
184
 
257
- # Resolve drift: state AWS (catch up state with manual console changes)
258
- cdkd drift MyStack --accept --yes
185
+ # Drift detection — compare state vs AWS reality (no synth)
186
+ cdkd drift MyStack # exit 1 if drift
187
+ cdkd drift MyStack --accept --yes # state ← AWS
188
+ cdkd drift MyStack --revert --yes # AWS ← state
259
189
 
260
- # Resolve drift: AWS state (push state values back into AWS via provider.update)
261
- cdkd drift MyStack --revert --yes
262
-
263
- # Refresh the deploy-time AWS snapshot used as drift baseline.
264
- # Optional — `cdkd deploy` itself auto-refreshes on the first deploy after
265
- # upgrading from a pre-v3 cdkd binary (= state schema `version: 2`), in
266
- # parallel with the deploy at no critical-path cost. This command is the
267
- # manual / non-deploy path: run it when you want the baseline refreshed
268
- # without redeploying (e.g. for resources that won't change in any
269
- # near-future deploy). Idempotent on the same v3 state — see "Drift
270
- # detection" below for the full upgrade story.
271
- cdkd state refresh-observed MyStack
272
-
273
- # Dry run (plan only, no changes)
274
- cdkd deploy --dry-run
275
-
276
- # Deploy with no rollback on failure (Terraform-style)
277
- cdkd deploy --no-rollback
278
-
279
- # Deploy only the specified stack (skip dependency auto-inclusion)
280
- cdkd deploy -e MyStack
281
-
282
- # Skip the multi-minute wait on async resources (CloudFront, RDS, NAT GW, etc.)
283
- cdkd deploy --no-wait
284
-
285
- # Synth + build + publish assets only (no deploy) — typical CI split
286
- cdkd publish-assets
287
-
288
- # Destroy resources
190
+ # Asset / destroy / unlock
191
+ cdkd publish-assets # synth + upload only (typical CI split)
289
192
  cdkd destroy MyStack
290
- cdkd destroy --all --force
193
+ cdkd force-unlock MyStack # clear stale lock from an interrupted deploy
291
194
 
292
- # Force-unlock a stale lock from interrupted deploy
293
- cdkd force-unlock MyStack
294
-
295
- # Adopt already-deployed AWS resources into cdkd state.
296
- # See docs/import.md for the full guide (auto / selective / hybrid modes,
297
- # --resource overrides, --resource-mapping CDK CLI compatibility).
298
- cdkd import MyStack --dry-run
195
+ # Adopt existing AWS resources into cdkd state
299
196
  cdkd import MyStack --yes
300
197
 
301
- # Inspect state-bucket info on demand (bucket name, region, source, schema version, stack count).
302
- # Routine commands (deploy / destroy / etc.) no longer print the bucket banner by default
303
- # pass --verbose to surface it in their debug logs, or use this subcommand for an explicit answer.
304
- cdkd state info
305
- cdkd state info --json # JSON output for tooling
306
- cdkd state info --state-bucket my-bucket # explicit bucket; reports Source: --state-bucket flag
307
-
308
- # List stacks registered in the cdkd state bucket
309
- cdkd state list
310
- cdkd state ls --long # include resource count, last-modified, lock status
311
- cdkd state list --json # JSON output (alone, or combined with --long)
312
- cdkd state list --tree # parent → child stack tree (nested stacks; #555 A3)
313
- cdkd state list --tree --json # tree as nested JSON
314
-
315
- # List resources of a single stack from state
316
- cdkd state resources MyStack # aligned columns: LogicalID, Type, PhysicalID
317
- cdkd state resources MyStack --long # per-resource block with dependencies and attributes
318
- cdkd state resources MyStack --json # full JSON array
319
-
320
- # Show full state record for a stack (metadata, outputs, all resources incl. properties)
321
- cdkd state show MyStack
322
- cdkd state show MyStack --json # raw {state, lock} JSON
323
- cdkd state show MyParent --show-nested # recursively show every nested-stack child (#555 A4)
324
- cdkd state show MyParent --show-nested --json # tree as nested {state, lock, children: [...]} JSON
325
-
326
- # Orphan one or more RESOURCES from cdkd's state (does NOT delete AWS resources).
327
- # Per-resource, mirrors aws-cdk-cli's `cdk orphan --unstable=orphan`.
328
- # Synth-driven — needs --app / cdk.json. Construct paths use CDK's L2-style form
329
- # (`<StackName>/<Path/To/Construct>`); the synthesized `/Resource` suffix is
330
- # matched implicitly. Passing an L2 wrapper that contains multiple CFn resources
331
- # orphans every child under it (matches upstream's prefix-match semantics).
332
- cdkd orphan MyStack/MyTable # confirmation prompt (y/N)
333
- cdkd orphan MyStack/MyTable --yes
334
- cdkd orphan MyStack/MyTable MyStack/MyBucket # multiple resources, same stack
335
- cdkd orphan MyStack/MyTable --dry-run # print rewrite audit, no save
336
- cdkd orphan MyStack/MyTable --force # also fall back to cached
337
- # attributes when live fetch fails
338
-
339
- # State-driven counterpart that orphans a WHOLE STACK's state record
340
- # (no CDK app needed — works against the bucket).
341
- cdkd state orphan MyStack # confirmation prompt (y/N)
342
- cdkd state orphan MyStack --yes # skip confirmation
343
- cdkd state orphan StackA StackB --force # also bypass the locked-stack refusal
344
-
345
- # Destroy a stack's AWS resources AND remove its state record, without
346
- # requiring the CDK app (no synth — works from any working directory).
347
- cdkd state destroy MyStack # per-stack confirmation prompt
348
- cdkd state destroy MyStack OtherStack --yes
349
- cdkd state destroy --all -y # every stack in the bucket
350
- cdkd state destroy MyStack --region us-east-1
198
+ # State-bucket-only commands (no CDK app needed)
199
+ cdkd state info # bucket name, region, schema version
200
+ cdkd state list # one row per (stackName, region)
201
+ cdkd state list --tree # parent → child nested-stack tree
202
+ cdkd state show MyStack # full state record
203
+ cdkd state resources MyStack # logical id / type / physical id
204
+ cdkd state destroy MyStack # delete AWS resources + state, no CDK app
205
+ cdkd state orphan MyStack # remove state record only (AWS resources stay)
351
206
  ```
352
207
 
353
- ## Compatibility
354
-
355
- cdkd supports the standard CloudFormation surface — intrinsic functions,
356
- pseudo parameters, parameters / conditions, cross-stack / cross-region
357
- references, asset publishing, custom resources, and so on. See
358
- **[docs/supported-features.md](docs/supported-features.md)** for the
359
- full reference. For per-resource-type provisioning support (SDK Providers
360
- vs Cloud Control API fallback), see
361
- **[docs/supported-resources.md](docs/supported-resources.md)**.
362
-
363
- **Property-level coverage is incremental.** SDK Providers wire most but not every CFn property of a supported type. cdkd fails fast at pre-flight when a template uses a not-yet-implemented property, with the property name + a 1-click issue link. `--allow-unsupported-properties <Type>:<Prop>,...` is the safety valve when this is too strict (e.g. mid-life update on an existing resource); avoid it on security-meaningful properties (encryption / IAM / TLS). See [docs/cli-reference.md](docs/cli-reference.md#--allow-unsupported-properties-deploy).
364
-
365
- ## Rollback behavior
366
-
367
- When a deploy fails mid-stack (e.g. a resource hits a validation error
368
- or AWS rejects the request), cdkd by default **rolls back the
369
- already-completed resources in the same deploy** so the stack state
370
- stays consistent — every resource cdkd just created in this run is
371
- deleted in reverse dependency order, the state record is updated to
372
- match, and the CLI exits non-zero. Resources that existed before this
373
- deploy are NOT touched.
374
-
375
- Pass `cdkd deploy --no-rollback` to skip the rollback (Terraform-style:
376
- the partial state is preserved so you can `cdkd state show <stack>`,
377
- inspect what landed, fix the underlying issue, and re-run `cdkd deploy`
378
- to continue from the half-deployed state). Recommended only when you
379
- plan to manually inspect / repair; the default is safer for CI.
380
-
381
- Mid-deploy state is also saved per-resource as work completes, so even
382
- if cdkd itself crashes between the failure and the rollback, the state
383
- file accurately reflects what's on AWS and a follow-up `cdkd destroy`
384
- won't orphan anything.
208
+ See **[docs/cli-reference.md](docs/cli-reference.md)** for the full flag
209
+ matrix (`--concurrency`, `--no-aggressive-vpc-parallel`,
210
+ `--allow-unsupported-properties`, `--role-arn`, etc.), per-command details
211
+ including the synth-driven per-resource `cdkd orphan <constructPath>`
212
+ variant, and stage / wildcard pattern matching.
385
213
 
386
214
  ## `--no-wait`: skip async-resource waits
387
215
 
@@ -397,20 +225,6 @@ See [docs/cli-reference.md](docs/cli-reference.md#--no-wait-skip-async-resource-
397
225
  for per-resource caveats (NAT egress, RDS final-snapshot timing,
398
226
  etc.).
399
227
 
400
- ## VPC route DependsOn relaxation (on by default)
401
-
402
- CDK injects defensive `DependsOn` from VPC Lambdas onto private-subnet
403
- routes. The dependency is real at runtime but NOT required at deploy
404
- time. cdkd drops it by default so CloudFront + Lambda::Url propagation
405
- runs in parallel with NAT stabilization (~50% faster on VPC+Lambda+CloudFront
406
- stacks; bench-cdk-sample 398s → 181s). Pass
407
- `cdkd deploy --no-aggressive-vpc-parallel` to opt out (e.g. when a
408
- Custom Resource synchronously invokes a VPC Lambda outside cdkd's
409
- Lambda-ServiceToken Active wait).
410
-
411
- See [docs/cli-reference.md](docs/cli-reference.md) for the full
412
- type-pair allowlist and trade-off notes.
413
-
414
228
  ## Local execution
415
229
 
416
230
  The `cdkd local` family runs AWS workloads on the developer's machine
@@ -455,7 +269,8 @@ cdkd local start-api --from-state # OR --from-cfn-stack
455
269
  REST v1 + HTTP API v2 + Function URL with all integration kinds
456
270
  (AWS_PROXY / MOCK / HTTP_PROXY / HTTP / AWS Lambda non-proxy via
457
271
  hand-rolled VTL), authorizers (Lambda / Cognito / HTTP v2 JWT /
458
- REST v1 AWS_IAM SigV4), CORS, stage variables, `--watch` hot reload.
272
+ AWS_IAM SigV4 on REST v1 + Function URL), CORS, stage variables,
273
+ `--watch` hot reload.
459
274
 
460
275
  ### `local run-task`
461
276
 
@@ -484,6 +299,27 @@ full reference — runtimes, target resolution, every flag, integration
484
299
  and authorizer detail, route precedence, container pool, networking,
485
300
  `--from-cfn-stack` semantics, v1 scope.
486
301
 
302
+ ## Rollback behavior
303
+
304
+ When a deploy fails mid-stack (e.g. a resource hits a validation error
305
+ or AWS rejects the request), cdkd by default **rolls back the
306
+ already-completed resources in the same deploy** so the stack state
307
+ stays consistent — every resource cdkd just created in this run is
308
+ deleted in reverse dependency order, the state record is updated to
309
+ match, and the CLI exits non-zero. Resources that existed before this
310
+ deploy are NOT touched.
311
+
312
+ Pass `cdkd deploy --no-rollback` to skip the rollback (Terraform-style:
313
+ the partial state is preserved so you can `cdkd state show <stack>`,
314
+ inspect what landed, fix the underlying issue, and re-run `cdkd deploy`
315
+ to continue from the half-deployed state). Recommended only when you
316
+ plan to manually inspect / repair; the default is safer for CI.
317
+
318
+ Mid-deploy state is also saved per-resource as work completes, so even
319
+ if cdkd itself crashes between the failure and the rollback, the state
320
+ file accurately reflects what's on AWS and a follow-up `cdkd destroy`
321
+ won't orphan anything.
322
+
487
323
  ## Importing existing resources
488
324
 
489
325
  `cdkd import` adopts AWS resources that are already deployed (via
@@ -583,6 +419,20 @@ Two `orphan` variants at different granularities:
583
419
  Both `cdkd destroy` (synth-driven) and `cdkd state destroy`
584
420
  (state-driven, no synth) delete AWS resources + state.
585
421
 
422
+ ## VPC route DependsOn relaxation (on by default)
423
+
424
+ CDK injects defensive `DependsOn` from VPC Lambdas onto private-subnet
425
+ routes. The dependency is real at runtime but NOT required at deploy
426
+ time. cdkd drops it by default so CloudFront + Lambda::Url propagation
427
+ runs in parallel with NAT stabilization (~50% faster on VPC+Lambda+CloudFront
428
+ stacks; bench-cdk-sample 398s → 181s). Pass
429
+ `cdkd deploy --no-aggressive-vpc-parallel` to opt out (e.g. when a
430
+ Custom Resource synchronously invokes a VPC Lambda outside cdkd's
431
+ Lambda-ServiceToken Active wait).
432
+
433
+ See [docs/cli-reference.md](docs/cli-reference.md) for the full
434
+ type-pair allowlist and trade-off notes.
435
+
586
436
  ## `--remove-protection`: one-shot bypass for protected resources
587
437
 
588
438
  `cdkd destroy --remove-protection` (and `cdkd state destroy --remove-protection`)
@@ -632,6 +482,18 @@ cdkd publish-assets -a cdk.out # skip synth, use pre-synthesized assembly
632
482
  See [docs/cli-reference.md](docs/cli-reference.md#publish-assets-synth--build--publish-no-deploy)
633
483
  for stack-selection rules and concurrency knobs.
634
484
 
485
+ ## Compatibility
486
+
487
+ cdkd supports the standard CloudFormation surface — intrinsic functions,
488
+ pseudo parameters, parameters / conditions, cross-stack / cross-region
489
+ references, asset publishing, custom resources, and so on. See
490
+ **[docs/supported-features.md](docs/supported-features.md)** for the
491
+ full reference. For per-resource-type provisioning support (SDK Providers
492
+ vs Cloud Control API fallback), see
493
+ **[docs/supported-resources.md](docs/supported-resources.md)**.
494
+
495
+ **Property-level coverage is incremental.** SDK Providers wire most but not every CFn property of a supported type. cdkd fails fast at pre-flight when a template uses a not-yet-implemented property, with the property name + a 1-click issue link. `--allow-unsupported-properties <Type>:<Prop>,...` is the safety valve when this is too strict (e.g. mid-life update on an existing resource); avoid it on security-meaningful properties (encryption / IAM / TLS). See [docs/cli-reference.md](docs/cli-reference.md#--allow-unsupported-properties-deploy).
496
+
635
497
  ## State Management
636
498
 
637
499
  State is stored in S3 with optimistic locking via S3 Conditional Writes
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { _ as withSkipPrefix, a as runDockerStreaming, c as getLogger, d as getLiveRenderer, f as PATTERN_B_NAME_PROPERTIES, g as generateResourceNameWithFallback, h as generateResourceName, i as runDockerForeground, n as formatDockerLoginError, p as PATTERN_B_RESOURCE_TYPES, r as getDockerCmd, u as runStackBuffered, v as withStackName } from "./docker-cmd-iDMcWcre.js";
3
- import { $ as CdkdError, A as shouldRetainResource, B as resolveSkipPrefix, C as IntrinsicFunctionResolver, D as TemplateParser, E as DagBuilder, F as Synthesizer, G as CFN_TEMPLATE_URL_LIMIT, H as resolveStateBucketWithDefaultAndSource, I as getDefaultStateBucketName, J as uploadCfnTemplate, K as MIGRATE_TMP_PREFIX, L as getLegacyStateBucketName, M as stringifyValue, N as WorkGraph, O as LockManager, P as buildDockerImage, R as resolveApp, S as assertRegionMatch, T as DiffCalculator, U as warnDeprecatedNoPrefixCliFlag, V as resolveStateBucketWithDefault, W as CFN_TEMPLATE_BODY_LIMIT, Y as AssemblyReader, Z as resolveBucketRegion, _ as matchesCdkPath, a as withRetry, b as ProviderRegistry, bt as withErrorHandling, c as bold, ct as PartialFailureError, d as green, dt as ResourceUpdateNotSupportedError, f as red, ft as RouteDiscoveryError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, i as withResourceDeadline, it as LocalStartServiceError, j as AssetPublisher, k as S3StateBackend, l as cyan, lt as ProvisioningError, m as IAMRoleProvider, mt as StackTerminationProtectionError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as LocalInvokeBuildError, o as IMPLICIT_DELETE_DEPENDENCIES, ot as MissingCdkCliError, p as yellow, pt as StackHasActiveImportsError, q as findLargeInlineResources, r as DeployEngine, rt as LocalMigrateError, s as formatResourceLine, st as NestedStackChildDirectDestroyError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ResourceTimeoutError, v as normalizeAwsTagsToCfn, w as applyRoleArnIfSet, x as CloudControlProvider, y as resolveExplicitPhysicalId, yt as normalizeAwsError, z as resolveCaptureObservedState } from "./deploy-engine-CGmdz5WP.js";
3
+ import { $ as CdkdError, A as shouldRetainResource, B as resolveSkipPrefix, C as IntrinsicFunctionResolver, D as TemplateParser, E as DagBuilder, F as Synthesizer, G as CFN_TEMPLATE_URL_LIMIT, H as resolveStateBucketWithDefaultAndSource, I as getDefaultStateBucketName, J as uploadCfnTemplate, K as MIGRATE_TMP_PREFIX, L as getLegacyStateBucketName, M as stringifyValue, N as WorkGraph, O as LockManager, P as buildDockerImage, R as resolveApp, S as assertRegionMatch, T as DiffCalculator, U as warnDeprecatedNoPrefixCliFlag, V as resolveStateBucketWithDefault, W as CFN_TEMPLATE_BODY_LIMIT, Y as AssemblyReader, Z as resolveBucketRegion, _ as matchesCdkPath, a as withRetry, b as ProviderRegistry, bt as withErrorHandling, c as bold, ct as PartialFailureError, d as green, dt as ResourceUpdateNotSupportedError, f as red, ft as RouteDiscoveryError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, i as withResourceDeadline, it as LocalStartServiceError, j as AssetPublisher, k as S3StateBackend, l as cyan, lt as ProvisioningError, m as IAMRoleProvider, mt as StackTerminationProtectionError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as LocalInvokeBuildError, o as IMPLICIT_DELETE_DEPENDENCIES, ot as MissingCdkCliError, p as yellow, pt as StackHasActiveImportsError, q as findLargeInlineResources, r as DeployEngine, rt as LocalMigrateError, s as formatResourceLine, st as NestedStackChildDirectDestroyError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ResourceTimeoutError, v as normalizeAwsTagsToCfn, w as applyRoleArnIfSet, x as CloudControlProvider, y as resolveExplicitPhysicalId, yt as normalizeAwsError, z as resolveCaptureObservedState } from "./deploy-engine-BzrECC3i.js";
4
4
  import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-B15NAPbL.js";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { createHash, createHmac, createPublicKey, createVerify, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -46754,9 +46754,14 @@ function classifyServiceIntegrationRoute(baseRoute, integrationProps, stackName,
46754
46754
  * JSON prelude — `{statusCode, headers, cookies?}` — followed by 8
46755
46755
  * NULL bytes and then the raw body chunks). The HTTP server pipes
46756
46756
  * the chunks to the client with `Transfer-Encoding: chunked` (#467).
46757
- * - `AuthType !== 'NONE'` (e.g. `AWS_IAM`) → deferred-error
46758
- * unsupported. Boot proceeds; HTTP 501 + `reason` at request time.
46759
- * IAM auth would need SigV4 verification cdkd cannot emulate.
46757
+ * - `AuthType === 'AWS_IAM'` → normal route. The `IamAuthorizer` is
46758
+ * attached at the authorizer-resolver pass (`detectAuthorizer` in
46759
+ * `authorizer-resolver.ts`) so the HTTP server runs the same SigV4
46760
+ * verification it ships for REST v1 `AuthorizationType: 'AWS_IAM'`
46761
+ * (PR #447). Signature verification only — no IAM policy emulation.
46762
+ * - `AuthType` other than `'NONE'` / `'AWS_IAM'` (defensive — AWS docs
46763
+ * only define those two values) → deferred-error unsupported. Boot
46764
+ * proceeds; HTTP 501 + `reason` at request time.
46760
46765
  *
46761
46766
  * The Lambda Arn intrinsic resolution still **hard-errors** when it
46762
46767
  * cannot pin down a same-template Lambda — Function URLs have no other
@@ -46781,10 +46786,10 @@ function discoverFunctionUrl(logicalId, resource, template, stackName) {
46781
46786
  declaredAt: `${stackName}/${logicalId}`
46782
46787
  };
46783
46788
  const authType = props["AuthType"];
46784
- if (authType !== "NONE") return [{
46789
+ if (authType !== "NONE" && authType !== "AWS_IAM") return [{
46785
46790
  ...baseRoute,
46786
46791
  lambdaLogicalId,
46787
- unsupported: { reason: `${stackName}/${logicalId}: AuthType '${String(authType)}' is not supported (only NONE IAM auth requires SigV4 verification cdkd cannot emulate locally).` }
46792
+ unsupported: { reason: `${stackName}/${logicalId}: AuthType ${shortJson$1(authType)} is not a recognized Function URL auth type (expected 'NONE' or 'AWS_IAM').` }
46788
46793
  }];
46789
46794
  const invokeModeRaw = props["InvokeMode"];
46790
46795
  let invokeMode = "BUFFERED";
@@ -51087,6 +51092,28 @@ function detectAuthorizer(route, stack) {
51087
51092
  if (!resource) return void 0;
51088
51093
  if (resource.Type === "AWS::ApiGateway::Method") return detectRestV1Authorizer(resource, logicalId, stack);
51089
51094
  if (resource.Type === "AWS::ApiGatewayV2::Route") return detectHttpApiAuthorizer(resource, logicalId, stack);
51095
+ if (resource.Type === "AWS::Lambda::Url") return detectFunctionUrlAuthorizer(resource, logicalId, stack);
51096
+ }
51097
+ /**
51098
+ * Function URL (`AWS::Lambda::Url`) authorizer detection (issue #621).
51099
+ *
51100
+ * `AuthType: 'AWS_IAM'` uses the same SigV4 mechanism REST v1 ships
51101
+ * (PR #447), so we route through the same `IamAuthorizer` descriptor and
51102
+ * let the HTTP server's existing `if (authorizer.kind === 'iam')`
51103
+ * request-time branch run `verifySigV4`. Like REST v1 AWS_IAM, no IAM
51104
+ * policy emulation — signature verification only.
51105
+ *
51106
+ * `AuthType: 'NONE'` (and any non-AWS_IAM AuthType that
51107
+ * `route-discovery.ts` already flipped to `unsupported`) returns
51108
+ * `undefined` so the route runs without an authorizer pass.
51109
+ */
51110
+ function detectFunctionUrlAuthorizer(urlResource, urlLogicalId, stack) {
51111
+ if ((urlResource.Properties ?? {})["AuthType"] !== "AWS_IAM") return void 0;
51112
+ return {
51113
+ kind: "iam",
51114
+ logicalId: "AWS_IAM",
51115
+ declaredAt: `${stack.stackName}/${urlLogicalId}`
51116
+ };
51090
51117
  }
51091
51118
  function detectRestV1Authorizer(methodResource, methodLogicalId, stack) {
51092
51119
  const props = methodResource.Properties ?? {};
@@ -52548,15 +52575,15 @@ async function handleRequest(req, res, state, opts) {
52548
52575
  outcome = await runAuthorizerPass(authorizer, snapshot, matchCtx, state, opts, baseEvent["requestContext"]);
52549
52576
  } catch (err) {
52550
52577
  logger.error(`Authorizer ${authorizer.logicalId} threw for ${match.route.declaredAt}: ${err instanceof Error ? err.message : String(err)}`);
52551
- writeAuthRejection(res, match.route.apiVersion, "policy-deny");
52578
+ writeAuthRejection(res, match.route.apiVersion, "policy-deny", authorizer.kind);
52552
52579
  return;
52553
52580
  }
52554
52581
  if (!outcome.result.allow) {
52555
- writeAuthRejection(res, match.route.apiVersion, outcome.denyKind ?? "policy-deny");
52582
+ writeAuthRejection(res, match.route.apiVersion, outcome.denyKind ?? "policy-deny", authorizer.kind);
52556
52583
  return;
52557
52584
  }
52558
52585
  authResult = outcome.result;
52559
- const overlay = buildOverlay(authorizer, authResult);
52586
+ const overlay = buildOverlay(authorizer, authResult, match.route.apiVersion);
52560
52587
  if (overlay) baseEvent = applyAuthorizerOverlay(baseEvent, overlay);
52561
52588
  }
52562
52589
  if (match.route.serviceIntegration) {
@@ -52967,7 +52994,7 @@ function pickSourceIp(apiVersion, requestContext, snapshot) {
52967
52994
  }
52968
52995
  return snapshot.sourceIp ?? "127.0.0.1";
52969
52996
  }
52970
- function buildOverlay(authorizer, result) {
52997
+ function buildOverlay(authorizer, result, routeApiVersion) {
52971
52998
  if (authorizer.kind === "lambda-token" || authorizer.kind === "lambda-request") return authorizer.kind === "lambda-request" && authorizer.apiVersion === "v2" ? {
52972
52999
  kind: "lambda-http-v2",
52973
53000
  ...result.principalId !== void 0 && { principalId: result.principalId },
@@ -52981,10 +53008,13 @@ function buildOverlay(authorizer, result) {
52981
53008
  kind: "cognito-rest-v1",
52982
53009
  claims: result.context ?? {}
52983
53010
  };
52984
- if (authorizer.kind === "iam") return {
52985
- kind: "lambda-rest-v1",
52986
- ...result.principalId !== void 0 && { principalId: result.principalId }
52987
- };
53011
+ if (authorizer.kind === "iam") {
53012
+ if (routeApiVersion === "v2") return void 0;
53013
+ return {
53014
+ kind: "lambda-rest-v1",
53015
+ ...result.principalId !== void 0 && { principalId: result.principalId }
53016
+ };
53017
+ }
52988
53018
  return {
52989
53019
  kind: "jwt-http-v2",
52990
53020
  claims: result.context ?? {}
@@ -52992,6 +53022,11 @@ function buildOverlay(authorizer, result) {
52992
53022
  }
52993
53023
  /**
52994
53024
  * Map the authorizer rejection to an HTTP status code and body.
53025
+ * - REST v1 with AWS_IAM (issue #625) → 403 for both deny kinds (the
53026
+ * deployed REST v1 SigV4 layer rejects unsigned requests with 403
53027
+ * `{"message":"Missing Authentication Token"}` and signature/policy
53028
+ * failures with 403 `{"message":"Forbidden"}` — lowercase `message`
53029
+ * distinguishes the REST v1 shape from the Function URL shape below).
52995
53030
  * - REST v1, missing identity → 401 `{"message":"Unauthorized"}`
52996
53031
  * (matches deployed behavior; the route reaches the Method but no
52997
53032
  * identity source is present so the authorizer never runs).
@@ -52999,8 +53034,25 @@ function buildOverlay(authorizer, result) {
52999
53034
  * authorizer ran and denied; status mirrors AWS API Gateway).
53000
53035
  * - HTTP v2, both kinds → 401 `{"message":"Unauthorized"}` (HTTP API
53001
53036
  * collapses both into the same response).
53002
- */
53003
- function writeAuthRejection(res, apiVersion, denyKind) {
53037
+ * - Function URL with AWS_IAM (issue #621) → 403 `{"Message":"Forbidden"}`
53038
+ * for both deny kinds (matches Lambda's deployed Function URL IAM
53039
+ * behavior — the AWS SigV4 layer rejects with 403, not 401). Note the
53040
+ * capital `Message` — distinct from REST v1 AWS_IAM's lowercase
53041
+ * `message`.
53042
+ */
53043
+ function writeAuthRejection(res, apiVersion, denyKind, authorizerKind) {
53044
+ if (apiVersion === "v1" && authorizerKind === "iam") {
53045
+ if (denyKind === "missing-identity") {
53046
+ writeError(res, 403, "{\"message\":\"Missing Authentication Token\"}");
53047
+ return;
53048
+ }
53049
+ writeError(res, 403, "{\"message\":\"Forbidden\"}");
53050
+ return;
53051
+ }
53052
+ if (apiVersion === "v2" && authorizerKind === "iam") {
53053
+ writeError(res, 403, "{\"Message\":\"Forbidden\"}");
53054
+ return;
53055
+ }
53004
53056
  if (apiVersion === "v2") {
53005
53057
  writeError(res, 401, "{\"message\":\"Unauthorized\"}");
53006
53058
  return;
@@ -55097,7 +55149,7 @@ function resolveMtlsConfig(options) {
55097
55149
  * Builder for the `start-api` subcommand. Wired up by `local.ts`.
55098
55150
  */
55099
55151
  function createLocalStartApiCommand() {
55100
- const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and REST v1 AWS_IAM (SigV4 signature verification only IAM policy evaluation is NOT emulated; see docs/local-emulation.md). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.").argument("[target]", "Optional API filter. Accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted, every discovered API gets its own server. Mirrors `cdkd local invoke` / `cdkd local run-task` target syntax.").addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when cdk.out/ or asset directories change. Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <target> argument instead. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--from-state", "Read cdkd S3 state for every routed stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::Join (and AWS pseudo parameters) in Lambda env vars with the deployed physical IDs / attributes. Off by default — pre-PR warn-and-drop semantics are preserved. Turn on for stacks already deployed via cdkd deploy. Mirrors `cdkd local invoke --from-state` / `cdkd local run-task --from-state`. Re-runs against fresh state on every hot-reload firing (--watch).").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in Lambda env vars with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the cdkd stack name per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. Mutually exclusive with --from-state. Fn::GetAtt is warn-and-dropped in v1 (CFn DescribeStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-state when the same stack name has state in multiple regions, and with --from-cfn-stack as the CFn client region (cdkd does not have a separate --cfn-stack-region flag).")).addOption(new Option("--mtls-truststore <path>", "PEM-encoded CA bundle for client-certificate verification (mutual TLS). When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced on the Lambda event under requestContext.identity.clientCert (REST v1) / requestContext.authentication.clientCert (HTTP API v2). Must be set together with --mtls-cert + --mtls-key; partial flag sets are rejected. Generate a CA + server + client cert for local dev: openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj \"/CN=cdkd-local-ca\" -days 365; openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj \"/CN=localhost\"; openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj \"/CN=client\"; openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...")).addOption(new Option("--mtls-cert <path>", "PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. Must be set together with --mtls-truststore + --mtls-key.")).addOption(new Option("--mtls-key <path>", "PEM-encoded server private key matching --mtls-cert. Must be set together with --mtls-truststore + --mtls-cert.")).addOption(new Option("--allow-unverified-sigv4", "Opt-in: allow AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id, OR no local AWS credentials configured) to pass through with a placeholder principalId. DEFAULT off — fail-closed so unauthenticated bypass is impossible against `event.requestContext.identity.accessKey`-trusting handler code. Use only in dev loops where you understand the risk.").default(false)).action(withErrorHandling(localStartApiCommand));
55152
+ const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and AWS_IAM auth (REST v1 `AuthorizationType: AWS_IAM` and Function URL `AuthType: AWS_IAM` — SigV4 signature verification only; IAM policy evaluation is NOT emulated; see docs/local-emulation.md). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.").argument("[target]", "Optional API filter. Accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted, every discovered API gets its own server. Mirrors `cdkd local invoke` / `cdkd local run-task` target syntax.").addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when cdk.out/ or asset directories change. Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <target> argument instead. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--from-state", "Read cdkd S3 state for every routed stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::Join (and AWS pseudo parameters) in Lambda env vars with the deployed physical IDs / attributes. Off by default — pre-PR warn-and-drop semantics are preserved. Turn on for stacks already deployed via cdkd deploy. Mirrors `cdkd local invoke --from-state` / `cdkd local run-task --from-state`. Re-runs against fresh state on every hot-reload firing (--watch).").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in Lambda env vars with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the cdkd stack name per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. Mutually exclusive with --from-state. Fn::GetAtt is warn-and-dropped in v1 (CFn DescribeStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-state when the same stack name has state in multiple regions, and with --from-cfn-stack as the CFn client region (cdkd does not have a separate --cfn-stack-region flag).")).addOption(new Option("--mtls-truststore <path>", "PEM-encoded CA bundle for client-certificate verification (mutual TLS). When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced on the Lambda event under requestContext.identity.clientCert (REST v1) / requestContext.authentication.clientCert (HTTP API v2). Must be set together with --mtls-cert + --mtls-key; partial flag sets are rejected. Generate a CA + server + client cert for local dev: openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj \"/CN=cdkd-local-ca\" -days 365; openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj \"/CN=localhost\"; openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj \"/CN=client\"; openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...")).addOption(new Option("--mtls-cert <path>", "PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. Must be set together with --mtls-truststore + --mtls-key.")).addOption(new Option("--mtls-key <path>", "PEM-encoded server private key matching --mtls-cert. Must be set together with --mtls-truststore + --mtls-cert.")).addOption(new Option("--allow-unverified-sigv4", "Opt-in: allow AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id, OR no local AWS credentials configured) to pass through with a placeholder principalId. DEFAULT off — fail-closed so unauthenticated bypass is impossible against `event.requestContext.identity.accessKey`-trusting handler code. Use only in dev loops where you understand the risk.").default(false)).action(withErrorHandling(localStartApiCommand));
55101
55153
  [
55102
55154
  ...commonOptions,
55103
55155
  ...appOptions,
@@ -59436,7 +59488,7 @@ function reorderArgs(argv) {
59436
59488
  */
59437
59489
  async function main() {
59438
59490
  const program = new Command();
59439
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.158.1");
59491
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.159.1");
59440
59492
  program.addCommand(createBootstrapCommand());
59441
59493
  program.addCommand(createSynthCommand());
59442
59494
  program.addCommand(createListCommand());