@go-to-k/cdkd 0.127.0 → 0.128.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/dist/cli.js CHANGED
@@ -36602,7 +36602,7 @@ function extractLambdaProperties(stack, logicalId, resource, resources) {
36602
36602
  const timeoutSec = typeof props["Timeout"] === "number" ? props["Timeout"] : 3;
36603
36603
  const ephemeralStorageMb = extractEphemeralStorageMb(props, logicalId);
36604
36604
  const code = props["Code"] ?? {};
36605
- const imageUri = extractImageUri(code["ImageUri"], logicalId, stack.stackName, resources);
36605
+ const imageUri = extractImageUri$1(code["ImageUri"], logicalId, stack.stackName, resources);
36606
36606
  if (imageUri !== void 0) return extractImageLambdaProperties({
36607
36607
  stack,
36608
36608
  logicalId,
@@ -36699,7 +36699,7 @@ function extractEphemeralStorageMb(props, logicalId) {
36699
36699
  * for genuinely unrecognized shapes so the caller's downstream ZIP-vs-
36700
36700
  * IMAGE branching can route to its existing error path.
36701
36701
  */
36702
- function extractImageUri(value, logicalId, stackName, resources) {
36702
+ function extractImageUri$1(value, logicalId, stackName, resources) {
36703
36703
  if (typeof value === "string" && value.length > 0) return value;
36704
36704
  if (value && typeof value === "object" && !Array.isArray(value)) {
36705
36705
  const obj = value;
@@ -40347,31 +40347,56 @@ function createContainerPool(specs, options) {
40347
40347
  /**
40348
40348
  * Spin up one new container for the given Lambda spec. Returns a
40349
40349
  * handle the caller can write into the entry's data structures.
40350
+ *
40351
+ * Branches on `spec.kind`:
40352
+ * - `'zip'`: bind-mount the function's local code dir at
40353
+ * `/var/task` (or `/var/runtime` for `provided.*` runtimes),
40354
+ * base image from `public.ecr.aws/lambda/<lang>:<v>`, CMD =
40355
+ * `[<Handler>]`.
40356
+ * - `'image'`: no code bind-mount (image already includes the
40357
+ * code), base image is the pre-built local tag, CMD =
40358
+ * `ImageConfig.Command` (may be empty), optional EntryPoint /
40359
+ * WorkingDirectory / --platform applied verbatim.
40350
40360
  */
40351
40361
  async function startOne(spec) {
40352
- const image = resolveRuntimeImage(spec.lambda.runtime);
40353
40362
  const hostPort = await pickFreePort();
40354
40363
  const name = `cdkd-local-${spec.lambda.logicalId}-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
40355
- logger.debug(`Starting container ${name} for ${spec.lambda.logicalId} on ${spec.containerHost}:${hostPort}`);
40356
- const optMount = spec.optDir ? [{
40357
- hostPath: spec.optDir,
40358
- containerPath: "/opt",
40359
- readOnly: true
40360
- }] : [];
40361
- const containerCodePath = resolveRuntimeCodeMountPath(spec.lambda.runtime);
40362
- const containerId = await runDetached({
40363
- image,
40364
- mounts: [{
40365
- hostPath: spec.codeDir,
40366
- containerPath: containerCodePath,
40364
+ logger.debug(`Starting container ${name} for ${spec.lambda.logicalId} (kind=${spec.kind}) on ${spec.containerHost}:${hostPort}`);
40365
+ let containerId;
40366
+ if (spec.kind === "zip") {
40367
+ const optMount = spec.optDir ? [{
40368
+ hostPath: spec.optDir,
40369
+ containerPath: "/opt",
40367
40370
  readOnly: true
40368
- }],
40369
- extraMounts: optMount,
40371
+ }] : [];
40372
+ const containerCodePath = resolveRuntimeCodeMountPath(spec.lambda.runtime);
40373
+ containerId = await runDetached({
40374
+ image: resolveRuntimeImage(spec.lambda.runtime),
40375
+ mounts: [{
40376
+ hostPath: spec.codeDir,
40377
+ containerPath: containerCodePath,
40378
+ readOnly: true
40379
+ }],
40380
+ extraMounts: optMount,
40381
+ env: spec.env,
40382
+ cmd: [spec.lambda.handler],
40383
+ hostPort,
40384
+ host: spec.containerHost,
40385
+ name,
40386
+ ...spec.debugPort !== void 0 && { debugPort: spec.debugPort },
40387
+ ...spec.tmpfs !== void 0 && { tmpfs: spec.tmpfs }
40388
+ });
40389
+ } else containerId = await runDetached({
40390
+ image: spec.image,
40391
+ mounts: [],
40370
40392
  env: spec.env,
40371
- cmd: [spec.lambda.handler],
40393
+ cmd: spec.command,
40372
40394
  hostPort,
40373
40395
  host: spec.containerHost,
40374
40396
  name,
40397
+ platform: spec.platform,
40398
+ ...spec.entryPoint !== void 0 && { entryPoint: spec.entryPoint },
40399
+ ...spec.workingDir !== void 0 && { workingDir: spec.workingDir },
40375
40400
  ...spec.debugPort !== void 0 && { debugPort: spec.debugPort },
40376
40401
  ...spec.tmpfs !== void 0 && { tmpfs: spec.tmpfs }
40377
40402
  });
@@ -44142,12 +44167,13 @@ async function localStartApiCommand(target, options) {
44142
44167
  inlineTmpDirs,
44143
44168
  layerTmpDirs,
44144
44169
  stateByStack,
44170
+ skipPull: options.pull === false,
44145
44171
  ...options.layerRoleArn !== void 0 && { layerRoleArn: options.layerRoleArn }
44146
44172
  });
44147
44173
  specs.set(logicalId, spec);
44148
44174
  }
44149
44175
  const distinctImages = /* @__PURE__ */ new Set();
44150
- for (const spec of specs.values()) distinctImages.add(resolveRuntimeImage(spec.lambda.runtime));
44176
+ for (const spec of specs.values()) if (spec.kind === "zip") distinctImages.add(resolveRuntimeImage(spec.lambda.runtime));
44151
44177
  for (const image of distinctImages) await pullImage(image, options.pull === false);
44152
44178
  return {
44153
44179
  routes: routesWithAuth,
@@ -44176,13 +44202,23 @@ async function localStartApiCommand(target, options) {
44176
44202
  /**
44177
44203
  * Compute the watched-asset list from a spec map. Pure helper —
44178
44204
  * keeps the side-effect (`lastAssetPaths.value = ...`) confined to
44179
- * the post-swap call sites (initial boot + post-reload). `codeDir`
44180
- * is either the unzipped asset directory or the inline-code tmpdir;
44181
- * both are watch-worthy.
44205
+ * the post-swap call sites (initial boot + post-reload). For ZIP
44206
+ * Lambdas `codeDir` is either the unzipped asset directory or the
44207
+ * inline-code tmpdir; both are watch-worthy. IMAGE Lambdas
44208
+ * (`kind: 'image'`) don't have a host-side bind-mount source — the
44209
+ * code is baked into the docker image at build time. Their build
44210
+ * context (Dockerfile + source directory) is rebuilt on every
44211
+ * reload via `synthesizeAndBuild` → `buildContainerSpec` →
44212
+ * `resolveContainerImageForStartApi`, so a source edit DOES trigger
44213
+ * rebuild AND the deterministic `image` tag changes — but watching
44214
+ * the build-context dir explicitly here is deferred to a follow-up
44215
+ * (the watched-asset list is currently sourced from `cdk.out/`
44216
+ * which transitively covers most container-Lambda asset dirs since
44217
+ * `cdk synth` re-stages them on every synth call).
44182
44218
  */
44183
44219
  const computeAssetPaths = (specs) => {
44184
44220
  const assetPaths = /* @__PURE__ */ new Set();
44185
- for (const spec of specs.values()) assetPaths.add(spec.codeDir);
44221
+ for (const spec of specs.values()) if (spec.kind === "zip") assetPaths.add(spec.codeDir);
44186
44222
  return [...assetPaths];
44187
44223
  };
44188
44224
  const initialMaterial = await synthesizeAndBuild();
@@ -44455,10 +44491,19 @@ function warnIamRoutes(routesWithAuth) {
44455
44491
  * missing, runtime not supported).
44456
44492
  */
44457
44493
  async function buildContainerSpec(args) {
44458
- const { logicalId, stacks, overrides, assumeRole, containerHost, debugPort, stsRegion, inlineTmpDirs, layerTmpDirs, stateByStack, layerRoleArn } = args;
44494
+ const { logicalId, stacks, overrides, assumeRole, containerHost, debugPort, stsRegion, inlineTmpDirs, layerTmpDirs, stateByStack, skipPull, layerRoleArn } = args;
44459
44495
  const lambda = resolveLambdaByLogicalId(logicalId, stacks);
44460
- const codeDir = lambda.codePath ?? materializeInlineCode$1(lambda.handler, lambda.inlineCode ?? "", resolveRuntimeFileExtension(lambda.runtime), inlineTmpDirs);
44461
- const optDir = await materializeLambdaLayers$1(lambda.layers, layerTmpDirs, layerRoleArn);
44496
+ let codeDir;
44497
+ let optDir;
44498
+ let imageRef;
44499
+ let platform;
44500
+ if (lambda.kind === "zip") {
44501
+ codeDir = lambda.codePath ?? materializeInlineCode$1(lambda.handler, lambda.inlineCode ?? "", resolveRuntimeFileExtension(lambda.runtime), inlineTmpDirs);
44502
+ optDir = await materializeLambdaLayers$1(lambda.layers, layerTmpDirs, layerRoleArn);
44503
+ } else {
44504
+ imageRef = (await resolveContainerImageForStartApi(lambda, skipPull)).imageRef;
44505
+ platform = architectureToPlatform(lambda.architecture);
44506
+ }
44462
44507
  let templateEnv = getTemplateEnv$1(lambda.resource);
44463
44508
  const stateBundle = stateByStack.get(lambda.stack.stackName);
44464
44509
  let stateAudit;
@@ -44498,7 +44543,8 @@ async function buildContainerSpec(args) {
44498
44543
  target: "/tmp",
44499
44544
  sizeMb: lambda.ephemeralStorageMb
44500
44545
  } : void 0;
44501
- return {
44546
+ if (lambda.kind === "zip") return {
44547
+ kind: "zip",
44502
44548
  lambda,
44503
44549
  codeDir,
44504
44550
  env: dockerEnv,
@@ -44507,6 +44553,60 @@ async function buildContainerSpec(args) {
44507
44553
  ...debugPort !== void 0 && { debugPort },
44508
44554
  ...tmpfs !== void 0 && { tmpfs }
44509
44555
  };
44556
+ return {
44557
+ kind: "image",
44558
+ lambda,
44559
+ image: imageRef,
44560
+ platform,
44561
+ command: lambda.imageConfig.command ?? [],
44562
+ ...lambda.imageConfig.entryPoint !== void 0 && lambda.imageConfig.entryPoint.length > 0 && { entryPoint: lambda.imageConfig.entryPoint },
44563
+ ...lambda.imageConfig.workingDirectory !== void 0 && { workingDir: lambda.imageConfig.workingDirectory },
44564
+ env: dockerEnv,
44565
+ containerHost,
44566
+ ...debugPort !== void 0 && { debugPort },
44567
+ ...tmpfs !== void 0 && { tmpfs }
44568
+ };
44569
+ }
44570
+ /**
44571
+ * Resolve a container Lambda's local docker image — local build from
44572
+ * `cdk.out` asset manifest first, ECR-pull fallback when the asset
44573
+ * manifest has no matching entry. Mirrors `cdkd local invoke`'s
44574
+ * `resolveContainerImagePlan` shape; the start-api server doesn't
44575
+ * need the no-build flag (deterministic-tag cache reuse is automatic
44576
+ * across reloads because the per-Lambda tag is content-addressed).
44577
+ *
44578
+ * Same-account / same-region only on the ECR-pull path (matches the
44579
+ * `cdkd local invoke` PR 5 of #224 boundary). Cross-account /
44580
+ * cross-region ECR pull is the W2-1 deferred follow-up.
44581
+ */
44582
+ async function resolveContainerImageForStartApi(lambda, skipPull) {
44583
+ const logger = getLogger();
44584
+ const localBuild = await resolveLocalBuildPlan$1(lambda);
44585
+ if (localBuild) return { imageRef: await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, { architecture: lambda.architecture }) };
44586
+ if (!parseEcrUri(lambda.imageUri)) throw new Error(`Container Lambda '${lambda.logicalId}' has no matching asset in cdk.out, and Code.ImageUri '${lambda.imageUri}' is not an ECR URI cdkd can authenticate against. Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.`);
44587
+ logger.info(`No matching cdk.out asset for ${lambda.imageUri}; falling back to ECR pull (same-acct/region only)...`);
44588
+ return { imageRef: await pullEcrImage(lambda.imageUri, { skipPull }) };
44589
+ }
44590
+ /**
44591
+ * Look up the docker image asset that backs a container Lambda.
44592
+ * Returns `undefined` when the asset manifest has no matching entry —
44593
+ * the caller falls back to the ECR-pull path.
44594
+ *
44595
+ * Mirrors `local-invoke.ts:resolveLocalBuildPlan`; kept separate so
44596
+ * the two commands evolve their asset-lookup heuristics independently.
44597
+ */
44598
+ async function resolveLocalBuildPlan$1(lambda) {
44599
+ const manifestPath = lambda.stack.assetManifestPath;
44600
+ if (!manifestPath) return void 0;
44601
+ const cdkOutDir = path.dirname(manifestPath);
44602
+ const manifest = await new AssetManifestLoader().loadManifest(cdkOutDir, lambda.stack.stackName);
44603
+ if (!manifest) return void 0;
44604
+ const entry = getDockerImageBySourceHash(manifest, lambda.imageUri);
44605
+ if (!entry) return void 0;
44606
+ return {
44607
+ asset: entry.asset,
44608
+ cdkOutDir
44609
+ };
44510
44610
  }
44511
44611
  /**
44512
44612
  * Build the `/opt` bind-mount source for a Lambda's layers. Mirrors
@@ -44568,15 +44668,23 @@ function resolveLambdaByLogicalId(logicalId, stacks) {
44568
44668
  const resource = stack.template.Resources?.[logicalId];
44569
44669
  if (!resource || resource.Type !== "AWS::Lambda::Function") continue;
44570
44670
  const props = resource.Properties ?? {};
44571
- const runtime = typeof props["Runtime"] === "string" ? props["Runtime"] : "";
44572
- const handler = typeof props["Handler"] === "string" ? props["Handler"] : "";
44573
44671
  const memoryMb = typeof props["MemorySize"] === "number" ? props["MemorySize"] : 128;
44574
44672
  const timeoutSec = typeof props["Timeout"] === "number" ? props["Timeout"] : 3;
44575
- if (!runtime) throw new Error(`Lambda '${logicalId}' has no Runtime property. Container-image Lambdas (Code.ImageUri) are not supported in cdkd local start-api v1.`);
44576
- if (!handler) throw new Error(`Lambda '${logicalId}' has no Handler property.`);
44577
44673
  const code = props["Code"] ?? {};
44578
- const imageUri = code["ImageUri"];
44579
- if (typeof imageUri === "string" || typeof imageUri === "object" && imageUri !== null && "Fn::Sub" in imageUri) throw new Error(`Lambda '${logicalId}' uses Code.ImageUri (container-image Lambda). 'cdkd local start-api' v1 supports ZIP Lambdas only — container-image support is deferred to a follow-up PR. Use 'cdkd local invoke' to exercise this function locally.`);
44674
+ const imageUri = extractImageUri(code["ImageUri"]);
44675
+ if (imageUri !== void 0) return resolveImageLambda({
44676
+ stack,
44677
+ logicalId,
44678
+ resource,
44679
+ props,
44680
+ memoryMb,
44681
+ timeoutSec,
44682
+ imageUri
44683
+ });
44684
+ const runtime = typeof props["Runtime"] === "string" ? props["Runtime"] : "";
44685
+ const handler = typeof props["Handler"] === "string" ? props["Handler"] : "";
44686
+ if (!runtime) throw new Error(`Lambda '${logicalId}' has no Runtime property and no Code.ImageUri. cdkd local start-api cannot tell if this is a ZIP or a container Lambda.`);
44687
+ if (!handler) throw new Error(`Lambda '${logicalId}' has no Handler property.`);
44580
44688
  const inlineCode = typeof code["ZipFile"] === "string" ? code["ZipFile"] : void 0;
44581
44689
  let codePath = null;
44582
44690
  if (!inlineCode) codePath = resolveAssetCodePath(stack, logicalId, resource);
@@ -44600,6 +44708,66 @@ function resolveLambdaByLogicalId(logicalId, stacks) {
44600
44708
  throw new Error(`No AWS::Lambda::Function resource named '${logicalId}' found in target stacks. This is likely a synthesis bug — the route-discovery phase resolved a route to this logical ID.`);
44601
44709
  }
44602
44710
  /**
44711
+ * Extract `Code.ImageUri` across the shapes CDK actually synthesizes.
44712
+ * Mirrors the simpler subset of `lambda-resolver.ts:extractImageUri`
44713
+ * scoped to the shapes `cdkd local start-api` consumes — flat string
44714
+ * and `Fn::Sub` (the canonical asset shape for
44715
+ * `lambda.DockerImageCode.fromImageAsset`). `Fn::Join` shapes for
44716
+ * `lambda.DockerImageCode.fromEcr` are deferred to a follow-up: the
44717
+ * start-api boot flow doesn't yet load cdkd state up front, and the
44718
+ * `Fn::Join` resolver needs it to recover same-stack ECR repository
44719
+ * URIs. When the user hits the unsupported shape, the downstream
44720
+ * resolveLocalBuildPlan / pullEcrImage path surfaces a clear error.
44721
+ *
44722
+ * Returns `undefined` when the field is absent or non-recognized,
44723
+ * which routes the caller to the ZIP branch (with its existing
44724
+ * "no Runtime / no Handler" validations).
44725
+ */
44726
+ function extractImageUri(value) {
44727
+ if (typeof value === "string" && value.length > 0) return value;
44728
+ if (value && typeof value === "object" && !Array.isArray(value)) {
44729
+ const sub = value["Fn::Sub"];
44730
+ if (typeof sub === "string" && sub.length > 0) return sub;
44731
+ if (Array.isArray(sub) && typeof sub[0] === "string") return sub[0];
44732
+ }
44733
+ }
44734
+ /**
44735
+ * Build the IMAGE-variant `ResolvedStartApiLambda` from a Lambda
44736
+ * template entry with `Code.ImageUri`. Mirrors
44737
+ * `lambda-resolver.ts:extractImageLambdaProperties` but trimmed to the
44738
+ * fields `cdkd local start-api` actually consumes.
44739
+ */
44740
+ function resolveImageLambda(args) {
44741
+ const { stack, logicalId, resource, props, memoryMb, timeoutSec, imageUri } = args;
44742
+ const rawImageConfig = props["ImageConfig"] ?? {};
44743
+ const imageConfig = {};
44744
+ if (Array.isArray(rawImageConfig["Command"])) imageConfig.command = rawImageConfig["Command"].filter((s) => typeof s === "string");
44745
+ if (Array.isArray(rawImageConfig["EntryPoint"])) imageConfig.entryPoint = rawImageConfig["EntryPoint"].filter((s) => typeof s === "string");
44746
+ if (typeof rawImageConfig["WorkingDirectory"] === "string") imageConfig.workingDirectory = rawImageConfig["WorkingDirectory"];
44747
+ const arches = props["Architectures"];
44748
+ let architecture = "x86_64";
44749
+ if (Array.isArray(arches) && arches.length > 0) {
44750
+ const first = arches[0];
44751
+ if (first === "arm64") architecture = "arm64";
44752
+ else if (first === "x86_64") architecture = "x86_64";
44753
+ else throw new Error(`Lambda '${logicalId}' has unsupported Architectures value '${String(first)}'. cdkd local start-api supports x86_64 and arm64.`);
44754
+ }
44755
+ const ephemeralStorageMb = extractEphemeralStorageMb(props, logicalId);
44756
+ return {
44757
+ kind: "image",
44758
+ stack,
44759
+ logicalId,
44760
+ resource,
44761
+ memoryMb,
44762
+ timeoutSec,
44763
+ imageUri,
44764
+ imageConfig,
44765
+ architecture,
44766
+ layers: [],
44767
+ ...ephemeralStorageMb !== void 0 && { ephemeralStorageMb }
44768
+ };
44769
+ }
44770
+ /**
44603
44771
  * Locate the Lambda's local code directory using the CDK-blessed
44604
44772
  * `Metadata['aws:asset:path']` hint. Bind-mounted directly at
44605
44773
  * `/var/task` (read-only) by the docker-runner.
@@ -48129,7 +48297,7 @@ function reorderArgs(argv) {
48129
48297
  */
48130
48298
  async function main() {
48131
48299
  const program = new Command();
48132
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.127.0");
48300
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.128.0");
48133
48301
  program.addCommand(createBootstrapCommand());
48134
48302
  program.addCommand(createSynthCommand());
48135
48303
  program.addCommand(createListCommand());