@go-to-k/cdkd 0.161.0 → 0.161.2

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
@@ -42081,6 +42081,55 @@ function createLocalStateProvider(options, cdkdStackName, synthRegion) {
42081
42081
  //#endregion
42082
42082
  //#region src/local/intrinsic-image.ts
42083
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
+ /**
42084
42133
  * Resolve the canonical CDK 2.x `Fn::Join` shape emitted by
42085
42134
  * `ContainerImage.fromEcrRepository(repo, tag)` (ECS) and
42086
42135
  * `lambda.DockerImageCode.fromEcr(repo, { tagOrDigest })` (Lambda
@@ -42447,7 +42496,7 @@ function extractLambdaProperties(stack, logicalId, resource, resources) {
42447
42496
  const timeoutSec = typeof props["Timeout"] === "number" ? props["Timeout"] : 3;
42448
42497
  const ephemeralStorageMb = extractEphemeralStorageMb(props, logicalId);
42449
42498
  const code = props["Code"] ?? {};
42450
- const imageUri = extractImageUri$1(code["ImageUri"], logicalId, stack.stackName, resources);
42499
+ const imageUri = extractImageUri$1(code["ImageUri"], logicalId, stack.stackName, resources, stack.region);
42451
42500
  if (imageUri !== void 0) return extractImageLambdaProperties({
42452
42501
  stack,
42453
42502
  logicalId,
@@ -42544,7 +42593,7 @@ function extractEphemeralStorageMb(props, logicalId) {
42544
42593
  * for genuinely unrecognized shapes so the caller's downstream ZIP-vs-
42545
42594
  * IMAGE branching can route to its existing error path.
42546
42595
  */
42547
- function extractImageUri$1(value, logicalId, stackName, resources) {
42596
+ function extractImageUri$1(value, logicalId, stackName, resources, region) {
42548
42597
  if (typeof value === "string" && value.length > 0) return value;
42549
42598
  if (value && typeof value === "object" && !Array.isArray(value)) {
42550
42599
  const obj = value;
@@ -42552,11 +42601,12 @@ function extractImageUri$1(value, logicalId, stackName, resources) {
42552
42601
  if (typeof sub === "string" && sub.length > 0) return sub;
42553
42602
  if (Array.isArray(sub) && typeof sub[0] === "string") return sub[0];
42554
42603
  if ("Fn::Join" in obj) {
42555
- const joinResolved = tryResolveImageFnJoin(value, resources, void 0);
42604
+ const pseudoParameters = derivePseudoParametersFromRegion(region);
42605
+ const joinResolved = tryResolveImageFnJoin(value, resources, pseudoParameters ? { pseudoParameters } : void 0);
42556
42606
  if (joinResolved.kind === "resolved") return joinResolved.uri;
42557
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.`);
42558
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).`);
42559
- 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.`);
42560
42610
  }
42561
42611
  }
42562
42612
  }
@@ -43056,9 +43106,14 @@ function substituteAgainstState(value, contextOrResources) {
43056
43106
  if (intrinsic === "Fn::GetAtt") return resolveGetAtt(arg, context);
43057
43107
  if (intrinsic === "Fn::Sub") return resolveSub(arg, context);
43058
43108
  if (intrinsic === "Fn::Join") return resolveJoin(arg, context);
43109
+ if (intrinsic === "Fn::Select") return resolveSelect(arg, context);
43110
+ if (intrinsic === "Fn::Split") return {
43111
+ kind: "unresolved",
43112
+ reason: `Fn::Split returns an array, which is not a valid env-var value (use Fn::Select to pick one element)`
43113
+ };
43059
43114
  return {
43060
43115
  kind: "unresolved",
43061
- reason: `unsupported intrinsic '${intrinsic}' (supported: Ref, Fn::GetAtt, Fn::Sub, Fn::Join)`
43116
+ reason: `unsupported intrinsic '${intrinsic}' (supported: Ref, Fn::GetAtt, Fn::Sub, Fn::Join, Fn::Select, Fn::Split)`
43062
43117
  };
43063
43118
  }
43064
43119
  function isContext(v) {
@@ -43272,6 +43327,121 @@ function resolveJoin(arg, context) {
43272
43327
  };
43273
43328
  }
43274
43329
  /**
43330
+ * `Fn::Select: [<index>, <list>]` — pick the indexed element of an
43331
+ * array. Mirrors the semantics of `resolveImageIntrinsic`'s `Fn::Select`
43332
+ * branch in `src/local/intrinsic-image.ts` so the two resolvers behave
43333
+ * the same way for the same template shape.
43334
+ *
43335
+ * - Index may be a number (`0`) OR a numeric string (`'0'`) — CFn
43336
+ * templates often carry the string form after a JSON round-trip.
43337
+ * - List may be a resolved-array intrinsic (today only `Fn::Split`
43338
+ * produces a `string[]`) OR a literal `[...]` array of intrinsics
43339
+ * resolved on the fly.
43340
+ * - Out-of-bounds / negative / non-finite index reports unresolved.
43341
+ */
43342
+ function resolveSelect(arg, context) {
43343
+ if (!Array.isArray(arg) || arg.length !== 2) return {
43344
+ kind: "unresolved",
43345
+ reason: `Fn::Select expects [index, list], got ${Array.isArray(arg) ? `array of length ${arg.length}` : typeof arg}`
43346
+ };
43347
+ const [rawIndex, listArg] = arg;
43348
+ let index;
43349
+ if (typeof rawIndex === "number") index = rawIndex;
43350
+ else if (typeof rawIndex === "string" && /^-?\d+$/.test(rawIndex)) index = Number.parseInt(rawIndex, 10);
43351
+ if (index === void 0 || !Number.isFinite(index)) return {
43352
+ kind: "unresolved",
43353
+ reason: `Fn::Select index must be a finite number (or numeric string), got ${typeof rawIndex}`
43354
+ };
43355
+ if (index < 0) return {
43356
+ kind: "unresolved",
43357
+ reason: `Fn::Select index must be non-negative, got ${index}`
43358
+ };
43359
+ const list = resolveAny(listArg, context);
43360
+ if (list.kind === "unresolved") return {
43361
+ kind: "unresolved",
43362
+ reason: `Fn::Select list: ${list.reason}`
43363
+ };
43364
+ if (Array.isArray(list.value)) {
43365
+ if (index >= list.value.length) return {
43366
+ kind: "unresolved",
43367
+ reason: `Fn::Select index ${index} out of bounds (list length ${list.value.length})`
43368
+ };
43369
+ return {
43370
+ kind: "literal",
43371
+ value: list.value[index]
43372
+ };
43373
+ }
43374
+ return {
43375
+ kind: "unresolved",
43376
+ reason: `Fn::Select list must resolve to an array, got ${typeof list.value}`
43377
+ };
43378
+ }
43379
+ /**
43380
+ * `Fn::Split: [<delimiter>, <string>]` — split a string into a
43381
+ * `string[]`. Only callable through `resolveAny` (i.e. inside
43382
+ * `Fn::Select`); the top-level dispatcher rejects bare `Fn::Split`
43383
+ * since an array cannot be an env-var value.
43384
+ *
43385
+ * The string argument can itself be an intrinsic (typical CDK shape:
43386
+ * `Fn::Split: [':', { 'Fn::GetAtt': [<Secret>, 'SecretArn'] }]`); it's
43387
+ * resolved through `substituteAgainstState` so we don't accidentally
43388
+ * admit nested-array shapes there.
43389
+ */
43390
+ function resolveSplitAsArray(arg, context) {
43391
+ if (!Array.isArray(arg) || arg.length !== 2) return {
43392
+ kind: "unresolved",
43393
+ reason: `Fn::Split expects [delimiter, string], got ${Array.isArray(arg) ? `array of length ${arg.length}` : typeof arg}`
43394
+ };
43395
+ const [delim, src] = arg;
43396
+ if (typeof delim !== "string") return {
43397
+ kind: "unresolved",
43398
+ reason: `Fn::Split delimiter must be a string, got ${typeof delim}`
43399
+ };
43400
+ const sub = substituteAgainstState(src, context);
43401
+ if (sub.kind !== "literal") return {
43402
+ kind: "unresolved",
43403
+ reason: `Fn::Split string argument: ${sub.reason}`
43404
+ };
43405
+ if (typeof sub.value !== "string") return {
43406
+ kind: "unresolved",
43407
+ reason: `Fn::Split string argument must resolve to a string, got ${typeof sub.value}`
43408
+ };
43409
+ return {
43410
+ kind: "literal",
43411
+ value: sub.value.split(delim)
43412
+ };
43413
+ }
43414
+ /**
43415
+ * Array-tolerant resolver used by `Fn::Select`'s `list` argument.
43416
+ * Returns either the scalar `StateSubstitutionResult` shape (delegating
43417
+ * to `substituteAgainstState`) OR a `{kind: 'literal', value: string[]}`
43418
+ * when the node is `Fn::Split` / a literal array of intrinsics.
43419
+ *
43420
+ * Top-level `substituteAgainstState` deliberately doesn't go through
43421
+ * this helper — env-var values can't be arrays, and the asymmetry
43422
+ * matches `intrinsic-image.ts`'s `resolveImageIntrinsic` (scalar) vs
43423
+ * `resolveImageIntrinsicAny` (scalar OR array) split.
43424
+ */
43425
+ function resolveAny(value, context) {
43426
+ if (Array.isArray(value)) {
43427
+ const out = [];
43428
+ for (let i = 0; i < value.length; i += 1) {
43429
+ const sub = substituteAgainstState(value[i], context);
43430
+ if (sub.kind !== "literal") return {
43431
+ kind: "unresolved",
43432
+ reason: `list element [${i}]: ${sub.reason}`
43433
+ };
43434
+ out.push(String(sub.value));
43435
+ }
43436
+ return {
43437
+ kind: "literal",
43438
+ value: out
43439
+ };
43440
+ }
43441
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 1 && Object.prototype.hasOwnProperty.call(value, "Fn::Split")) return resolveSplitAsArray(value["Fn::Split"], context);
43442
+ return substituteAgainstState(value, context);
43443
+ }
43444
+ /**
43275
43445
  * Async sibling of {@link substituteAgainstState}. Same semantics for every
43276
43446
  * intrinsic the sync path supports; additionally consults the
43277
43447
  * `crossStackResolver` (when supplied on the context) for `Fn::ImportValue`
@@ -43301,12 +43471,12 @@ async function substituteAgainstStateAsync(value, contextOrResources) {
43301
43471
  };
43302
43472
  const intrinsic = keys[0];
43303
43473
  const arg = obj[intrinsic];
43304
- if (intrinsic === "Ref" || intrinsic === "Fn::GetAtt" || intrinsic === "Fn::Sub" || intrinsic === "Fn::Join") return substituteAgainstState(value, context);
43474
+ if (intrinsic === "Ref" || intrinsic === "Fn::GetAtt" || intrinsic === "Fn::Sub" || intrinsic === "Fn::Join" || intrinsic === "Fn::Select" || intrinsic === "Fn::Split") return substituteAgainstState(value, context);
43305
43475
  if (intrinsic === "Fn::ImportValue") return resolveImportValueAsync(arg, context);
43306
43476
  if (intrinsic === "Fn::GetStackOutput") return resolveGetStackOutputAsync(arg, context);
43307
43477
  return {
43308
43478
  kind: "unresolved",
43309
- reason: `unsupported intrinsic '${intrinsic}' (supported: Ref, Fn::GetAtt, Fn::Sub, Fn::Join, Fn::ImportValue, Fn::GetStackOutput)`
43479
+ reason: `unsupported intrinsic '${intrinsic}' (supported: Ref, Fn::GetAtt, Fn::Sub, Fn::Join, Fn::Select, Fn::Split, Fn::ImportValue, Fn::GetStackOutput)`
43310
43480
  };
43311
43481
  }
43312
43482
  /**
@@ -54691,7 +54861,7 @@ function resolveLambdaByLogicalId(logicalId, stacks) {
54691
54861
  const memoryMb = typeof props["MemorySize"] === "number" ? props["MemorySize"] : 128;
54692
54862
  const timeoutSec = typeof props["Timeout"] === "number" ? props["Timeout"] : 3;
54693
54863
  const code = props["Code"] ?? {};
54694
- const imageUri = extractImageUri(code["ImageUri"], logicalId, stack.stackName, stack.template.Resources ?? {});
54864
+ const imageUri = extractImageUri(code["ImageUri"], logicalId, stack.stackName, stack.template.Resources ?? {}, stack.region);
54695
54865
  if (imageUri !== void 0) return resolveImageLambda({
54696
54866
  stack,
54697
54867
  logicalId,
@@ -54735,29 +54905,24 @@ function resolveLambdaByLogicalId(logicalId, stacks) {
54735
54905
  * `lambda.DockerImageCode.fromImageAsset`), and `Fn::Join` (the
54736
54906
  * canonical shape for `lambda.DockerImageCode.fromImageAsset` in
54737
54907
  * CDK 2.x, which emits a `Fn::Join` over the literal bootstrap ECR
54738
- * URI with `${AWS::URLSuffix}` — issue #627).
54908
+ * URI with `${AWS::URLSuffix}` — issues #627 + #637).
54739
54909
  *
54740
54910
  * The `Fn::Join` arm routes through the shared
54741
54911
  * `tryResolveImageFnJoin` helper (`src/local/intrinsic-image.ts`) used
54742
- * by `cdkd local invoke`. Like the sibling `lambda-resolver.ts`, we
54743
- * pass `undefined` for the `ImageResolutionContext` start-api
54744
- * doesn't load cdkd state up front, so same-stack ECR refs surface
54745
- * as `needs-state` and pseudo-parameter-only shapes (`Ref:
54746
- * AWS::URLSuffix`) surface as `not-applicable`. Both cases throw a
54747
- * clear error that names the actual root cause instead of falling
54748
- * through to the ZIP branch's misleading "no Runtime" hard error.
54749
- *
54750
- * Pseudo-parameter substitution (`${AWS::URLSuffix}` → `amazonaws.com`)
54751
- * is deliberately not implemented here — `lambda-resolver.ts` (the
54752
- * canonical sibling) also defers it, and shipping one-sided support
54753
- * would surprise users. Tracked separately as the issue's optional
54754
- * follow-up.
54912
+ * by `cdkd local invoke`. When the synth template recorded a deploy
54913
+ * region (`stack.region`), we derive `{ urlSuffix, partition, region }`
54914
+ * via `derivePseudoParametersFromRegion` and pass it as the resolver's
54915
+ * `pseudoParameters` block so the canonical `${AWS::URLSuffix}` shape
54916
+ * resolves cleanly (issue #637). Same-stack ECR refs still surface as
54917
+ * `needs-state` (those require `--from-state`); a Join that references
54918
+ * `${AWS::AccountId}` without state still surfaces as `not-applicable`
54919
+ * with a more specific error message naming the missing parameter.
54755
54920
  *
54756
54921
  * Returns `undefined` when the field is absent or non-recognized,
54757
54922
  * which routes the caller to the ZIP branch (with its existing
54758
54923
  * "no Runtime / no Handler" validations).
54759
54924
  */
54760
- function extractImageUri(value, logicalId, stackName, resources) {
54925
+ function extractImageUri(value, logicalId, stackName, resources, region) {
54761
54926
  if (typeof value === "string" && value.length > 0) return value;
54762
54927
  if (value && typeof value === "object" && !Array.isArray(value)) {
54763
54928
  const obj = value;
@@ -54765,11 +54930,12 @@ function extractImageUri(value, logicalId, stackName, resources) {
54765
54930
  if (typeof sub === "string" && sub.length > 0) return sub;
54766
54931
  if (Array.isArray(sub) && typeof sub[0] === "string") return sub[0];
54767
54932
  if ("Fn::Join" in obj) {
54768
- const joinResolved = tryResolveImageFnJoin(value, resources, void 0);
54933
+ const pseudoParameters = derivePseudoParametersFromRegion(region);
54934
+ const joinResolved = tryResolveImageFnJoin(value, resources, pseudoParameters ? { pseudoParameters } : void 0);
54769
54935
  if (joinResolved.kind === "resolved") return joinResolved.uri;
54770
54936
  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.`);
54771
54937
  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).`);
54772
- 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.`);
54938
+ 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.`);
54773
54939
  }
54774
54940
  }
54775
54941
  }
@@ -59598,7 +59764,7 @@ function reorderArgs(argv) {
59598
59764
  */
59599
59765
  async function main() {
59600
59766
  const program = new Command();
59601
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.161.0");
59767
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.161.2");
59602
59768
  program.addCommand(createBootstrapCommand());
59603
59769
  program.addCommand(createSynthCommand());
59604
59770
  program.addCommand(createListCommand());