@go-to-k/cdkd 0.73.0 → 0.74.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
@@ -634,6 +634,19 @@ cdkd local invoke MyStack/Handler --debug-port 9229
634
634
  cdkd local invoke MyStack/Handler --from-state
635
635
  ```
636
636
 
637
+ **Lambda Layers (PR 6 of #224, issue #232)** — same-stack
638
+ `AWS::Lambda::LayerVersion` references in `Properties.Layers` are
639
+ resolved automatically and bind-mounted at `/opt` (read-only) inside
640
+ the container. Each layer's unzipped asset directory under `cdk.out/`
641
+ becomes one `-v <layerAssetPath>:/opt:ro` mount; multiple layers
642
+ stack via Docker overlay layering, and AWS's "last layer wins on
643
+ file collision" rule is preserved by keeping the template's input
644
+ order. Cross-stack / cross-account / cross-region layer ARNs (literal
645
+ ARN strings in `Properties.Layers`) are out of scope for v1 — cdkd
646
+ hard-errors with a clear pointer at the offending entry. Container
647
+ Lambdas (`Code.ImageUri`) silently ignore `Layers` (matches AWS:
648
+ container images bake layers at build time).
649
+
637
650
  See [docs/cli-reference.md](docs/cli-reference.md#local-invoke-run-lambda-functions-locally)
638
651
  for the full surface, target-resolution rules, and v1 scope notes.
639
652
 
package/dist/cli.js CHANGED
@@ -70034,7 +70034,7 @@ async function captureObservedForImportedResources(stackState, providerRegistry,
70034
70034
  }
70035
70035
 
70036
70036
  // src/cli/commands/local-invoke.ts
70037
- import { mkdtempSync as mkdtempSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
70037
+ import { cpSync as cpSync2, mkdtempSync as mkdtempSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
70038
70038
  import { tmpdir as tmpdir3 } from "node:os";
70039
70039
  import { dirname as dirname3 } from "node:path";
70040
70040
  import * as path2 from "node:path";
@@ -70169,6 +70169,7 @@ function extractLambdaProperties(stack, logicalId, resource) {
70169
70169
  if (!inlineCode) {
70170
70170
  codePath = resolveAssetCodePath(stack, logicalId, resource);
70171
70171
  }
70172
+ const layers = resolveLambdaLayers(stack, logicalId, props);
70172
70173
  return {
70173
70174
  kind: "zip",
70174
70175
  stack,
@@ -70179,6 +70180,7 @@ function extractLambdaProperties(stack, logicalId, resource) {
70179
70180
  memoryMb,
70180
70181
  timeoutSec,
70181
70182
  codePath,
70183
+ layers,
70182
70184
  ...inlineCode !== void 0 && { inlineCode }
70183
70185
  };
70184
70186
  }
@@ -70235,7 +70237,8 @@ function extractImageLambdaProperties(args) {
70235
70237
  timeoutSec,
70236
70238
  imageUri,
70237
70239
  imageConfig,
70238
- architecture
70240
+ architecture,
70241
+ layers: []
70239
70242
  };
70240
70243
  }
70241
70244
  function resolveAssetCodePath(stack, logicalId, resource) {
@@ -70255,6 +70258,72 @@ function resolveAssetCodePath(stack, logicalId, resource) {
70255
70258
  }
70256
70259
  return abs;
70257
70260
  }
70261
+ function resolveLambdaLayers(stack, logicalId, props) {
70262
+ const layers = props["Layers"];
70263
+ if (layers === void 0)
70264
+ return [];
70265
+ if (!Array.isArray(layers)) {
70266
+ throw new LocalInvokeResolutionError(
70267
+ `Lambda '${logicalId}' has a non-array Layers property. Expected an array of LayerVersion references.`
70268
+ );
70269
+ }
70270
+ if (layers.length === 0)
70271
+ return [];
70272
+ const resources = stack.template.Resources ?? {};
70273
+ const out = [];
70274
+ for (let i = 0; i < layers.length; i++) {
70275
+ const entry = layers[i];
70276
+ const layerLogicalId = pickLayerLogicalId(entry);
70277
+ if (!layerLogicalId) {
70278
+ throw new LocalInvokeResolutionError(
70279
+ `Lambda '${logicalId}' has a Layers entry [${i}] cdkd cannot resolve locally: ${describeLayerEntry(entry)}. Only same-stack Ref / Fn::GetAtt to an AWS::Lambda::LayerVersion are supported in v1; cross-account / cross-region / pre-existing-ARN layers are deferred to a follow-up PR.`
70280
+ );
70281
+ }
70282
+ const layerResource = resources[layerLogicalId];
70283
+ if (!layerResource) {
70284
+ throw new LocalInvokeResolutionError(
70285
+ `Lambda '${logicalId}' Layers entry [${i}] references '${layerLogicalId}', but no resource with that logical ID exists in stack '${stack.stackName}'.`
70286
+ );
70287
+ }
70288
+ if (layerResource.Type !== "AWS::Lambda::LayerVersion") {
70289
+ throw new LocalInvokeResolutionError(
70290
+ `Lambda '${logicalId}' Layers entry [${i}] references '${layerLogicalId}' (${layerResource.Type}), which is not an AWS::Lambda::LayerVersion.`
70291
+ );
70292
+ }
70293
+ const assetPath = resolveAssetCodePath(stack, layerLogicalId, layerResource);
70294
+ out.push({ logicalId: layerLogicalId, assetPath });
70295
+ }
70296
+ return out;
70297
+ }
70298
+ function pickLayerLogicalId(entry) {
70299
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry))
70300
+ return void 0;
70301
+ const obj = entry;
70302
+ if (typeof obj["Ref"] === "string")
70303
+ return obj["Ref"];
70304
+ if ("Fn::GetAtt" in obj) {
70305
+ const arg = obj["Fn::GetAtt"];
70306
+ if (Array.isArray(arg) && typeof arg[0] === "string")
70307
+ return arg[0];
70308
+ if (typeof arg === "string")
70309
+ return arg.split(".")[0];
70310
+ }
70311
+ return void 0;
70312
+ }
70313
+ function describeLayerEntry(entry) {
70314
+ if (typeof entry === "string")
70315
+ return `literal ARN '${entry}'`;
70316
+ if (entry === null)
70317
+ return "null";
70318
+ if (typeof entry !== "object")
70319
+ return String(entry);
70320
+ try {
70321
+ const json = JSON.stringify(entry);
70322
+ return json.length > 120 ? json.substring(0, 117) + "..." : json;
70323
+ } catch {
70324
+ return Object.prototype.toString.call(entry);
70325
+ }
70326
+ }
70258
70327
  function notFoundError(target, stack, resources) {
70259
70328
  const lambdas = [];
70260
70329
  for (const [logicalId, resource] of Object.entries(resources)) {
@@ -70578,6 +70647,12 @@ async function runDetached(opts) {
70578
70647
  const ro = mount.readOnly ? ":ro" : "";
70579
70648
  args.push("-v", `${mount.hostPath}:${mount.containerPath}${ro}`);
70580
70649
  }
70650
+ if (opts.extraMounts) {
70651
+ for (const mount of opts.extraMounts) {
70652
+ const ro = mount.readOnly ? ":ro" : "";
70653
+ args.push("-v", `${mount.hostPath}:${mount.containerPath}${ro}`);
70654
+ }
70655
+ }
70581
70656
  for (const [k, v] of Object.entries(opts.env)) {
70582
70657
  args.push("-e", `${k}=${v}`);
70583
70658
  }
@@ -71089,7 +71164,7 @@ function extractHashFromImageUri(imageUri) {
71089
71164
  init_aws_clients();
71090
71165
 
71091
71166
  // src/cli/commands/local-start-api.ts
71092
- import { mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
71167
+ import { cpSync, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
71093
71168
  import { tmpdir as tmpdir2 } from "node:os";
71094
71169
  import * as path from "node:path";
71095
71170
  import { Command as Command14, Option as Option7 } from "commander";
@@ -71480,9 +71555,11 @@ function createContainerPool(specs, options) {
71480
71555
  logger.debug(
71481
71556
  `Starting container ${name} for ${spec.lambda.logicalId} on ${spec.containerHost}:${hostPort}`
71482
71557
  );
71558
+ const optMount = spec.optDir ? [{ hostPath: spec.optDir, containerPath: "/opt", readOnly: true }] : [];
71483
71559
  const containerId = await runDetached({
71484
71560
  image,
71485
71561
  mounts: [{ hostPath: spec.codeDir, containerPath: "/var/task", readOnly: true }],
71562
+ extraMounts: optMount,
71486
71563
  env: spec.env,
71487
71564
  cmd: [spec.lambda.handler],
71488
71565
  hostPort,
@@ -73465,6 +73542,7 @@ async function localStartApiCommand(options) {
73465
73542
  const debugPortBase = options.debugPortBase ? parseDebugPort(options.debugPortBase) : void 0;
73466
73543
  const specs = /* @__PURE__ */ new Map();
73467
73544
  const inlineTmpDirs = /* @__PURE__ */ new Set();
73545
+ const layerTmpDirs = /* @__PURE__ */ new Set();
73468
73546
  for (let i = 0; i < lambdaIds.length; i++) {
73469
73547
  const logicalId = lambdaIds[i];
73470
73548
  const spec = await buildContainerSpec({
@@ -73475,7 +73553,8 @@ async function localStartApiCommand(options) {
73475
73553
  containerHost: options.containerHost,
73476
73554
  ...debugPortBase !== void 0 && { debugPort: debugPortBase + i },
73477
73555
  stsRegion: options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"],
73478
- inlineTmpDirs
73556
+ inlineTmpDirs,
73557
+ layerTmpDirs
73479
73558
  });
73480
73559
  specs.set(logicalId, spec);
73481
73560
  }
@@ -73570,6 +73649,15 @@ async function localStartApiCommand(options) {
73570
73649
  );
73571
73650
  }
73572
73651
  }
73652
+ for (const dir of layerTmpDirs) {
73653
+ try {
73654
+ rmSync2(dir, { recursive: true, force: true });
73655
+ } catch (err) {
73656
+ logger.warn(
73657
+ `Failed to remove merged-layers tmpdir ${dir}: ${err instanceof Error ? err.message : String(err)}`
73658
+ );
73659
+ }
73660
+ }
73573
73661
  process.exit(exitCode);
73574
73662
  };
73575
73663
  process.on("SIGINT", () => {
@@ -73683,7 +73771,8 @@ async function buildContainerSpec(args) {
73683
73771
  containerHost,
73684
73772
  debugPort,
73685
73773
  stsRegion,
73686
- inlineTmpDirs
73774
+ inlineTmpDirs,
73775
+ layerTmpDirs
73687
73776
  } = args;
73688
73777
  const lambda = resolveLambdaByLogicalId(logicalId, stacks);
73689
73778
  const codeDir = lambda.codePath ?? materializeInlineCode(
@@ -73692,6 +73781,7 @@ async function buildContainerSpec(args) {
73692
73781
  resolveRuntimeFileExtension(lambda.runtime),
73693
73782
  inlineTmpDirs
73694
73783
  );
73784
+ const optDir = materializeLambdaLayers(lambda.layers, layerTmpDirs);
73695
73785
  const templateEnv = getTemplateEnv(lambda.resource);
73696
73786
  const envResult = resolveEnvVars(logicalId, templateEnv, overrides);
73697
73787
  for (const key of envResult.unresolved) {
@@ -73727,10 +73817,23 @@ async function buildContainerSpec(args) {
73727
73817
  codeDir,
73728
73818
  env: dockerEnv,
73729
73819
  containerHost,
73820
+ ...optDir !== void 0 && { optDir },
73730
73821
  ...debugPort !== void 0 && { debugPort }
73731
73822
  };
73732
73823
  return spec;
73733
73824
  }
73825
+ function materializeLambdaLayers(layers, layerTmpDirs) {
73826
+ if (layers.length === 0)
73827
+ return void 0;
73828
+ if (layers.length === 1)
73829
+ return layers[0].assetPath;
73830
+ const dir = mkdtempSync2(path.join(tmpdir2(), "cdkd-local-start-api-layers-"));
73831
+ for (const layer of layers) {
73832
+ cpSync(layer.assetPath, dir, { recursive: true, force: true });
73833
+ }
73834
+ layerTmpDirs.add(dir);
73835
+ return dir;
73836
+ }
73734
73837
  function resolveLambdaByLogicalId(logicalId, stacks) {
73735
73838
  for (const stack of stacks) {
73736
73839
  const resource = stack.template.Resources?.[logicalId];
@@ -73761,6 +73864,7 @@ function resolveLambdaByLogicalId(logicalId, stacks) {
73761
73864
  if (!inlineCode) {
73762
73865
  codePath = resolveAssetCodePath2(stack, logicalId, resource);
73763
73866
  }
73867
+ const layers = resolveLambdaLayers(stack, logicalId, props);
73764
73868
  return {
73765
73869
  kind: "zip",
73766
73870
  stack,
@@ -73771,6 +73875,7 @@ function resolveLambdaByLogicalId(logicalId, stacks) {
73771
73875
  memoryMb,
73772
73876
  timeoutSec,
73773
73877
  codePath,
73878
+ layers,
73774
73879
  ...inlineCode !== void 0 && { inlineCode }
73775
73880
  };
73776
73881
  }
@@ -73957,123 +74062,170 @@ async function localInvokeCommand(target, options) {
73957
74062
  logger.setLevel("debug");
73958
74063
  }
73959
74064
  warnIfDeprecatedRegion(options);
73960
- await applyRoleArnIfSet({ roleArn: options.roleArn, region: options.region });
73961
- await ensureDockerAvailable();
73962
- const appCmd = resolveApp(options.app);
73963
- if (!appCmd) {
73964
- throw new Error('No CDK app specified. Pass --app, set CDKD_APP, or add "app" to cdk.json.');
73965
- }
73966
- logger.info("Synthesizing CDK app...");
73967
- const synthesizer = new Synthesizer();
73968
- const context = parseContextOptions(options.context);
73969
- const synthOpts = {
73970
- app: appCmd,
73971
- output: options.output,
73972
- ...options.region && { region: options.region },
73973
- ...options.profile && { profile: options.profile },
73974
- ...Object.keys(context).length > 0 && { context }
73975
- };
73976
- const { stacks } = await synthesizer.synthesize(synthOpts);
73977
- const lambda = resolveLambdaTarget(target, stacks);
73978
- const targetLabel = lambda.kind === "zip" ? lambda.runtime : "container image";
73979
- logger.info(`Target: ${lambda.stack.stackName}/${lambda.logicalId} (${targetLabel})`);
73980
- const imagePlan = await resolveImagePlan(lambda, options);
73981
- let stateAudit;
73982
- let templateEnv = getTemplateEnv2(lambda.resource);
73983
- let stateForRoleHint;
73984
- if (options.fromState) {
73985
- const loaded = await loadStateForStack(lambda.stack.stackName, lambda.stack.region, {
73986
- ...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
73987
- ...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
73988
- statePrefix: options.statePrefix,
73989
- ...options.region !== void 0 && { region: options.region },
73990
- ...options.profile !== void 0 && { profile: options.profile }
73991
- });
73992
- if (loaded) {
73993
- stateForRoleHint = loaded.state;
73994
- const { env, audit } = substituteEnvVarsFromState(templateEnv, loaded.state.resources);
73995
- templateEnv = env;
73996
- stateAudit = audit;
73997
- for (const key of audit.resolvedKeys) {
73998
- logger.debug(`--from-state: substituted env var ${key} from cdkd state`);
73999
- }
74000
- for (const { key, reason } of audit.unresolved) {
74001
- logger.warn(
74002
- `--from-state: could not substitute env var ${key} (${reason}). Override it via --env-vars or it will be dropped.`
74065
+ let imagePlan;
74066
+ let containerId;
74067
+ let stopLogs;
74068
+ let sigintHandler;
74069
+ const cleanup = async () => {
74070
+ if (stopLogs) {
74071
+ try {
74072
+ stopLogs();
74073
+ } catch (err) {
74074
+ getLogger().debug(
74075
+ `streamLogs stop failed: ${err instanceof Error ? err.message : String(err)}`
74076
+ );
74077
+ }
74078
+ }
74079
+ if (containerId) {
74080
+ try {
74081
+ await removeContainer(containerId);
74082
+ } catch (err) {
74083
+ getLogger().debug(
74084
+ `removeContainer(${containerId}) failed: ${err instanceof Error ? err.message : String(err)}`
74085
+ );
74086
+ }
74087
+ }
74088
+ if (imagePlan?.inlineTmpDir) {
74089
+ try {
74090
+ rmSync3(imagePlan.inlineTmpDir, { recursive: true, force: true });
74091
+ } catch (err) {
74092
+ getLogger().debug(
74093
+ `Failed to remove inline-code tmpdir ${imagePlan.inlineTmpDir}: ${err instanceof Error ? err.message : String(err)}`
74094
+ );
74095
+ }
74096
+ }
74097
+ if (imagePlan?.layersTmpDir) {
74098
+ try {
74099
+ rmSync3(imagePlan.layersTmpDir, { recursive: true, force: true });
74100
+ } catch (err) {
74101
+ getLogger().debug(
74102
+ `Failed to remove merged-layers tmpdir ${imagePlan.layersTmpDir}: ${err instanceof Error ? err.message : String(err)}`
74003
74103
  );
74004
74104
  }
74005
74105
  }
74006
- }
74007
- const overrides = readEnvOverridesFile2(options.envVars);
74008
- const envResult = resolveEnvVars(lambda.logicalId, templateEnv, overrides);
74009
- for (const key of envResult.unresolved) {
74010
- if (stateAudit && stateAudit.unresolved.some((u) => u.key === key))
74011
- continue;
74012
- logger.warn(
74013
- `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 to recover deployed values.`
74014
- );
74015
- }
74016
- if (options.fromState && !options.assumeRole && stateForRoleHint) {
74017
- suggestAssumeRoleFromState(stateForRoleHint, lambda.logicalId);
74018
- }
74019
- const event = await readEvent(options);
74020
- const dockerEnv = {
74021
- AWS_LAMBDA_FUNCTION_NAME: lambda.logicalId,
74022
- AWS_LAMBDA_FUNCTION_MEMORY_SIZE: String(lambda.memoryMb),
74023
- AWS_LAMBDA_FUNCTION_TIMEOUT: String(lambda.timeoutSec),
74024
- AWS_LAMBDA_FUNCTION_VERSION: "$LATEST",
74025
- AWS_LAMBDA_LOG_GROUP_NAME: `/aws/lambda/${lambda.logicalId}`,
74026
- AWS_LAMBDA_LOG_STREAM_NAME: "local",
74027
- ...envResult.resolved
74028
74106
  };
74029
- if (options.assumeRole) {
74030
- const stsRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"];
74031
- const creds = await assumeLambdaExecutionRole2(options.assumeRole, stsRegion);
74032
- dockerEnv["AWS_ACCESS_KEY_ID"] = creds.accessKeyId;
74033
- dockerEnv["AWS_SECRET_ACCESS_KEY"] = creds.secretAccessKey;
74034
- dockerEnv["AWS_SESSION_TOKEN"] = creds.sessionToken;
74035
- if (stsRegion)
74036
- dockerEnv["AWS_REGION"] = stsRegion;
74037
- } else {
74038
- forwardAwsEnv2(dockerEnv);
74039
- }
74040
- let debugPort;
74041
- if (options.debugPort) {
74042
- debugPort = Number(options.debugPort);
74043
- if (!Number.isInteger(debugPort) || debugPort <= 0 || debugPort > 65535) {
74044
- throw new Error(`--debug-port must be an integer in 1..65535, got '${options.debugPort}'`);
74107
+ try {
74108
+ await applyRoleArnIfSet({ roleArn: options.roleArn, region: options.region });
74109
+ await ensureDockerAvailable();
74110
+ const appCmd = resolveApp(options.app);
74111
+ if (!appCmd) {
74112
+ throw new Error('No CDK app specified. Pass --app, set CDKD_APP, or add "app" to cdk.json.');
74045
74113
  }
74046
- dockerEnv["NODE_OPTIONS"] = `--inspect-brk=0.0.0.0:${debugPort}`;
74047
- if (lambda.kind === "image") {
74114
+ logger.info("Synthesizing CDK app...");
74115
+ const synthesizer = new Synthesizer();
74116
+ const context = parseContextOptions(options.context);
74117
+ const synthOpts = {
74118
+ app: appCmd,
74119
+ output: options.output,
74120
+ ...options.region && { region: options.region },
74121
+ ...options.profile && { profile: options.profile },
74122
+ ...Object.keys(context).length > 0 && { context }
74123
+ };
74124
+ const { stacks } = await synthesizer.synthesize(synthOpts);
74125
+ const lambda = resolveLambdaTarget(target, stacks);
74126
+ const targetLabel = lambda.kind === "zip" ? lambda.runtime : "container image";
74127
+ logger.info(`Target: ${lambda.stack.stackName}/${lambda.logicalId} (${targetLabel})`);
74128
+ imagePlan = await resolveImagePlan(lambda, options);
74129
+ let stateAudit;
74130
+ let templateEnv = getTemplateEnv2(lambda.resource);
74131
+ let stateForRoleHint;
74132
+ if (options.fromState) {
74133
+ const loaded = await loadStateForStack(lambda.stack.stackName, lambda.stack.region, {
74134
+ ...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
74135
+ ...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
74136
+ statePrefix: options.statePrefix,
74137
+ ...options.region !== void 0 && { region: options.region },
74138
+ ...options.profile !== void 0 && { profile: options.profile }
74139
+ });
74140
+ if (loaded) {
74141
+ stateForRoleHint = loaded.state;
74142
+ const { env, audit } = substituteEnvVarsFromState(templateEnv, loaded.state.resources);
74143
+ templateEnv = env;
74144
+ stateAudit = audit;
74145
+ for (const key of audit.resolvedKeys) {
74146
+ logger.debug(`--from-state: substituted env var ${key} from cdkd state`);
74147
+ }
74148
+ for (const { key, reason } of audit.unresolved) {
74149
+ logger.warn(
74150
+ `--from-state: could not substitute env var ${key} (${reason}). Override it via --env-vars or it will be dropped.`
74151
+ );
74152
+ }
74153
+ }
74154
+ }
74155
+ const overrides = readEnvOverridesFile2(options.envVars);
74156
+ const envResult = resolveEnvVars(lambda.logicalId, templateEnv, overrides);
74157
+ for (const key of envResult.unresolved) {
74158
+ if (stateAudit && stateAudit.unresolved.some((u) => u.key === key))
74159
+ continue;
74048
74160
  logger.warn(
74049
- "--debug-port sets NODE_OPTIONS unconditionally on container Lambdas. If the image's runtime is not Node.js, this flag is a no-op."
74161
+ `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 to recover deployed values.`
74050
74162
  );
74051
74163
  }
74052
- }
74053
- const hostPort = await pickFreePort();
74054
- const containerHost = options.containerHost;
74055
- logger.info(`Starting container (image=${imagePlan.image}, port=${hostPort})...`);
74056
- const containerId = await runDetached({
74057
- image: imagePlan.image,
74058
- mounts: imagePlan.mounts,
74059
- env: dockerEnv,
74060
- cmd: imagePlan.cmd,
74061
- hostPort,
74062
- host: containerHost,
74063
- ...debugPort !== void 0 && { debugPort },
74064
- ...imagePlan.platform !== void 0 && { platform: imagePlan.platform },
74065
- ...imagePlan.entryPoint !== void 0 && { entryPoint: imagePlan.entryPoint },
74066
- ...imagePlan.workingDir !== void 0 && { workingDir: imagePlan.workingDir }
74067
- });
74068
- const stopLogs = streamLogs(containerId);
74069
- const sigintHandler = () => {
74070
- stopLogs();
74071
- void removeContainer(containerId).then(() => {
74072
- process.exit(130);
74073
- });
74074
- };
74075
- process.on("SIGINT", sigintHandler);
74076
- try {
74164
+ if (options.fromState && !options.assumeRole && stateForRoleHint) {
74165
+ suggestAssumeRoleFromState(stateForRoleHint, lambda.logicalId);
74166
+ }
74167
+ const event = await readEvent(options);
74168
+ const dockerEnv = {
74169
+ AWS_LAMBDA_FUNCTION_NAME: lambda.logicalId,
74170
+ AWS_LAMBDA_FUNCTION_MEMORY_SIZE: String(lambda.memoryMb),
74171
+ AWS_LAMBDA_FUNCTION_TIMEOUT: String(lambda.timeoutSec),
74172
+ AWS_LAMBDA_FUNCTION_VERSION: "$LATEST",
74173
+ AWS_LAMBDA_LOG_GROUP_NAME: `/aws/lambda/${lambda.logicalId}`,
74174
+ AWS_LAMBDA_LOG_STREAM_NAME: "local",
74175
+ ...envResult.resolved
74176
+ };
74177
+ if (options.assumeRole) {
74178
+ const stsRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"];
74179
+ const creds = await assumeLambdaExecutionRole2(options.assumeRole, stsRegion);
74180
+ dockerEnv["AWS_ACCESS_KEY_ID"] = creds.accessKeyId;
74181
+ dockerEnv["AWS_SECRET_ACCESS_KEY"] = creds.secretAccessKey;
74182
+ dockerEnv["AWS_SESSION_TOKEN"] = creds.sessionToken;
74183
+ if (stsRegion)
74184
+ dockerEnv["AWS_REGION"] = stsRegion;
74185
+ } else {
74186
+ forwardAwsEnv2(dockerEnv);
74187
+ }
74188
+ let debugPort;
74189
+ if (options.debugPort) {
74190
+ debugPort = Number(options.debugPort);
74191
+ if (!Number.isInteger(debugPort) || debugPort <= 0 || debugPort > 65535) {
74192
+ throw new Error(`--debug-port must be an integer in 1..65535, got '${options.debugPort}'`);
74193
+ }
74194
+ dockerEnv["NODE_OPTIONS"] = `--inspect-brk=0.0.0.0:${debugPort}`;
74195
+ if (lambda.kind === "image") {
74196
+ logger.warn(
74197
+ "--debug-port sets NODE_OPTIONS unconditionally on container Lambdas. If the image's runtime is not Node.js, this flag is a no-op."
74198
+ );
74199
+ }
74200
+ }
74201
+ const hostPort = await pickFreePort();
74202
+ const containerHost = options.containerHost;
74203
+ if (lambda.layers.length > 0) {
74204
+ logger.info(
74205
+ `Mounting ${lambda.layers.length} Lambda layer${lambda.layers.length === 1 ? "" : "s"} at /opt`
74206
+ );
74207
+ }
74208
+ logger.info(`Starting container (image=${imagePlan.image}, port=${hostPort})...`);
74209
+ containerId = await runDetached({
74210
+ image: imagePlan.image,
74211
+ mounts: imagePlan.mounts,
74212
+ extraMounts: imagePlan.extraMounts,
74213
+ env: dockerEnv,
74214
+ cmd: imagePlan.cmd,
74215
+ hostPort,
74216
+ host: containerHost,
74217
+ ...debugPort !== void 0 && { debugPort },
74218
+ ...imagePlan.platform !== void 0 && { platform: imagePlan.platform },
74219
+ ...imagePlan.entryPoint !== void 0 && { entryPoint: imagePlan.entryPoint },
74220
+ ...imagePlan.workingDir !== void 0 && { workingDir: imagePlan.workingDir }
74221
+ });
74222
+ stopLogs = streamLogs(containerId);
74223
+ sigintHandler = () => {
74224
+ void cleanup().then(() => {
74225
+ process.exit(130);
74226
+ });
74227
+ };
74228
+ process.on("SIGINT", sigintHandler);
74077
74229
  await waitForRieReady(containerHost, hostPort, 5e3);
74078
74230
  const invokeTimeoutMs = Math.max(3e4, lambda.timeoutSec * 2 * 1e3);
74079
74231
  const result = await invokeRie(containerHost, hostPort, event, invokeTimeoutMs);
@@ -74081,18 +74233,9 @@ async function localInvokeCommand(target, options) {
74081
74233
  process.stdout.write(`${result.raw}
74082
74234
  `);
74083
74235
  } finally {
74084
- process.off("SIGINT", sigintHandler);
74085
- stopLogs();
74086
- await removeContainer(containerId);
74087
- if (imagePlan.inlineTmpDir) {
74088
- try {
74089
- rmSync3(imagePlan.inlineTmpDir, { recursive: true, force: true });
74090
- } catch (err) {
74091
- getLogger().debug(
74092
- `Failed to remove inline-code tmpdir ${imagePlan.inlineTmpDir}: ${err instanceof Error ? err.message : String(err)}`
74093
- );
74094
- }
74095
- }
74236
+ if (sigintHandler)
74237
+ process.off("SIGINT", sigintHandler);
74238
+ await cleanup();
74096
74239
  }
74097
74240
  }
74098
74241
  async function resolveImagePlan(lambda, options) {
@@ -74114,11 +74257,31 @@ async function resolveZipImagePlan(lambda, options) {
74114
74257
  }
74115
74258
  const image = resolveRuntimeImage(lambda.runtime);
74116
74259
  await pullImage(image, options.pull === false);
74260
+ const layerPlan = materializeLambdaLayers2(lambda.layers);
74117
74261
  return {
74118
74262
  image,
74119
74263
  mounts: [{ hostPath: codeDir, containerPath: "/var/task", readOnly: true }],
74264
+ extraMounts: layerPlan.mount ? [layerPlan.mount] : [],
74120
74265
  cmd: [lambda.handler],
74121
- ...inlineTmpDir !== void 0 && { inlineTmpDir }
74266
+ ...inlineTmpDir !== void 0 && { inlineTmpDir },
74267
+ ...layerPlan.tmpDir !== void 0 && { layersTmpDir: layerPlan.tmpDir }
74268
+ };
74269
+ }
74270
+ function materializeLambdaLayers2(layers) {
74271
+ if (layers.length === 0)
74272
+ return {};
74273
+ if (layers.length === 1) {
74274
+ return {
74275
+ mount: { hostPath: layers[0].assetPath, containerPath: "/opt", readOnly: true }
74276
+ };
74277
+ }
74278
+ const tmpDir = mkdtempSync3(path2.join(tmpdir3(), "cdkd-local-invoke-layers-"));
74279
+ for (const layer of layers) {
74280
+ cpSync2(layer.assetPath, tmpDir, { recursive: true, force: true });
74281
+ }
74282
+ return {
74283
+ mount: { hostPath: tmpDir, containerPath: "/opt", readOnly: true },
74284
+ tmpDir
74122
74285
  };
74123
74286
  }
74124
74287
  async function resolveContainerImagePlan(lambda, options) {
@@ -74151,6 +74314,7 @@ async function resolveContainerImagePlan(lambda, options) {
74151
74314
  return {
74152
74315
  image: imageRef,
74153
74316
  mounts: [],
74317
+ extraMounts: [],
74154
74318
  cmd: lambda.imageConfig.command ?? [],
74155
74319
  platform,
74156
74320
  ...lambda.imageConfig.entryPoint && lambda.imageConfig.entryPoint.length > 0 && {
@@ -74469,7 +74633,7 @@ function reorderArgs(argv) {
74469
74633
  }
74470
74634
  async function main() {
74471
74635
  const program = new Command16();
74472
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.73.0");
74636
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.74.0");
74473
74637
  program.addCommand(createBootstrapCommand());
74474
74638
  program.addCommand(createSynthCommand());
74475
74639
  program.addCommand(createListCommand());