@go-to-k/cdkd 0.168.0 → 0.169.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
@@ -190,10 +190,12 @@ cdkd drift MyStack --revert --yes # AWS ← state
190
190
  # Asset / destroy / unlock
191
191
  cdkd publish-assets # synth + upload only (typical CI split)
192
192
  cdkd destroy MyStack
193
+ cdkd orphan MyStack/MyBucket # drop one resource from state (AWS resource stays)
193
194
  cdkd force-unlock MyStack # clear stale lock from an interrupted deploy
194
195
 
195
- # Adopt existing AWS resources into cdkd state
196
- cdkd import MyStack --yes
196
+ # Migrate between cdkd and CloudFormation
197
+ cdkd import MyStack --yes # adopt existing AWS resources into cdkd state
198
+ cdkd export MyStack # hand a cdkd-managed stack back to CloudFormation
197
199
 
198
200
  # State-bucket-only commands (no CDK app needed)
199
201
  cdkd state info # bucket name, region, schema version
@@ -264,6 +266,11 @@ bind-mounted at `/opt`.
264
266
  cdkd local start-api # one HTTP server per discovered API
265
267
  cdkd local start-api MyStack/MyHttpApi --watch # filter + hot reload
266
268
  cdkd local start-api --from-state # OR --from-cfn-stack
269
+
270
+ # Typical shape — the bare `--from-cfn-stack` flag auto-resolves to the
271
+ # routed stack's name (here `MyStack`). Pass an explicit value only when
272
+ # the deployed CFn stack name differs from the CDK stack name.
273
+ cdkd local start-api MyStack/MyHttpApi --from-cfn-stack
267
274
  ```
268
275
 
269
276
  REST v1 + HTTP API v2 + Function URL with all integration kinds
package/dist/cli.js CHANGED
@@ -36475,6 +36475,21 @@ function readCdkPath(resource) {
36475
36475
  return typeof v === "string" ? v : "";
36476
36476
  }
36477
36477
  /**
36478
+ * Same lookup as `readCdkPath`, but returns `undefined` instead of an
36479
+ * empty string when no `aws:cdk:path` metadata is present.
36480
+ *
36481
+ * Use this variant when the caller passes the value into APIs whose
36482
+ * contract distinguishes "no path known" from "empty path". For example,
36483
+ * `resolveEnvVars(logicalId, displayPath, ...)` short-circuits the
36484
+ * display-path lookup when `displayPath` is `undefined`, but an empty
36485
+ * string `''` would still hit the loop and could spuriously match a
36486
+ * malformed override key.
36487
+ */
36488
+ function readCdkPathOrUndefined(resource) {
36489
+ const path = readCdkPath(resource);
36490
+ return path === "" ? void 0 : path;
36491
+ }
36492
+ /**
36478
36493
  * Build a `Map<cdkPath, logicalId>` from a synthesized template.
36479
36494
  *
36480
36495
  * Used by `cdkd orphan <constructPath>` to translate user-supplied
@@ -43668,21 +43683,37 @@ function looksLikeAccessDenied(err) {
43668
43683
  * @param logicalId The function's CloudFormation logical ID. Used
43669
43684
  * to look up function-specific overrides in the
43670
43685
  * `--env-vars` file.
43686
+ * @param displayPath The function's CDK display path
43687
+ * (`Metadata['aws:cdk:path']`, e.g.
43688
+ * `"MyStack/MyHandler"`), or `undefined` when
43689
+ * the resource has no path metadata. Display-path
43690
+ * keys in the override file are matched against
43691
+ * this value in addition to `logicalId`. Pass
43692
+ * `undefined` rather than the logical ID when no
43693
+ * path is known, so the override lookup does not
43694
+ * accidentally double-match the same key.
43671
43695
  * @param templateEnv The function's `Properties.Environment.Variables`
43672
43696
  * object from the synthesized template, or
43673
43697
  * `undefined` when the function has no env vars.
43674
43698
  * @param overrides Parsed `--env-vars` file contents, or
43675
43699
  * `undefined` when the flag was not passed.
43676
43700
  */
43677
- function resolveEnvVars(logicalId, templateEnv, overrides) {
43701
+ function resolveEnvVars(logicalId, displayPath, templateEnv, overrides) {
43678
43702
  const resolved = {};
43679
43703
  const unresolved = [];
43680
43704
  if (templateEnv) for (const [key, value] of Object.entries(templateEnv)) if (isLiteralEnvValue(value)) resolved[key] = String(value);
43681
43705
  else unresolved.push(key);
43682
43706
  if (overrides) {
43683
43707
  applyOverrideMap$1(resolved, overrides.Parameters);
43684
- const fnOverrides = overrides[logicalId];
43685
- if (fnOverrides && typeof fnOverrides === "object") applyOverrideMap$1(resolved, fnOverrides);
43708
+ for (const [key, val] of Object.entries(overrides)) {
43709
+ if (key === "Parameters") continue;
43710
+ if (!val || typeof val !== "object") continue;
43711
+ if (key === logicalId) {
43712
+ applyOverrideMap$1(resolved, val);
43713
+ continue;
43714
+ }
43715
+ if (displayPath && (displayPath === key || displayPath.startsWith(`${key}/`))) applyOverrideMap$1(resolved, val);
43716
+ }
43686
43717
  }
43687
43718
  return {
43688
43719
  resolved,
@@ -55108,6 +55139,7 @@ async function localStartApiCommand(target, options) {
55108
55139
  const jwksWarnedUrls = /* @__PURE__ */ new Set();
55109
55140
  let sigV4CredentialsLoader;
55110
55141
  const sigV4WarnedForeignIds = /* @__PURE__ */ new Set();
55142
+ const fromCfnTipEmitted = { value: false };
55111
55143
  /**
55112
55144
  * One synth + discover + build pass. Returns the next-state
55113
55145
  * material. Reused on initial boot AND every hot-reload firing.
@@ -55133,8 +55165,14 @@ async function localStartApiCommand(target, options) {
55133
55165
  ...options.profile && { macroExpandS3ClientOpts: { profile: options.profile } }
55134
55166
  };
55135
55167
  const { stacks } = await synthesizer.synthesize(synthOpts);
55136
- const targetStacks = pickTargetStacks(stacks, options.stack);
55137
- if (targetStacks.length === 0) throw new Error("No stacks matched. Pass --stack <name> or run from a single-stack app.");
55168
+ const cfnStackFallback = typeof options.fromCfnStack === "string" ? options.fromCfnStack : void 0;
55169
+ const targetStackPrefix = target?.includes("/") === true ? target.slice(0, target.indexOf("/")) : void 0;
55170
+ const targetStacks = pickTargetStacks(stacks, options.stack, cfnStackFallback, targetStackPrefix);
55171
+ if (targetStacks.length === 0) throw new Error("No stacks matched. Pass --stack <name> (or --from-cfn-stack <name>) or run from a single-stack app.");
55172
+ const routedStackNames = targetStacks.map((s) => s.stackName);
55173
+ tryEmitFromCfnRedundancyTipOnce(options.fromCfnStack, routedStackNames, fromCfnTipEmitted, (routedStackName) => {
55174
+ logger.info(`tip: --from-cfn-stack value matches the routed stack name (${routedStackName}); you can omit the value: \`cdkd local start-api ... --from-cfn-stack\` (bare flag) resolves to the same value.`);
55175
+ });
55138
55176
  const routes = discoverRoutes(targetStacks);
55139
55177
  const wsDiscovery = discoverWebSocketApis(targetStacks);
55140
55178
  if (wsDiscovery.errors.length > 0) for (const e of wsDiscovery.errors) logger.warn(`WebSocket discovery: ${e}`);
@@ -55509,12 +55547,60 @@ async function localStartApiCommand(target, options) {
55509
55547
  * Match the `--stack` pattern (or single-stack auto-detect) to a list
55510
55548
  * of stacks the route-discovery walks. Mirrors the deploy/diff matcher
55511
55549
  * routing rules.
55550
+ *
55551
+ * @internal exported for unit tests.
55512
55552
  */
55513
- function pickTargetStacks(stacks, pattern) {
55514
- if (pattern) return matchStacks(stacks, [pattern]);
55553
+ function pickTargetStacks(stacks, pattern, cfnStackFallback, targetFallback) {
55554
+ const effective = pattern ?? cfnStackFallback ?? targetFallback;
55555
+ if (effective) return matchStacks(stacks, [effective]);
55515
55556
  if (stacks.length === 1) return stacks;
55516
55557
  if (stacks.length === 0) return [];
55517
- throw new Error(`Multi-stack app: pass --stack <name> to pick a target. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
55558
+ throw new Error(`Multi-stack app: pass --stack <name>, --from-cfn-stack <name>, or a stack-qualified target like "<StackName>/<construct>" to pick a target. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
55559
+ }
55560
+ /**
55561
+ * Decide whether the `--from-cfn-stack <name>` redundancy tip should
55562
+ * fire for the current invocation. Fires only when:
55563
+ * - `fromCfnStack` is a non-empty STRING (explicit value, not bare `true`)
55564
+ * - exactly ONE stack is routed
55565
+ * - the explicit value equals the routed stack's `stackName`
55566
+ *
55567
+ * Extracted as a pure function so it can be unit-tested without booting
55568
+ * the full server. See improvement A in the start-api UX PR.
55569
+ *
55570
+ * @internal exported for unit tests.
55571
+ */
55572
+ function shouldEmitFromCfnRedundancyTip(fromCfnStack, routedStackNames) {
55573
+ if (typeof fromCfnStack !== "string") return false;
55574
+ if (fromCfnStack.length === 0) return false;
55575
+ if (routedStackNames.length !== 1) return false;
55576
+ return fromCfnStack === routedStackNames[0];
55577
+ }
55578
+ /**
55579
+ * One-shot wrapper around `shouldEmitFromCfnRedundancyTip` for the
55580
+ * `--watch` hot-reload path. `synthesizeAndBuild` re-runs on every
55581
+ * reload firing, so without a gate the tip would re-emit on every
55582
+ * reload — noisy. This helper consults the caller-supplied ref:
55583
+ * - If the predicate fires AND the ref is still `false`, calls `emit`
55584
+ * and flips the ref to `true`.
55585
+ * - On subsequent invocations the ref is `true` and the helper is a
55586
+ * no-op for the rest of the ref's lifetime.
55587
+ * - When the predicate does NOT fire (no `--from-cfn-stack` value /
55588
+ * intentionally-different value / multi-stack run), the ref stays
55589
+ * `false` so a future reload whose synthesized stacks change in a
55590
+ * way that DOES make the value redundant still emits the tip once.
55591
+ *
55592
+ * The ref is owned by `localStartApiCommand` (one per server boot), so
55593
+ * independent server invocations get independent flags.
55594
+ *
55595
+ * @internal exported for unit tests.
55596
+ */
55597
+ function tryEmitFromCfnRedundancyTipOnce(fromCfnStack, routedStackNames, emittedRef, emit) {
55598
+ if (emittedRef.value) return;
55599
+ if (!shouldEmitFromCfnRedundancyTip(fromCfnStack, routedStackNames)) return;
55600
+ const routedStackName = routedStackNames[0];
55601
+ if (routedStackName === void 0) return;
55602
+ emit(routedStackName);
55603
+ emittedRef.value = true;
55518
55604
  }
55519
55605
  /**
55520
55606
  * Distinct, stable list of Lambda logical IDs reachable through any
@@ -55656,10 +55742,12 @@ async function buildContainerSpec(args) {
55656
55742
  for (const key of audit.resolvedKeys) getLogger().debug(`Lambda ${logicalId}: --from-state substituted env var ${key}`);
55657
55743
  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.`);
55658
55744
  }
55659
- const envResult = resolveEnvVars(logicalId, templateEnv, overrides);
55745
+ const lambdaCdkPath = readCdkPathOrUndefined(lambda.resource);
55746
+ const envResult = resolveEnvVars(logicalId, lambdaCdkPath, templateEnv, overrides);
55660
55747
  for (const key of envResult.unresolved) {
55661
55748
  if (stateAudit && stateAudit.unresolved.some((u) => u.key === key)) continue;
55662
- 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.`);
55749
+ const overrideKeyExample = lambdaCdkPath?.replace(/\/Resource$/, "") ?? logicalId;
55750
+ getLogger().warn(`Lambda ${logicalId}: env var ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${overrideKeyExample}":{"${key}":"<literal>"}}) or pass --from-state to recover deployed values.`);
55663
55751
  }
55664
55752
  const dockerEnv = {
55665
55753
  AWS_LAMBDA_FUNCTION_NAME: logicalId,
@@ -59180,10 +59268,12 @@ async function localInvokeCommand(target, options) {
59180
59268
  stateProvider.dispose();
59181
59269
  }
59182
59270
  const overrides = readEnvOverridesFile(options.envVars);
59183
- const envResult = resolveEnvVars(lambda.logicalId, templateEnv, overrides);
59271
+ const lambdaCdkPath = readCdkPathOrUndefined(lambda.resource);
59272
+ const envResult = resolveEnvVars(lambda.logicalId, lambdaCdkPath, templateEnv, overrides);
59184
59273
  for (const key of envResult.unresolved) {
59185
59274
  if (stateAudit && stateAudit.unresolved.some((u) => u.key === key)) continue;
59186
- logger.warn(`Environment variable ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${lambda.logicalId}":{"${key}":"<literal>"}}), or pass --from-state (cdkd-deployed) / --from-cfn-stack (cdk-deployed) to recover deployed values.`);
59275
+ const overrideKeyExample = lambdaCdkPath?.replace(/\/Resource$/, "") ?? lambda.logicalId;
59276
+ logger.warn(`Environment variable ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${overrideKeyExample}":{"${key}":"<literal>"}}), or pass --from-state (cdkd-deployed) / --from-cfn-stack (cdk-deployed) to recover deployed values.`);
59187
59277
  }
59188
59278
  let resolvedAssumeRoleArn;
59189
59279
  if (typeof options.assumeRole === "string") resolvedAssumeRoleArn = options.assumeRole;
@@ -60927,7 +61017,7 @@ function reorderArgs(argv) {
60927
61017
  */
60928
61018
  async function main() {
60929
61019
  const program = new Command();
60930
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.168.0");
61020
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.169.0");
60931
61021
  program.addCommand(createBootstrapCommand());
60932
61022
  program.addCommand(createSynthCommand());
60933
61023
  program.addCommand(createListCommand());