@go-to-k/cdkd 0.98.1 → 0.99.0

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
@@ -448,13 +448,15 @@ cdkd local start-api --port 3000 # pin the first server's port
448
448
  cdkd local start-api MyHttpApi # filter to one API (logical id, single-stack apps)
449
449
  cdkd local start-api MyStack/MyHttpApi # OR: CDK Construct path
450
450
  cdkd local start-api --warm --watch # pre-start + hot reload
451
+ cdkd local start-api --from-state # substitute deployed env vars in Lambda Environment
451
452
  ```
452
453
 
453
454
  One server per discovered API — authorizers, CORS configs, and stage
454
455
  variables stay scoped to the owning API. Supports REST v1 + HTTP API +
455
456
  Function URL with AWS_PROXY integrations; Lambda TOKEN / REQUEST,
456
457
  Cognito User Pool, and HTTP v2 JWT authorizers (JWKS-verified); CORS
457
- preflight; hot reload via `--watch`.
458
+ preflight; hot reload via `--watch`; deploy-state-backed env var
459
+ substitution via `--from-state`.
458
460
 
459
461
  ### `local run-task`
460
462
 
package/dist/cli.js CHANGED
@@ -4754,23 +4754,75 @@ var SQSQueuePolicyProvider = class {
4754
4754
  /**
4755
4755
  * Adopt an existing SQS queue policy into cdkd state.
4756
4756
  *
4757
- * **Explicit override only.** A `QueuePolicy` is an attachment applied to
4758
- * a queue via `SetQueueAttributes(Policy=...)`it has no standalone
4759
- * identity and is not independently taggable. There is no `aws:cdk:path`
4760
- * tag to look up by; only the queue itself is taggable.
4761
- *
4762
- * Users adopting an existing queue policy should pass
4763
- * `--resource <logicalId>=<queueUrl>` (matching the physical id format
4764
- * returned by `create()`, which uses the first queue URL).
4757
+ * The operational identifier for a `QueuePolicy` is the **queue URL**
4758
+ * (`https://sqs.<region>.amazonaws.com/<account>/<name>`) — every AWS
4759
+ * SDK call (`SetQueueAttributes` / `GetQueueAttributes`) takes a queue
4760
+ * URL via the `QueueUrl` parameter, and cdkd's `create()` records the
4761
+ * first `Queues` entry as the resource's `physicalId` so subsequent
4762
+ * `update()` / `delete()` / `readCurrentState()` calls hit the right
4763
+ * queue. A `QueuePolicy` has no standalone identity, no taggable ARN,
4764
+ * and no `aws:cdk:path` lookup only the parent queue is taggable.
4765
+ *
4766
+ * Resolution order (closes [#351](https://github.com/go-to-k/cdkd/issues/351)):
4767
+ *
4768
+ * 1. **`knownPhysicalId` if it is a valid queue URL.** Preserves the
4769
+ * `cdkd import --resource <logicalId>=<queueUrl>` path that has
4770
+ * always worked.
4771
+ * 2. **First entry of `properties.Queues` if it is a literal queue URL.**
4772
+ * Closes the `--migrate-from-cloudformation` case: AWS CloudFormation's
4773
+ * `DescribeStackResources` returns the CFn-generated policy NAME for
4774
+ * `AWS::SQS::QueuePolicy` (e.g. `MyStack-MyQueuePolicy-XXXXXXXXXX`),
4775
+ * which is NOT a valid `QueueUrl` and crashes the AWS SDK
4776
+ * `queueUrlMiddleware` with `TypeError: Invalid URL` the first time
4777
+ * cdkd touches it (typically `captureObservedForImportedResources` →
4778
+ * `readCurrentState` → `GetQueueAttributes`). The user can also
4779
+ * point `--migrate-from-cloudformation` at a stack whose QueuePolicy
4780
+ * is templated as `Queues: ['https://sqs...']` (rare but valid) —
4781
+ * that literal form falls into this branch.
4782
+ * 3. **Hard error** when neither path resolves a queue URL. This
4783
+ * covers (a) `--migrate-from-cloudformation` against a CFn stack
4784
+ * whose template carries `Queues: [{Ref: <MyQueue>}]` (the typical
4785
+ * CDK shape) when the referenced queue is NOT in the importable
4786
+ * set (or hasn't been imported yet in the current run), and (b)
4787
+ * explicit `--resource <logicalId>=<non-url>` typos. Pointing the
4788
+ * user at `--resource <logicalId>=<queueUrl>` is the recovery path
4789
+ * that always works.
4790
+ *
4791
+ * Intrinsic-valued `Queues[0]` (e.g. `{Ref: <MyQueue>}`) falls into
4792
+ * branch 3 here even when the referenced sibling has been imported in
4793
+ * the same run — `import()` is called BEFORE
4794
+ * `resolveImportedProperties` runs the synth template's Properties
4795
+ * through the intrinsic resolver, so the raw intrinsic object is what
4796
+ * we see. The recovery message names `--resource` as the explicit
4797
+ * escape hatch.
4765
4798
  */
4766
4799
  async import(input) {
4767
- if (input.knownPhysicalId) return {
4800
+ if (input.knownPhysicalId && isSqsQueueUrl(input.knownPhysicalId)) return {
4768
4801
  physicalId: input.knownPhysicalId,
4769
4802
  attributes: {}
4770
4803
  };
4771
- return null;
4804
+ const queues = input.properties["Queues"];
4805
+ if (Array.isArray(queues) && queues.length > 0) {
4806
+ const first = queues[0];
4807
+ if (typeof first === "string" && isSqsQueueUrl(first)) return {
4808
+ physicalId: first,
4809
+ attributes: {}
4810
+ };
4811
+ }
4812
+ const knownNote = input.knownPhysicalId ? ` Got knownPhysicalId='${input.knownPhysicalId}' (not a queue URL; CloudFormation returns the policy resource NAME for AWS::SQS::QueuePolicy, which is not the operational identifier).` : "";
4813
+ const queuesNote = Array.isArray(queues) && queues.length > 0 ? ` Properties.Queues[0]=${JSON.stringify(queues[0])} did not resolve to a literal queue URL (intrinsic-valued entries like {Ref: <Queue>} are not resolved at import time).` : " Properties.Queues is missing or empty.";
4814
+ throw new Error(`Cannot determine queue URL for ${input.resourceType} '${input.logicalId}'.${knownNote}${queuesNote} Re-run with --resource ${input.logicalId}=<queueUrl> (e.g. https://sqs.${input.region}.amazonaws.com/<account>/<queue-name>) to point cdkd at the queue this policy is attached to.`);
4772
4815
  }
4773
4816
  };
4817
+ /**
4818
+ * Recognize an SQS queue URL. AWS standard form is
4819
+ * `https://sqs.<region>.amazonaws.com/<account>/<name>`; FIFO queues end
4820
+ * in `.fifo`. Non-standard partitions (`amazonaws.com.cn` /
4821
+ * `c2s.ic.gov` / etc.) are accepted via the broader prefix check.
4822
+ */
4823
+ function isSqsQueueUrl(value) {
4824
+ return value.startsWith("https://sqs.") && value.includes("/");
4825
+ }
4774
4826
 
4775
4827
  //#endregion
4776
4828
  //#region src/provisioning/providers/sns-topic-provider.ts
@@ -38789,6 +38841,7 @@ async function localStartApiCommand(target, options) {
38789
38841
  const m = buildCorsConfigByApiId(stack.template);
38790
38842
  for (const [k, v] of m) corsConfigByApiId.set(k, v);
38791
38843
  }
38844
+ const stateByStack = options.fromState ? await loadStateForRoutedStacks(targetStacks, routes, routesWithAuth, options) : /* @__PURE__ */ new Map();
38792
38845
  const lambdaIds = uniqueLambdaIds(routes, routesWithAuth);
38793
38846
  const specs = /* @__PURE__ */ new Map();
38794
38847
  for (let i = 0; i < lambdaIds.length; i++) {
@@ -38802,7 +38855,8 @@ async function localStartApiCommand(target, options) {
38802
38855
  ...debugPortBase !== void 0 && { debugPort: debugPortBase + i },
38803
38856
  stsRegion: options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"],
38804
38857
  inlineTmpDirs,
38805
- layerTmpDirs
38858
+ layerTmpDirs,
38859
+ stateByStack
38806
38860
  });
38807
38861
  specs.set(logicalId, spec);
38808
38862
  }
@@ -39081,12 +39135,27 @@ function warnVpcConfigLambdas(routesWithAuth, stacks) {
39081
39135
  * missing, runtime not supported).
39082
39136
  */
39083
39137
  async function buildContainerSpec(args) {
39084
- const { logicalId, stacks, overrides, assumeRole, containerHost, debugPort, stsRegion, inlineTmpDirs, layerTmpDirs } = args;
39138
+ const { logicalId, stacks, overrides, assumeRole, containerHost, debugPort, stsRegion, inlineTmpDirs, layerTmpDirs, stateByStack } = args;
39085
39139
  const lambda = resolveLambdaByLogicalId(logicalId, stacks);
39086
39140
  const codeDir = lambda.codePath ?? materializeInlineCode$1(lambda.handler, lambda.inlineCode ?? "", resolveRuntimeFileExtension(lambda.runtime), inlineTmpDirs);
39087
39141
  const optDir = materializeLambdaLayers$1(lambda.layers, layerTmpDirs);
39088
- const envResult = resolveEnvVars(logicalId, getTemplateEnv$1(lambda.resource), overrides);
39089
- for (const key of envResult.unresolved) getLogger().warn(`Lambda ${logicalId}: env var ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${logicalId}":{"${key}":"<literal>"}}) to surface a literal value.`);
39142
+ let templateEnv = getTemplateEnv$1(lambda.resource);
39143
+ const stateBundle = stateByStack.get(lambda.stack.stackName);
39144
+ let stateAudit;
39145
+ if (stateBundle) {
39146
+ const context = { resources: stateBundle.state.resources };
39147
+ if (stateBundle.pseudoParameters) context.pseudoParameters = stateBundle.pseudoParameters;
39148
+ const { env, audit } = substituteEnvVarsFromState(templateEnv, context);
39149
+ templateEnv = env;
39150
+ stateAudit = audit;
39151
+ for (const key of audit.resolvedKeys) getLogger().debug(`Lambda ${logicalId}: --from-state substituted env var ${key}`);
39152
+ for (const { key, reason } of audit.unresolved) getLogger().warn(`Lambda ${logicalId}: --from-state could not substitute env var ${key} (${reason}). Override it via --env-vars or it will be dropped.`);
39153
+ }
39154
+ const envResult = resolveEnvVars(logicalId, templateEnv, overrides);
39155
+ for (const key of envResult.unresolved) {
39156
+ if (stateAudit && stateAudit.unresolved.some((u) => u.key === key)) continue;
39157
+ getLogger().warn(`Lambda ${logicalId}: env var ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${logicalId}":{"${key}":"<literal>"}}) or pass --from-state to recover deployed values.`);
39158
+ }
39090
39159
  const dockerEnv = {
39091
39160
  AWS_LAMBDA_FUNCTION_NAME: logicalId,
39092
39161
  AWS_LAMBDA_FUNCTION_MEMORY_SIZE: String(lambda.memoryMb),
@@ -39394,6 +39463,117 @@ async function reloadAllServers(args) {
39394
39463
  if (watcher) watcher.update([output, ...lastAssetPaths.value]);
39395
39464
  printPerServerRouteTables(servers);
39396
39465
  }
39466
+ /**
39467
+ * Returns true when any value in the function's template env map is a
39468
+ * CFn intrinsic (non-primitive). Used to gate the pseudo-parameter STS
39469
+ * hop inside the `--from-state` flow: literal-only env maps don't need
39470
+ * the pseudo-parameter bag and shouldn't pay for an STS call. Mirrors
39471
+ * the same gating in `local-invoke.ts` (`envHasIntrinsicValue`) and
39472
+ * `ecs-task-resolver.ts` (`containerHasIntrinsicEnvOrSecret`).
39473
+ */
39474
+ function envHasIntrinsicValue$1(templateEnv) {
39475
+ if (!templateEnv) return false;
39476
+ for (const v of Object.values(templateEnv)) {
39477
+ if (v === void 0 || v === null) continue;
39478
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") continue;
39479
+ return true;
39480
+ }
39481
+ return false;
39482
+ }
39483
+ /**
39484
+ * Load cdkd's S3 state for every stack that owns a routed Lambda. Once
39485
+ * per `synthesizeAndBuild` pass (initial boot + every reload), so a
39486
+ * Lambda's per-spec build does not pay one round-trip per Lambda. Per-
39487
+ * stack failures (no state, ambiguous region, bucket resolution error)
39488
+ * degrade to warn-and-fall-back via {@link loadStateForStack} — the
39489
+ * affected stack's reachable Lambdas behave as if `--from-state` were
39490
+ * not set, while sibling stacks with loadable state still substitute.
39491
+ *
39492
+ * Pseudo parameters are resolved per stack and only when at least one
39493
+ * reachable Lambda in that stack has an intrinsic-valued env entry
39494
+ * (gated via {@link envHasIntrinsicValue}). STS failures degrade to
39495
+ * warn and leave `pseudoParameters: undefined` — substitution still
39496
+ * runs for non-`AWS::*` refs.
39497
+ */
39498
+ async function loadStateForRoutedStacks(stacks, routes, routesWithAuth, options) {
39499
+ const logger = getLogger();
39500
+ const out = /* @__PURE__ */ new Map();
39501
+ const lambdaIds = uniqueLambdaIds(routes, routesWithAuth);
39502
+ const reachableStackNames = /* @__PURE__ */ new Set();
39503
+ for (const logicalId of lambdaIds) for (const stack of stacks) {
39504
+ const resource = stack.template.Resources?.[logicalId];
39505
+ if (resource && resource.Type === "AWS::Lambda::Function") {
39506
+ reachableStackNames.add(stack.stackName);
39507
+ break;
39508
+ }
39509
+ }
39510
+ const stackHasIntrinsicEnv = (stackName) => {
39511
+ for (const logicalId of lambdaIds) for (const stack of stacks) {
39512
+ if (stack.stackName !== stackName) continue;
39513
+ const resource = stack.template.Resources?.[logicalId];
39514
+ if (!resource || resource.Type !== "AWS::Lambda::Function") continue;
39515
+ if (envHasIntrinsicValue$1(getTemplateEnv$1(resource))) return true;
39516
+ }
39517
+ return false;
39518
+ };
39519
+ for (const stackName of reachableStackNames) {
39520
+ const stack = stacks.find((s) => s.stackName === stackName);
39521
+ if (!stack) continue;
39522
+ const loaded = await loadStateForStack(stack.stackName, stack.region, {
39523
+ ...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
39524
+ ...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
39525
+ statePrefix: options.statePrefix,
39526
+ ...options.region !== void 0 && { region: options.region },
39527
+ ...options.profile !== void 0 && { profile: options.profile }
39528
+ });
39529
+ if (!loaded) continue;
39530
+ const bundle = { state: loaded.state };
39531
+ if (stackHasIntrinsicEnv(stackName)) {
39532
+ const pseudo = await resolvePseudoParametersForStartApi(loaded.region, options);
39533
+ if (pseudo) bundle.pseudoParameters = pseudo;
39534
+ }
39535
+ out.set(stackName, bundle);
39536
+ logger.debug(`--from-state: loaded state for ${stackName} (${loaded.region})`);
39537
+ }
39538
+ return out;
39539
+ }
39540
+ /**
39541
+ * Build the AWS pseudo-parameter bag for `--from-state` env-var
39542
+ * substitution. Mirrors `resolvePseudoParametersForInvoke` in
39543
+ * `local-invoke.ts` byte-for-byte — kept inlined here rather than
39544
+ * extracted into a shared helper because the two call sites differ in
39545
+ * region precedence (this one is per-stack so the resolved state
39546
+ * region takes priority).
39547
+ *
39548
+ * Region precedence: `--region` > `AWS_REGION` > `AWS_DEFAULT_REGION` >
39549
+ * the state record's region (returned by `loadStateForStack`).
39550
+ */
39551
+ async function resolvePseudoParametersForStartApi(stateRegion, options) {
39552
+ const logger = getLogger();
39553
+ const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? stateRegion;
39554
+ let accountId;
39555
+ try {
39556
+ const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
39557
+ const sts = new STSClient({ ...region && { region } });
39558
+ try {
39559
+ accountId = (await sts.send(new GetCallerIdentityCommand({}))).Account;
39560
+ } finally {
39561
+ sts.destroy();
39562
+ }
39563
+ } catch (err) {
39564
+ logger.warn(`--from-state: resolver needs \${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. Substitution will be skipped for AWS::AccountId; affected env entries will be dropped with per-key warnings.`);
39565
+ }
39566
+ const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
39567
+ const bag = {
39568
+ ...accountId !== void 0 && { accountId },
39569
+ ...region !== void 0 && { region },
39570
+ ...partitionAndSuffix && {
39571
+ partition: partitionAndSuffix.partition,
39572
+ urlSuffix: partitionAndSuffix.urlSuffix
39573
+ }
39574
+ };
39575
+ return Object.keys(bag).length === 0 ? void 0 : bag;
39576
+ }
39397
39577
  /** Validate `--debug-port-base`. */
39398
39578
  function parseDebugPort(raw) {
39399
39579
  const parsed = parseInt(raw, 10);
@@ -39404,11 +39584,12 @@ function parseDebugPort(raw) {
39404
39584
  * Builder for the `start-api` subcommand. Wired up by `local.ts`.
39405
39585
  */
39406
39586
  function createLocalStartApiCommand() {
39407
- 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 and Cognito User Pool / HTTP v2 JWT authorizers; 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.")).action(withErrorHandling(localStartApiCommand));
39587
+ 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 and Cognito User Pool / HTTP v2 JWT authorizers; 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("--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("--stack-region <region>", "Region of the cdkd state record to read (used with --from-state when the same stack name has state in multiple regions).")).action(withErrorHandling(localStartApiCommand));
39408
39588
  [
39409
39589
  ...commonOptions,
39410
39590
  ...appOptions,
39411
- ...contextOptions
39591
+ ...contextOptions,
39592
+ ...stateOptions
39412
39593
  ].forEach((opt) => startApi.addOption(opt));
39413
39594
  startApi.addOption(deprecatedRegionOption);
39414
39595
  return startApi;
@@ -42243,7 +42424,7 @@ function reorderArgs(argv) {
42243
42424
  */
42244
42425
  async function main() {
42245
42426
  const program = new Command();
42246
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.98.0");
42427
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.98.2");
42247
42428
  program.addCommand(createBootstrapCommand());
42248
42429
  program.addCommand(createSynthCommand());
42249
42430
  program.addCommand(createListCommand());