@go-to-k/cdkd 0.167.3 → 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
@@ -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 { A as S3StateBackend, B as resolveCaptureObservedState, C as assertRegionMatch, D as DagBuilder, E as DiffCalculator, F as buildDockerImage, G as CFN_TEMPLATE_BODY_LIMIT, H as resolveStateBucketWithDefault, I as Synthesizer, J as findLargeInlineResources, K as CFN_TEMPLATE_URL_LIMIT, L as getDefaultStateBucketName, M as AssetPublisher, N as stringifyValue, O as TemplateParser, P as WorkGraph, Q as resolveBucketRegion, R as getLegacyStateBucketName, S as CloudControlProvider, T as applyRoleArnIfSet, U as resolveStateBucketWithDefaultAndSource, V as resolveSkipPrefix, W as warnDeprecatedNoPrefixCliFlag, X as AssemblyReader, Y as uploadCfnTemplate, _ as matchesCdkPath, a as withRetry, at as LocalStartServiceError, b as ProviderRegistry, bt as normalizeAwsError, c as bold, ct as NestedStackChildDirectDestroyError, d as green, dt as ResourceTimeoutError, et as CdkdError, f as red, ft as ResourceUpdateNotSupportedError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, ht as StackTerminationProtectionError, i as withResourceDeadline, it as LocalMigrateError, j as shouldRetainResource, k as LockManager, l as cyan, lt as PartialFailureError, m as IAMRoleProvider, mt as StackHasActiveImportsError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, o as IMPLICIT_DELETE_DEPENDENCIES, p as yellow, pt as RouteDiscoveryError, q as MIGRATE_TMP_PREFIX, r as DeployEngine, rt as LocalInvokeBuildError, s as formatResourceLine, st as MissingCdkCliError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ProvisioningError, v as normalizeAwsTagsToCfn, w as IntrinsicFunctionResolver, x as findActionableSilentDrops, xt as withErrorHandling, y as resolveExplicitPhysicalId, z as resolveApp } from "./deploy-engine-BQkk03hJ.js";
3
+ import { A as S3StateBackend, B as resolveCaptureObservedState, C as assertRegionMatch, D as DagBuilder, E as DiffCalculator, F as buildDockerImage, G as CFN_TEMPLATE_BODY_LIMIT, H as resolveStateBucketWithDefault, I as Synthesizer, J as findLargeInlineResources, K as CFN_TEMPLATE_URL_LIMIT, L as getDefaultStateBucketName, M as AssetPublisher, N as stringifyValue, O as TemplateParser, P as WorkGraph, Q as resolveBucketRegion, R as getLegacyStateBucketName, S as CloudControlProvider, T as applyRoleArnIfSet, U as resolveStateBucketWithDefaultAndSource, V as resolveSkipPrefix, W as warnDeprecatedNoPrefixCliFlag, X as AssemblyReader, Y as uploadCfnTemplate, _ as matchesCdkPath, a as withRetry, at as LocalStartServiceError, b as ProviderRegistry, bt as normalizeAwsError, c as bold, ct as NestedStackChildDirectDestroyError, d as green, dt as ResourceTimeoutError, et as CdkdError, f as red, ft as ResourceUpdateNotSupportedError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, ht as StackTerminationProtectionError, i as withResourceDeadline, it as LocalMigrateError, j as shouldRetainResource, k as LockManager, l as cyan, lt as PartialFailureError, m as IAMRoleProvider, mt as StackHasActiveImportsError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, o as IMPLICIT_DELETE_DEPENDENCIES, p as yellow, pt as RouteDiscoveryError, q as MIGRATE_TMP_PREFIX, r as DeployEngine, rt as LocalInvokeBuildError, s as formatResourceLine, st as MissingCdkCliError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ProvisioningError, v as normalizeAwsTagsToCfn, w as IntrinsicFunctionResolver, x as findActionableSilentDrops, xt as withErrorHandling, y as resolveExplicitPhysicalId, z as resolveApp } from "./deploy-engine-Cux0aKqI.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";
@@ -1369,16 +1369,28 @@ async function findDownstreamConsumers(input) {
1369
1369
  const region = ref.region ?? input.baseRegion;
1370
1370
  if (ref.stackName === input.producerStack && region === input.producerRegion) return null;
1371
1371
  try {
1372
- const imports = (await input.stateBackend.getState(ref.stackName, region))?.state.imports;
1373
- if (!imports || imports.length === 0) return null;
1374
- const matches = imports.filter((entry) => entry.sourceStack === input.producerStack && entry.sourceRegion === input.producerRegion);
1375
- if (matches.length === 0) return null;
1376
- return matches.map((entry) => ({
1377
- consumerStack: ref.stackName,
1378
- consumerRegion: region,
1379
- exportName: entry.exportName,
1380
- intrinsic: "ImportValue"
1381
- }));
1372
+ const got = await input.stateBackend.getState(ref.stackName, region);
1373
+ if (!got) return null;
1374
+ const out = [];
1375
+ const imports = got.state.imports;
1376
+ if (imports && imports.length > 0) {
1377
+ for (const entry of imports) if (entry.sourceStack === input.producerStack && entry.sourceRegion === input.producerRegion) out.push({
1378
+ consumerStack: ref.stackName,
1379
+ consumerRegion: region,
1380
+ exportName: entry.exportName,
1381
+ intrinsic: "ImportValue"
1382
+ });
1383
+ }
1384
+ const outputReads = got.state.outputReads;
1385
+ if (outputReads && outputReads.length > 0) {
1386
+ for (const entry of outputReads) if (entry.sourceStack === input.producerStack && entry.sourceRegion === input.producerRegion) out.push({
1387
+ consumerStack: ref.stackName,
1388
+ consumerRegion: region,
1389
+ exportName: entry.outputName,
1390
+ intrinsic: "GetStackOutput"
1391
+ });
1392
+ }
1393
+ return out.length > 0 ? out : null;
1382
1394
  } catch (err) {
1383
1395
  logger.debug(`findDownstreamConsumers: skip ${ref.stackName} (${region}); ${err instanceof Error ? err.message : String(err)}`);
1384
1396
  return null;
@@ -33933,7 +33945,7 @@ async function deployCommand(stacks, options) {
33933
33945
  validation: validateRecreateTargets({
33934
33946
  template: stackInfo.template,
33935
33947
  state: stateForRecreateCheck?.state ?? {
33936
- version: 7,
33948
+ version: 8,
33937
33949
  stackName: stackInfo.stackName,
33938
33950
  region: stackRegion,
33939
33951
  resources: {},
@@ -34940,7 +34952,7 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
34940
34952
  region,
34941
34953
  resources: {},
34942
34954
  outputs: {},
34943
- version: 7,
34955
+ version: 8,
34944
34956
  lastModified: Date.now()
34945
34957
  };
34946
34958
  }
@@ -36463,6 +36475,21 @@ function readCdkPath(resource) {
36463
36475
  return typeof v === "string" ? v : "";
36464
36476
  }
36465
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
+ /**
36466
36493
  * Build a `Map<cdkPath, logicalId>` from a synthesized template.
36467
36494
  *
36468
36495
  * Used by `cdkd orphan <constructPath>` to translate user-supplied
@@ -41656,7 +41683,7 @@ function buildStackState(stackName, region, rows, templateParser, template, exis
41656
41683
  };
41657
41684
  }
41658
41685
  return {
41659
- version: 7,
41686
+ version: 8,
41660
41687
  stackName,
41661
41688
  region,
41662
41689
  resources,
@@ -43656,21 +43683,37 @@ function looksLikeAccessDenied(err) {
43656
43683
  * @param logicalId The function's CloudFormation logical ID. Used
43657
43684
  * to look up function-specific overrides in the
43658
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.
43659
43695
  * @param templateEnv The function's `Properties.Environment.Variables`
43660
43696
  * object from the synthesized template, or
43661
43697
  * `undefined` when the function has no env vars.
43662
43698
  * @param overrides Parsed `--env-vars` file contents, or
43663
43699
  * `undefined` when the flag was not passed.
43664
43700
  */
43665
- function resolveEnvVars(logicalId, templateEnv, overrides) {
43701
+ function resolveEnvVars(logicalId, displayPath, templateEnv, overrides) {
43666
43702
  const resolved = {};
43667
43703
  const unresolved = [];
43668
43704
  if (templateEnv) for (const [key, value] of Object.entries(templateEnv)) if (isLiteralEnvValue(value)) resolved[key] = String(value);
43669
43705
  else unresolved.push(key);
43670
43706
  if (overrides) {
43671
43707
  applyOverrideMap$1(resolved, overrides.Parameters);
43672
- const fnOverrides = overrides[logicalId];
43673
- 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
+ }
43674
43717
  }
43675
43718
  return {
43676
43719
  resolved,
@@ -55096,6 +55139,7 @@ async function localStartApiCommand(target, options) {
55096
55139
  const jwksWarnedUrls = /* @__PURE__ */ new Set();
55097
55140
  let sigV4CredentialsLoader;
55098
55141
  const sigV4WarnedForeignIds = /* @__PURE__ */ new Set();
55142
+ const fromCfnTipEmitted = { value: false };
55099
55143
  /**
55100
55144
  * One synth + discover + build pass. Returns the next-state
55101
55145
  * material. Reused on initial boot AND every hot-reload firing.
@@ -55121,8 +55165,14 @@ async function localStartApiCommand(target, options) {
55121
55165
  ...options.profile && { macroExpandS3ClientOpts: { profile: options.profile } }
55122
55166
  };
55123
55167
  const { stacks } = await synthesizer.synthesize(synthOpts);
55124
- const targetStacks = pickTargetStacks(stacks, options.stack);
55125
- 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
+ });
55126
55176
  const routes = discoverRoutes(targetStacks);
55127
55177
  const wsDiscovery = discoverWebSocketApis(targetStacks);
55128
55178
  if (wsDiscovery.errors.length > 0) for (const e of wsDiscovery.errors) logger.warn(`WebSocket discovery: ${e}`);
@@ -55497,12 +55547,60 @@ async function localStartApiCommand(target, options) {
55497
55547
  * Match the `--stack` pattern (or single-stack auto-detect) to a list
55498
55548
  * of stacks the route-discovery walks. Mirrors the deploy/diff matcher
55499
55549
  * routing rules.
55550
+ *
55551
+ * @internal exported for unit tests.
55500
55552
  */
55501
- function pickTargetStacks(stacks, pattern) {
55502
- 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]);
55503
55556
  if (stacks.length === 1) return stacks;
55504
55557
  if (stacks.length === 0) return [];
55505
- 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;
55506
55604
  }
55507
55605
  /**
55508
55606
  * Distinct, stable list of Lambda logical IDs reachable through any
@@ -55644,10 +55742,12 @@ async function buildContainerSpec(args) {
55644
55742
  for (const key of audit.resolvedKeys) getLogger().debug(`Lambda ${logicalId}: --from-state substituted env var ${key}`);
55645
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.`);
55646
55744
  }
55647
- const envResult = resolveEnvVars(logicalId, templateEnv, overrides);
55745
+ const lambdaCdkPath = readCdkPathOrUndefined(lambda.resource);
55746
+ const envResult = resolveEnvVars(logicalId, lambdaCdkPath, templateEnv, overrides);
55648
55747
  for (const key of envResult.unresolved) {
55649
55748
  if (stateAudit && stateAudit.unresolved.some((u) => u.key === key)) continue;
55650
- 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.`);
55651
55751
  }
55652
55752
  const dockerEnv = {
55653
55753
  AWS_LAMBDA_FUNCTION_NAME: logicalId,
@@ -59168,10 +59268,12 @@ async function localInvokeCommand(target, options) {
59168
59268
  stateProvider.dispose();
59169
59269
  }
59170
59270
  const overrides = readEnvOverridesFile(options.envVars);
59171
- const envResult = resolveEnvVars(lambda.logicalId, templateEnv, overrides);
59271
+ const lambdaCdkPath = readCdkPathOrUndefined(lambda.resource);
59272
+ const envResult = resolveEnvVars(lambda.logicalId, lambdaCdkPath, templateEnv, overrides);
59172
59273
  for (const key of envResult.unresolved) {
59173
59274
  if (stateAudit && stateAudit.unresolved.some((u) => u.key === key)) continue;
59174
- 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.`);
59175
59277
  }
59176
59278
  let resolvedAssumeRoleArn;
59177
59279
  if (typeof options.assumeRole === "string") resolvedAssumeRoleArn = options.assumeRole;
@@ -60915,7 +61017,7 @@ function reorderArgs(argv) {
60915
61017
  */
60916
61018
  async function main() {
60917
61019
  const program = new Command();
60918
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.167.3");
61020
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.169.0");
60919
61021
  program.addCommand(createBootstrapCommand());
60920
61022
  program.addCommand(createSynthCommand());
60921
61023
  program.addCommand(createListCommand());