@go-to-k/cdkd 0.160.0 → 0.161.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/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-BXWv-yRb.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-BC1Z7ABm.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";
@@ -34360,6 +34360,7 @@ async function buildDiffTree(args) {
34360
34360
  displayName,
34361
34361
  region,
34362
34362
  changes: await computeStackDiff(state, template, region, stackName, stateBackend, diffCalculator),
34363
+ ccApiRoutes: collectCcApiRoutes(template, state),
34363
34364
  children: []
34364
34365
  };
34365
34366
  if (!recursive) return node;
@@ -34403,6 +34404,7 @@ async function buildDeletedSubtree(stackName, region, stateBackend, diffCalculat
34403
34404
  displayName: stackName,
34404
34405
  region,
34405
34406
  changes: await computeStackDiff(state, EMPTY_TEMPLATE, region, stackName, stateBackend, diffCalculator),
34407
+ ccApiRoutes: /* @__PURE__ */ new Map(),
34406
34408
  children: []
34407
34409
  };
34408
34410
  for (const [logicalId, resource] of Object.entries(state.resources)) {
@@ -34411,6 +34413,52 @@ async function buildDeletedSubtree(stackName, region, stateBackend, diffCalculat
34411
34413
  }
34412
34414
  return node;
34413
34415
  }
34416
+ const EMPTY_ALLOW_SET = /* @__PURE__ */ new Set();
34417
+ /**
34418
+ * Walk every resource in `template` and return the logicalId → annotation
34419
+ * source map that #614's auto-fallback would route via Cloud Control API.
34420
+ *
34421
+ * Two annotation sources are merged into one map so the diff renderer
34422
+ * matches the live-progress label and the design §8 statement that the
34423
+ * `[via CC API: ...]` tag "stays visible whenever the resource has the
34424
+ * `provisionedBy: 'cc-api'` state field set OR is being introduced via the
34425
+ * auto-route":
34426
+ *
34427
+ * - **Fresh hits**: a resource whose template uses one or more
34428
+ * silent-drop top-level CFn properties. Annotation value is the list
34429
+ * of property names (e.g. `LoggingConfig`).
34430
+ * - **Sticky hits**: a resource whose deployed state records
34431
+ * `provisionedBy: 'cc-api'` (from a prior deploy) even when the
34432
+ * current template's silent-drop set is empty. Annotation value is
34433
+ * the single token `sticky` so the renderer prints `[via CC API:
34434
+ * sticky]` — the routing decision is unchanged but the tag stays
34435
+ * visible per #614's sticky-state semantics.
34436
+ *
34437
+ * When both sources fire on the same resource, the fresh-hit prop list
34438
+ * wins (more informative). Empty allow-set:
34439
+ * `--allow-unsupported-properties` is a deploy-only flag, so diff
34440
+ * renders every actionable drop as an auto-route hint.
34441
+ *
34442
+ * Excludes `AWS::CDK::Metadata` (filtered like the deploy pre-flight); also
34443
+ * excludes `AWS::CloudFormation::Stack` rows since nested-stack children
34444
+ * recurse through their own templates rather than carrying CC-routable
34445
+ * properties on the parent's row.
34446
+ */
34447
+ function collectCcApiRoutes(template, state) {
34448
+ const hits = /* @__PURE__ */ new Map();
34449
+ for (const [logicalId, resource] of Object.entries(template.Resources ?? {})) {
34450
+ if (!resource) continue;
34451
+ if (resource.Type === "AWS::CDK::Metadata") continue;
34452
+ if (resource.Type === "AWS::CloudFormation::Stack") continue;
34453
+ const drops = findActionableSilentDrops(resource.Type, resource.Properties, EMPTY_ALLOW_SET);
34454
+ if (drops.length > 0) {
34455
+ hits.set(logicalId, drops.map((d) => d.property));
34456
+ continue;
34457
+ }
34458
+ if (state.resources[logicalId]?.provisionedBy === "cc-api") hits.set(logicalId, ["sticky"]);
34459
+ }
34460
+ return hits;
34461
+ }
34414
34462
  /** True when this node has at least one real (non-`NO_CHANGE`) change. */
34415
34463
  function nodeHasChanges(node) {
34416
34464
  for (const change of node.changes.values()) if (change.changeType !== "NO_CHANGE") return true;
@@ -34431,12 +34479,14 @@ function diffTreeToJson(node) {
34431
34479
  const changes = [];
34432
34480
  for (const change of node.changes.values()) {
34433
34481
  if (change.changeType === "NO_CHANGE") continue;
34482
+ const ccApi = node.ccApiRoutes.get(change.logicalId);
34434
34483
  changes.push({
34435
34484
  logicalId: change.logicalId,
34436
34485
  changeType: change.changeType,
34437
34486
  resourceType: change.resourceType,
34438
34487
  ...change.propertyChanges && change.propertyChanges.length > 0 ? { propertyChanges: change.propertyChanges } : {},
34439
- ...change.attributeChanges && change.attributeChanges.length > 0 ? { attributeChanges: change.attributeChanges } : {}
34488
+ ...change.attributeChanges && change.attributeChanges.length > 0 ? { attributeChanges: change.attributeChanges } : {},
34489
+ ...ccApi && ccApi.length > 0 ? { ccApi } : {}
34440
34490
  });
34441
34491
  }
34442
34492
  return {
@@ -34500,19 +34550,30 @@ function stripUnchangedValues(value, other) {
34500
34550
  * Render one resource-change map into human-readable diff lines via `logFn`,
34501
34551
  * returning the per-type counts. Shared by the root stack block and every
34502
34552
  * nested-stack block.
34553
+ *
34554
+ * When `ccApiRoutes` is supplied, every CREATE / UPDATE line whose logical ID
34555
+ * appears in the map gets a `[via CC API: <props>]` suffix so the user sees
34556
+ * #614's auto-fallback decision at plan time. DELETE lines are not annotated
34557
+ * — the delete routing is recorded on each resource's `provisionedBy` state
34558
+ * field rather than re-derived from the template.
34503
34559
  */
34504
- function renderChangeLines(changes, logFn) {
34560
+ function renderChangeLines(changes, logFn, ccApiRoutes) {
34505
34561
  let createCount = 0;
34506
34562
  let updateCount = 0;
34507
34563
  let deleteCount = 0;
34564
+ const annotateRouting = (logicalId) => {
34565
+ const props = ccApiRoutes?.get(logicalId);
34566
+ if (!props || props.length === 0) return "";
34567
+ return ` [via CC API: ${props.join(", ")}]`;
34568
+ };
34508
34569
  for (const [logicalId, change] of changes.entries()) switch (change.changeType) {
34509
34570
  case "CREATE":
34510
34571
  createCount++;
34511
- logFn(` [+] ${logicalId} (${change.resourceType})`);
34572
+ logFn(` [+] ${logicalId} (${change.resourceType})${annotateRouting(logicalId)}`);
34512
34573
  break;
34513
34574
  case "UPDATE":
34514
34575
  updateCount++;
34515
- logFn(` [~] ${logicalId} (${change.resourceType})`);
34576
+ logFn(` [~] ${logicalId} (${change.resourceType})${annotateRouting(logicalId)}`);
34516
34577
  if (change.propertyChanges && change.propertyChanges.length > 0) for (const propChange of change.propertyChanges) {
34517
34578
  const requiresReplace = propChange.requiresReplacement ? " [requires replacement]" : "";
34518
34579
  const oldFiltered = stripUnchangedValues(propChange.oldValue, propChange.newValue);
@@ -34552,7 +34613,7 @@ function renderChangeLines(changes, logFn) {
34552
34613
  function renderDiffTree(node, isRoot, logFn) {
34553
34614
  if (nodeHasChanges(node)) {
34554
34615
  logFn(isRoot ? `\nStack ${node.stackName}:` : `\nNested stack: ${node.displayName}`);
34555
- const { create, update, delete: del } = renderChangeLines(node.changes, logFn);
34616
+ const { create, update, delete: del } = renderChangeLines(node.changes, logFn, node.ccApiRoutes);
34556
34617
  logFn(`\n${create} to create, ${update} to update, ${del} to delete`);
34557
34618
  }
34558
34619
  for (const child of node.children) renderDiffTree(child, false, logFn);
@@ -42020,6 +42081,55 @@ function createLocalStateProvider(options, cdkdStackName, synthRegion) {
42020
42081
  //#endregion
42021
42082
  //#region src/local/intrinsic-image.ts
42022
42083
  /**
42084
+ * Derive the AWS pseudo parameters that are trivially knowable from the
42085
+ * deploy region alone, without any STS call or cdkd state load.
42086
+ * `urlSuffix` and `partition` follow the canonical AWS partition rules:
42087
+ *
42088
+ * - region prefix `cn-*` → partition `aws-cn`, urlSuffix `amazonaws.com.cn`
42089
+ * - region prefix `us-gov-*` → partition `aws-us-gov`, urlSuffix `amazonaws.com`
42090
+ * - region prefix `us-iso-*` → partition `aws-iso`, urlSuffix `c2s.ic.gov`
42091
+ * - region prefix `us-isob-*` → partition `aws-iso-b`, urlSuffix `sc2s.sgov.gov`
42092
+ * - everything else (`us-east-1` / `eu-west-2` / `ap-northeast-1` / etc.)
42093
+ * → partition `aws`, urlSuffix `amazonaws.com`
42094
+ *
42095
+ * `accountId` is optional pass-through (caller decides whether to populate
42096
+ * it). The bootstrap-ECR URI shape that `lambda.DockerImageCode.fromImageAsset`
42097
+ * synthesizes carries account-id + region as literal strings in the template,
42098
+ * so only `urlSuffix` / `partition` / `region` are required to resolve it
42099
+ * (issue #637).
42100
+ *
42101
+ * Returns `undefined` when `region` is undefined / empty so the caller can
42102
+ * fall through cleanly. The shape mirrors `ImageResolutionContext.pseudoParameters`
42103
+ * so the result drops straight into a context literal.
42104
+ */
42105
+ function derivePseudoParametersFromRegion(region, accountId) {
42106
+ if (!region || typeof region !== "string" || region.length === 0) return void 0;
42107
+ let partition;
42108
+ let urlSuffix;
42109
+ if (region.startsWith("cn-")) {
42110
+ partition = "aws-cn";
42111
+ urlSuffix = "amazonaws.com.cn";
42112
+ } else if (region.startsWith("us-gov-")) {
42113
+ partition = "aws-us-gov";
42114
+ urlSuffix = "amazonaws.com";
42115
+ } else if (region.startsWith("us-isob-")) {
42116
+ partition = "aws-iso-b";
42117
+ urlSuffix = "sc2s.sgov.gov";
42118
+ } else if (region.startsWith("us-iso-")) {
42119
+ partition = "aws-iso";
42120
+ urlSuffix = "c2s.ic.gov";
42121
+ } else {
42122
+ partition = "aws";
42123
+ urlSuffix = "amazonaws.com";
42124
+ }
42125
+ return {
42126
+ ...accountId !== void 0 && { accountId },
42127
+ region,
42128
+ partition,
42129
+ urlSuffix
42130
+ };
42131
+ }
42132
+ /**
42023
42133
  * Resolve the canonical CDK 2.x `Fn::Join` shape emitted by
42024
42134
  * `ContainerImage.fromEcrRepository(repo, tag)` (ECS) and
42025
42135
  * `lambda.DockerImageCode.fromEcr(repo, { tagOrDigest })` (Lambda
@@ -42386,7 +42496,7 @@ function extractLambdaProperties(stack, logicalId, resource, resources) {
42386
42496
  const timeoutSec = typeof props["Timeout"] === "number" ? props["Timeout"] : 3;
42387
42497
  const ephemeralStorageMb = extractEphemeralStorageMb(props, logicalId);
42388
42498
  const code = props["Code"] ?? {};
42389
- const imageUri = extractImageUri$1(code["ImageUri"], logicalId, stack.stackName, resources);
42499
+ const imageUri = extractImageUri$1(code["ImageUri"], logicalId, stack.stackName, resources, stack.region);
42390
42500
  if (imageUri !== void 0) return extractImageLambdaProperties({
42391
42501
  stack,
42392
42502
  logicalId,
@@ -42483,7 +42593,7 @@ function extractEphemeralStorageMb(props, logicalId) {
42483
42593
  * for genuinely unrecognized shapes so the caller's downstream ZIP-vs-
42484
42594
  * IMAGE branching can route to its existing error path.
42485
42595
  */
42486
- function extractImageUri$1(value, logicalId, stackName, resources) {
42596
+ function extractImageUri$1(value, logicalId, stackName, resources, region) {
42487
42597
  if (typeof value === "string" && value.length > 0) return value;
42488
42598
  if (value && typeof value === "object" && !Array.isArray(value)) {
42489
42599
  const obj = value;
@@ -42491,11 +42601,12 @@ function extractImageUri$1(value, logicalId, stackName, resources) {
42491
42601
  if (typeof sub === "string" && sub.length > 0) return sub;
42492
42602
  if (Array.isArray(sub) && typeof sub[0] === "string") return sub[0];
42493
42603
  if ("Fn::Join" in obj) {
42494
- const joinResolved = tryResolveImageFnJoin(value, resources, void 0);
42604
+ const pseudoParameters = derivePseudoParametersFromRegion(region);
42605
+ const joinResolved = tryResolveImageFnJoin(value, resources, pseudoParameters ? { pseudoParameters } : void 0);
42495
42606
  if (joinResolved.kind === "resolved") return joinResolved.uri;
42496
42607
  if (joinResolved.kind === "needs-state") throw new LocalInvokeResolutionError(`Lambda '${logicalId}' in ${stackName} references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. cdkd local invoke cannot resolve the repository URI without state — deploy the stack first (so cdkd records the repository physical id), rebuild via lambda.DockerImageCode.fromImageAsset, or pin a public image.`);
42497
42608
  if (joinResolved.kind === "unsupported-join") throw new LocalInvokeResolutionError(`Lambda '${logicalId}' in ${stackName} has an unsupported Fn::Join Code.ImageUri shape: ${joinResolved.reason}. cdkd local invoke recognizes the canonical CDK 2.x lambda.DockerImageCode.fromEcr Fn::Join shape (delimiter "" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).`);
42498
- throw new LocalInvokeResolutionError(`Lambda '${logicalId}' in ${stackName} has an Fn::Join Code.ImageUri that cdkd local invoke cannot resolve. The shape likely references AWS pseudo parameters (e.g. \${AWS::URLSuffix}) for an imported ECR repository. Workarounds: rebuild via lambda.DockerImageCode.fromImageAsset, or pin a fully-literal public image URI.`);
42609
+ throw new LocalInvokeResolutionError(`Lambda '${logicalId}' in ${stackName} has an Fn::Join Code.ImageUri that cdkd local invoke cannot resolve${pseudoParameters ? " (likely ${AWS::AccountId}, which cdkd cannot derive without --from-state or STS)" : ` (cdkd could not derive AWS pseudo parameters because stack.region was undefined)`}. Workarounds: deploy first and run with --from-state, or pin a fully-literal public image URI.`);
42499
42610
  }
42500
42611
  }
42501
42612
  }
@@ -54630,7 +54741,7 @@ function resolveLambdaByLogicalId(logicalId, stacks) {
54630
54741
  const memoryMb = typeof props["MemorySize"] === "number" ? props["MemorySize"] : 128;
54631
54742
  const timeoutSec = typeof props["Timeout"] === "number" ? props["Timeout"] : 3;
54632
54743
  const code = props["Code"] ?? {};
54633
- const imageUri = extractImageUri(code["ImageUri"], logicalId, stack.stackName, stack.template.Resources ?? {});
54744
+ const imageUri = extractImageUri(code["ImageUri"], logicalId, stack.stackName, stack.template.Resources ?? {}, stack.region);
54634
54745
  if (imageUri !== void 0) return resolveImageLambda({
54635
54746
  stack,
54636
54747
  logicalId,
@@ -54674,29 +54785,24 @@ function resolveLambdaByLogicalId(logicalId, stacks) {
54674
54785
  * `lambda.DockerImageCode.fromImageAsset`), and `Fn::Join` (the
54675
54786
  * canonical shape for `lambda.DockerImageCode.fromImageAsset` in
54676
54787
  * CDK 2.x, which emits a `Fn::Join` over the literal bootstrap ECR
54677
- * URI with `${AWS::URLSuffix}` — issue #627).
54788
+ * URI with `${AWS::URLSuffix}` — issues #627 + #637).
54678
54789
  *
54679
54790
  * The `Fn::Join` arm routes through the shared
54680
54791
  * `tryResolveImageFnJoin` helper (`src/local/intrinsic-image.ts`) used
54681
- * by `cdkd local invoke`. Like the sibling `lambda-resolver.ts`, we
54682
- * pass `undefined` for the `ImageResolutionContext` start-api
54683
- * doesn't load cdkd state up front, so same-stack ECR refs surface
54684
- * as `needs-state` and pseudo-parameter-only shapes (`Ref:
54685
- * AWS::URLSuffix`) surface as `not-applicable`. Both cases throw a
54686
- * clear error that names the actual root cause instead of falling
54687
- * through to the ZIP branch's misleading "no Runtime" hard error.
54688
- *
54689
- * Pseudo-parameter substitution (`${AWS::URLSuffix}` → `amazonaws.com`)
54690
- * is deliberately not implemented here — `lambda-resolver.ts` (the
54691
- * canonical sibling) also defers it, and shipping one-sided support
54692
- * would surprise users. Tracked separately as the issue's optional
54693
- * follow-up.
54792
+ * by `cdkd local invoke`. When the synth template recorded a deploy
54793
+ * region (`stack.region`), we derive `{ urlSuffix, partition, region }`
54794
+ * via `derivePseudoParametersFromRegion` and pass it as the resolver's
54795
+ * `pseudoParameters` block so the canonical `${AWS::URLSuffix}` shape
54796
+ * resolves cleanly (issue #637). Same-stack ECR refs still surface as
54797
+ * `needs-state` (those require `--from-state`); a Join that references
54798
+ * `${AWS::AccountId}` without state still surfaces as `not-applicable`
54799
+ * with a more specific error message naming the missing parameter.
54694
54800
  *
54695
54801
  * Returns `undefined` when the field is absent or non-recognized,
54696
54802
  * which routes the caller to the ZIP branch (with its existing
54697
54803
  * "no Runtime / no Handler" validations).
54698
54804
  */
54699
- function extractImageUri(value, logicalId, stackName, resources) {
54805
+ function extractImageUri(value, logicalId, stackName, resources, region) {
54700
54806
  if (typeof value === "string" && value.length > 0) return value;
54701
54807
  if (value && typeof value === "object" && !Array.isArray(value)) {
54702
54808
  const obj = value;
@@ -54704,11 +54810,12 @@ function extractImageUri(value, logicalId, stackName, resources) {
54704
54810
  if (typeof sub === "string" && sub.length > 0) return sub;
54705
54811
  if (Array.isArray(sub) && typeof sub[0] === "string") return sub[0];
54706
54812
  if ("Fn::Join" in obj) {
54707
- const joinResolved = tryResolveImageFnJoin(value, resources, void 0);
54813
+ const pseudoParameters = derivePseudoParametersFromRegion(region);
54814
+ const joinResolved = tryResolveImageFnJoin(value, resources, pseudoParameters ? { pseudoParameters } : void 0);
54708
54815
  if (joinResolved.kind === "resolved") return joinResolved.uri;
54709
54816
  if (joinResolved.kind === "needs-state") throw new Error(`Lambda '${logicalId}' in ${stackName} references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. cdkd local start-api cannot resolve the repository URI without state — deploy the stack first (so cdkd records the repository physical id), rebuild via lambda.DockerImageCode.fromImageAsset, or pin a public image.`);
54710
54817
  if (joinResolved.kind === "unsupported-join") throw new Error(`Lambda '${logicalId}' in ${stackName} has an unsupported Fn::Join Code.ImageUri shape: ${joinResolved.reason}. cdkd local start-api recognizes the canonical CDK 2.x lambda.DockerImageCode.fromEcr Fn::Join shape (delimiter "" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).`);
54711
- throw new Error(`Lambda '${logicalId}' in ${stackName} has an Fn::Join Code.ImageUri that cdkd local start-api cannot resolve. The shape likely references AWS pseudo parameters (e.g. \${AWS::URLSuffix}) the canonical CDK 2.x lambda.DockerImageCode.fromImageAsset synthesized shape. Workarounds: pin a fully-literal public image URI, or wait for the follow-up that substitutes \${AWS::URLSuffix} against the current region.`);
54818
+ throw new Error(`Lambda '${logicalId}' in ${stackName} has an Fn::Join Code.ImageUri that cdkd local start-api cannot resolve${pseudoParameters ? " (likely ${AWS::AccountId}, which cdkd cannot derive without --from-state or STS)" : ` (cdkd could not derive AWS pseudo parameters because stack.region was undefined)`}. Workarounds: deploy first and run with --from-state, or pin a fully-literal public image URI.`);
54712
54819
  }
54713
54820
  }
54714
54821
  }
@@ -59537,7 +59644,7 @@ function reorderArgs(argv) {
59537
59644
  */
59538
59645
  async function main() {
59539
59646
  const program = new Command();
59540
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.160.0");
59647
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.161.1");
59541
59648
  program.addCommand(createBootstrapCommand());
59542
59649
  program.addCommand(createSynthCommand());
59543
59650
  program.addCommand(createListCommand());