cdk-local 0.155.0 → 0.157.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.
@@ -46,10 +46,11 @@ import * as vm from "node:vm";
46
46
  import "@aws-sdk/signature-v4a";
47
47
  import { CloudFrontClient, paginateListKeyValueStores } from "@aws-sdk/client-cloudfront";
48
48
  import { CloudFrontKeyValueStoreClient, GetKeyCommand, ResourceNotFoundException } from "@aws-sdk/client-cloudfront-keyvaluestore";
49
- import { existsSync as existsSync$1, rmSync as rmSync$1, statSync as statSync$1 } from "fs";
49
+ import { existsSync as existsSync$1, readFileSync as readFileSync$1, rmSync as rmSync$1, statSync as statSync$1 } from "fs";
50
50
  import { request as request$2 } from "http";
51
51
  import { execFile as execFile$1, spawn as spawn$1 } from "child_process";
52
52
  import { promisify as promisify$1 } from "util";
53
+ import { randomUUID as randomUUID$1 } from "crypto";
53
54
  import { EventEmitter } from "node:events";
54
55
 
55
56
  //#region src/utils/url-authority.ts
@@ -3536,7 +3537,8 @@ function listTargets(stacks) {
3536
3537
  agentCoreRuntimes: sortEntries(scanAgentCoreRuntimes(stacks)),
3537
3538
  loadBalancers: sortEntries(scanApplicationLoadBalancers(stacks)),
3538
3539
  cloudFrontDistributions: sortEntries(scanByType(stacks, "AWS::CloudFront::Distribution")),
3539
- webFunctions: sortEntries(scanByType(stacks, "AWS::Lambda::WebFunction"))
3540
+ webFunctions: sortEntries(scanByType(stacks, "AWS::Lambda::WebFunction")),
3541
+ microvmImages: sortEntries(scanByType(stacks, "AWS::Lambda::MicrovmImage"))
3540
3542
  };
3541
3543
  }
3542
3544
  const pathOf$1 = (e) => e.displayPath ?? e.qualifiedId;
@@ -3559,7 +3561,7 @@ function sortApiEntries(entries) {
3559
3561
  });
3560
3562
  }
3561
3563
  function countTargets(listing) {
3562
- return listing.lambdas.length + listing.apis.length + listing.ecsServices.length + listing.ecsTaskDefinitions.length + listing.agentCoreRuntimes.length + listing.loadBalancers.length + listing.cloudFrontDistributions.length + listing.webFunctions.length;
3564
+ return listing.lambdas.length + listing.apis.length + listing.ecsServices.length + listing.ecsTaskDefinitions.length + listing.agentCoreRuntimes.length + listing.loadBalancers.length + listing.cloudFrontDistributions.length + listing.webFunctions.length + listing.microvmImages.length;
3563
3565
  }
3564
3566
 
3565
3567
  //#endregion
@@ -6493,7 +6495,7 @@ function pushMap(hash, name, value) {
6493
6495
 
6494
6496
  //#endregion
6495
6497
  //#region src/local/docker-runner.ts
6496
- const execFileAsync$6 = promisify(execFile);
6498
+ const execFileAsync$7 = promisify(execFile);
6497
6499
  var DockerRunnerError = class DockerRunnerError extends Error {
6498
6500
  constructor(message) {
6499
6501
  super(message);
@@ -6555,6 +6557,7 @@ async function runDetached(opts) {
6555
6557
  const host = opts.host ?? "127.0.0.1";
6556
6558
  args.push("-p", `${host}:${opts.hostPort}:${opts.containerPort ?? 8080}`);
6557
6559
  if (opts.debugPort !== void 0) args.push("-p", `${host}:${opts.debugPort}:${opts.debugPort}`);
6560
+ for (const extra of opts.extraPorts ?? []) args.push("-p", `${host}:${extra.hostPort}:${extra.containerPort}`);
6558
6561
  for (const mount of opts.mounts) {
6559
6562
  const ro = mount.readOnly ? ":ro" : "";
6560
6563
  args.push("-v", `${mount.hostPath}:${mount.containerPath}${ro}`);
@@ -6584,7 +6587,7 @@ async function runDetached(opts) {
6584
6587
  if (refusal !== void 0) throw new DockerRunnerError(refusal);
6585
6588
  warnFinchArgvExposure(forwardedKeys);
6586
6589
  try {
6587
- const { stdout } = await execFileAsync$6(getDockerCmd(), args, {
6590
+ const { stdout } = await execFileAsync$7(getDockerCmd(), args, {
6588
6591
  maxBuffer: 10 * 1024 * 1024,
6589
6592
  ...execEnvForSecrets(passthroughEnv)
6590
6593
  });
@@ -6641,8 +6644,8 @@ function streamLogs(containerId, opts = {}) {
6641
6644
  proc.stderr?.on("data", (chunk) => tap(chunk));
6642
6645
  }
6643
6646
  let flush = () => void 0;
6644
- if (process.env["CDKL_PREFIX_CONTAINER_OUTPUT"] === "1") {
6645
- const tag = `[${flattenToOneLine(containerId.slice(0, 12)).replace(/[\]\s]/g, "_")}] `;
6647
+ if (opts.prefix !== void 0 || process.env["CDKL_PREFIX_CONTAINER_OUTPUT"] === "1") {
6648
+ const tag = opts.prefix ?? `[${flattenToOneLine(containerId.slice(0, 12)).replace(/[\]\s]/g, "_")}] `;
6646
6649
  const stdoutRelay = createPrefixedLineRelay(tag, process.stdout);
6647
6650
  const stderrRelay = createPrefixedLineRelay(tag, process.stderr);
6648
6651
  proc.stdout?.on("data", (chunk) => stdoutRelay.push(chunk));
@@ -6666,7 +6669,7 @@ async function removeContainer(containerId) {
6666
6669
  if (!containerId) return;
6667
6670
  const logger = getLogger().child("docker");
6668
6671
  try {
6669
- await execFileAsync$6(getDockerCmd(), [
6672
+ await execFileAsync$7(getDockerCmd(), [
6670
6673
  "rm",
6671
6674
  "-f",
6672
6675
  containerId
@@ -6680,7 +6683,7 @@ async function removeContainer(containerId) {
6680
6683
  async function ensureDockerAvailable() {
6681
6684
  const cmd = getDockerCmd();
6682
6685
  try {
6683
- await execFileAsync$6(cmd, [
6686
+ await execFileAsync$7(cmd, [
6684
6687
  "version",
6685
6688
  "--format",
6686
6689
  "{{.Server.Version}}"
@@ -7213,14 +7216,14 @@ async function handleConnectionsRequest(opts) {
7213
7216
  const { req, res, registry } = opts;
7214
7217
  const parsed = parseConnectionsPath(req.url ?? "");
7215
7218
  if (!parsed) {
7216
- writeJson(res, 404, { message: "Not Found" });
7219
+ writeJson$1(res, 404, { message: "Not Found" });
7217
7220
  return;
7218
7221
  }
7219
7222
  const { connectionId } = parsed;
7220
7223
  const entry = registry.get(connectionId);
7221
7224
  const method = (req.method ?? "").toUpperCase();
7222
7225
  if (!entry) {
7223
- writeJson(res, 410, { message: "GoneException" });
7226
+ writeJson$1(res, 410, { message: "GoneException" });
7224
7227
  return;
7225
7228
  }
7226
7229
  if (method === "POST") {
@@ -7228,17 +7231,17 @@ async function handleConnectionsRequest(opts) {
7228
7231
  try {
7229
7232
  body = await readRequestBody$1(req);
7230
7233
  } catch (err) {
7231
- writeJson(res, 500, { message: `Failed to read request body: ${err instanceof Error ? err.message : String(err)}` });
7234
+ writeJson$1(res, 500, { message: `Failed to read request body: ${err instanceof Error ? err.message : String(err)}` });
7232
7235
  return;
7233
7236
  }
7234
7237
  if (entry.socket.readyState !== entry.socket.OPEN) {
7235
- writeJson(res, 410, { message: "GoneException" });
7238
+ writeJson$1(res, 410, { message: "GoneException" });
7236
7239
  return;
7237
7240
  }
7238
7241
  try {
7239
7242
  entry.socket.send(body);
7240
7243
  } catch (err) {
7241
- writeJson(res, 500, { message: `Failed to deliver to socket: ${err instanceof Error ? err.message : String(err)}` });
7244
+ writeJson$1(res, 500, { message: `Failed to deliver to socket: ${err instanceof Error ? err.message : String(err)}` });
7242
7245
  return;
7243
7246
  }
7244
7247
  res.writeHead(200);
@@ -7249,7 +7252,7 @@ async function handleConnectionsRequest(opts) {
7249
7252
  try {
7250
7253
  entry.socket.close(1e3, "DeleteConnection");
7251
7254
  } catch (err) {
7252
- writeJson(res, 500, { message: `Failed to close socket: ${err instanceof Error ? err.message : String(err)}` });
7255
+ writeJson$1(res, 500, { message: `Failed to close socket: ${err instanceof Error ? err.message : String(err)}` });
7253
7256
  return;
7254
7257
  }
7255
7258
  res.writeHead(204);
@@ -7257,7 +7260,7 @@ async function handleConnectionsRequest(opts) {
7257
7260
  return;
7258
7261
  }
7259
7262
  if (method === "GET") {
7260
- writeJson(res, 200, {
7263
+ writeJson$1(res, 200, {
7261
7264
  ConnectedAt: new Date(entry.connectedAt).toISOString(),
7262
7265
  Identity: { SourceIp: "127.0.0.1" },
7263
7266
  LastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7265,9 +7268,9 @@ async function handleConnectionsRequest(opts) {
7265
7268
  return;
7266
7269
  }
7267
7270
  res.setHeader("Allow", "POST, GET, DELETE");
7268
- writeJson(res, 405, { message: "MethodNotAllowedException" });
7271
+ writeJson$1(res, 405, { message: "MethodNotAllowedException" });
7269
7272
  }
7270
- function writeJson(res, status, body) {
7273
+ function writeJson$1(res, status, body) {
7271
7274
  const json = JSON.stringify(body);
7272
7275
  res.writeHead(status, {
7273
7276
  "Content-Type": "application/json",
@@ -11049,7 +11052,7 @@ async function startApiServer(opts) {
11049
11052
  const logger = getLogger().child("start-api");
11050
11053
  let currentState = opts.state;
11051
11054
  const requestHandler = (req, res) => {
11052
- handleRequest$2(req, res, currentState, opts).catch((err) => {
11055
+ handleRequest$3(req, res, currentState, opts).catch((err) => {
11053
11056
  logger.error(`Unhandled request error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
11054
11057
  if (!res.headersSent) writeError$1(res, 502);
11055
11058
  });
@@ -11101,7 +11104,7 @@ async function startApiServer(opts) {
11101
11104
  getServerState: () => currentState
11102
11105
  };
11103
11106
  }
11104
- async function handleRequest$2(req, res, state, opts) {
11107
+ async function handleRequest$3(req, res, state, opts) {
11105
11108
  const logger = getLogger().child("start-api");
11106
11109
  if (opts.preDispatch) try {
11107
11110
  if (await opts.preDispatch(req, res)) return;
@@ -15489,7 +15492,7 @@ async function resolveAgentCoreCodeImage(resolved, code, options, architecture,
15489
15492
  const loader = new AssetManifestLoader();
15490
15493
  const manifest = await loader.loadStackManifest(resolved.stack, cdkOutDir);
15491
15494
  const fileAssets = manifest ? loader.getFileAssets(manifest) : void 0;
15492
- const asset = fileAssets ? fileAssets.get(code.codeAssetHash) ?? findFileAssetByObjectKey$1(fileAssets, code.codeAssetHash) : void 0;
15495
+ const asset = fileAssets ? fileAssets.get(code.codeAssetHash) ?? findFileAssetByObjectKey(fileAssets, code.codeAssetHash) : void 0;
15493
15496
  if (!asset) throw new CdkLocalError(`AgentCore Runtime ${displayIdent(resolved.logicalId)} code bundle (asset ${displayIdent(code.codeAssetHash)}) was not found in the cdk.out asset manifest. ${getEmbedConfig().cliName} invoke-agentcore runs a local from-source build of a fromCodeAsset bundle — re-synthesize the app so the asset is staged in cdk.out and retry. (A fromS3 bundle is downloaded from S3 instead; this runtime has no literal Code.S3.Bucket.)`, "LOCAL_INVOKE_AGENTCORE_CODE_ASSET_NOT_FOUND");
15494
15497
  const sourceDir = loader.getAssetSourcePath(cdkOutDir, asset, {
15495
15498
  assetOutdir: assetPathDirs(resolved.stack).assetOutdir,
@@ -15539,7 +15542,7 @@ async function resolveAgentCoreCodeImageFromS3(resolved, code, s3Source, options
15539
15542
  await bundle.cleanup();
15540
15543
  }
15541
15544
  }
15542
- function findFileAssetByObjectKey$1(fileAssets, hash) {
15545
+ function findFileAssetByObjectKey(fileAssets, hash) {
15543
15546
  const zip = `${hash}.zip`;
15544
15547
  for (const asset of fileAssets.values()) if (Object.values(asset.destinations).some((d) => d.objectKey === zip || d.objectKey.endsWith(`/${zip}`))) return asset;
15545
15548
  }
@@ -16030,7 +16033,7 @@ async function runAgentCoreWatchLoop(args) {
16030
16033
  }
16031
16034
  }
16032
16035
  }
16033
- const execFileAsync$5 = promisify(execFile);
16036
+ const execFileAsync$6 = promisify(execFile);
16034
16037
  function describeExecError(err) {
16035
16038
  if (!(err instanceof Error)) return String(err);
16036
16039
  const stderr = err.stderr;
@@ -16042,7 +16045,7 @@ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
16042
16045
  const dockerCmd = getDockerCmd();
16043
16046
  let workdir;
16044
16047
  try {
16045
- const { stdout } = await execFileAsync$5(dockerCmd, [
16048
+ const { stdout } = await execFileAsync$6(dockerCmd, [
16046
16049
  "inspect",
16047
16050
  "--format",
16048
16051
  "{{.Config.WorkingDir}}",
@@ -16055,7 +16058,7 @@ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
16055
16058
  const workdirDest = workdir.endsWith("/") ? workdir : `${workdir}/`;
16056
16059
  logger.info(`Soft-reload: docker cp ${flattenToOneLine(newAssetSourceDir)} -> ${containerId}:${workdirDest}; restart.`);
16057
16060
  try {
16058
- await execFileAsync$5(dockerCmd, [
16061
+ await execFileAsync$6(dockerCmd, [
16059
16062
  "cp",
16060
16063
  `${newAssetSourceDir}/.`,
16061
16064
  `${containerId}:${workdirDest}`
@@ -16064,7 +16067,7 @@ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
16064
16067
  throw new CdkLocalError(`softReloadAgentContainer: docker cp into '${containerId}:${workdir}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_CP_FAILED");
16065
16068
  }
16066
16069
  try {
16067
- await execFileAsync$5(dockerCmd, ["restart", containerId]);
16070
+ await execFileAsync$6(dockerCmd, ["restart", containerId]);
16068
16071
  } catch (err) {
16069
16072
  throw new CdkLocalError(`softReloadAgentContainer: docker restart of '${containerId}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_RESTART_FAILED");
16070
16073
  }
@@ -16240,7 +16243,7 @@ function writePrefixedLines(prefix, buffer, out) {
16240
16243
 
16241
16244
  //#endregion
16242
16245
  //#region src/local/ecs-network.ts
16243
- const execFileAsync$4 = promisify(execFile);
16246
+ const execFileAsync$5 = promisify(execFile);
16244
16247
  const METADATA_ENDPOINT_IMAGE = "amazon/amazon-ecs-local-container-endpoints:latest-amd64";
16245
16248
  const METADATA_ENDPOINT_IP = "169.254.170.2";
16246
16249
  const DEFAULT_METADATA_ENDPOINT_SUBNET = "169.254.170.0/24";
@@ -16276,7 +16279,7 @@ async function sweepOrphanedSvcNetworks(prefix) {
16276
16279
  const filter = `${prefix}-svc-`;
16277
16280
  let names;
16278
16281
  try {
16279
- const { stdout } = await execFileAsync$4(getDockerCmd(), [
16282
+ const { stdout } = await execFileAsync$5(getDockerCmd(), [
16280
16283
  "network",
16281
16284
  "ls",
16282
16285
  "--filter",
@@ -16294,7 +16297,7 @@ async function sweepOrphanedSvcNetworks(prefix) {
16294
16297
  for (const name of names) {
16295
16298
  let attached;
16296
16299
  try {
16297
- const { stdout } = await execFileAsync$4(getDockerCmd(), [
16300
+ const { stdout } = await execFileAsync$5(getDockerCmd(), [
16298
16301
  "network",
16299
16302
  "inspect",
16300
16303
  name,
@@ -16322,7 +16325,7 @@ async function createNetworkAndSidecar(args) {
16322
16325
  await pullImage(METADATA_ENDPOINT_IMAGE, skipPull);
16323
16326
  logger.info(`Creating docker network ${networkName} (subnet ${cidr})...`);
16324
16327
  try {
16325
- await execFileAsync$4(getDockerCmd(), [
16328
+ await execFileAsync$5(getDockerCmd(), [
16326
16329
  "network",
16327
16330
  "create",
16328
16331
  "--driver",
@@ -16359,7 +16362,7 @@ async function createNetworkAndSidecar(args) {
16359
16362
  warnFinchArgvExposure(Object.keys(sidecarPassthroughEnv));
16360
16363
  logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
16361
16364
  try {
16362
- const { stdout } = await execFileAsync$4(getDockerCmd(), sidecarArgs, {
16365
+ const { stdout } = await execFileAsync$5(getDockerCmd(), sidecarArgs, {
16363
16366
  maxBuffer: 10 * 1024 * 1024,
16364
16367
  ...execEnvForSecrets(sidecarPassthroughEnv)
16365
16368
  });
@@ -16407,7 +16410,7 @@ async function destroyNetworkOnly(networkName) {
16407
16410
  if (!networkName) return;
16408
16411
  const logger = getLogger().child("ecs-network");
16409
16412
  try {
16410
- await execFileAsync$4(getDockerCmd(), [
16413
+ await execFileAsync$5(getDockerCmd(), [
16411
16414
  "network",
16412
16415
  "rm",
16413
16416
  networkName
@@ -16521,7 +16524,7 @@ async function resolveSsm(entry, shape, client) {
16521
16524
 
16522
16525
  //#endregion
16523
16526
  //#region src/local/ecs-task-runner.ts
16524
- const execFileAsync$3 = promisify(execFile);
16527
+ const execFileAsync$4 = promisify(execFile);
16525
16528
  var EcsTaskRunnerError = class EcsTaskRunnerError extends Error {
16526
16529
  constructor(message) {
16527
16530
  super(message);
@@ -16564,7 +16567,7 @@ async function cleanupEcsRun(state, options) {
16564
16567
  if (state.network && !state.network.ownedByCaller) await destroyTaskNetwork(state.network);
16565
16568
  state.network = void 0;
16566
16569
  for (const v of state.dockerVolumeNames) try {
16567
- await execFileAsync$3(getDockerCmd(), [
16570
+ await execFileAsync$4(getDockerCmd(), [
16568
16571
  "volume",
16569
16572
  "rm",
16570
16573
  v
@@ -16715,7 +16718,7 @@ async function runEcsTask(task, options, state) {
16715
16718
  let id;
16716
16719
  warnFinchArgvExposure(Object.keys(sensitiveEnv));
16717
16720
  try {
16718
- const { stdout } = await execFileAsync$3(getDockerCmd(), args, {
16721
+ const { stdout } = await execFileAsync$4(getDockerCmd(), args, {
16719
16722
  maxBuffer: 10 * 1024 * 1024,
16720
16723
  ...execEnvForSecrets(sensitiveEnv)
16721
16724
  });
@@ -16807,7 +16810,7 @@ async function waitForContainerHealthy(containerId, displayName) {
16807
16810
  let lastStatus = "";
16808
16811
  while (Date.now() < deadline) {
16809
16812
  try {
16810
- const { stdout } = await execFileAsync$3(getDockerCmd(), [
16813
+ const { stdout } = await execFileAsync$4(getDockerCmd(), [
16811
16814
  "inspect",
16812
16815
  "--format",
16813
16816
  "{{.State.Health.Status}}",
@@ -16830,7 +16833,7 @@ async function waitForContainerHealthy(containerId, displayName) {
16830
16833
  }
16831
16834
  async function waitForContainerExit(containerId) {
16832
16835
  try {
16833
- const { stdout } = await execFileAsync$3(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
16836
+ const { stdout } = await execFileAsync$4(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
16834
16837
  const code = Number.parseInt(stdout.trim(), 10);
16835
16838
  return Number.isFinite(code) ? code : 1;
16836
16839
  } catch (err) {
@@ -16840,7 +16843,7 @@ async function waitForContainerExit(containerId) {
16840
16843
  }
16841
16844
  async function stopContainer(containerId, graceSeconds) {
16842
16845
  try {
16843
- await execFileAsync$3(getDockerCmd(), [
16846
+ await execFileAsync$4(getDockerCmd(), [
16844
16847
  "stop",
16845
16848
  "-t",
16846
16849
  String(graceSeconds),
@@ -16935,7 +16938,7 @@ async function realizeDockerVolumes(volumes, state) {
16935
16938
  const dockerVolumeName = `${getEmbedConfig().resourceNamePrefix}-${v.name}-${randHex(4)}`;
16936
16939
  args.push(dockerVolumeName);
16937
16940
  try {
16938
- await execFileAsync$3(getDockerCmd(), args);
16941
+ await execFileAsync$4(getDockerCmd(), args);
16939
16942
  state.dockerVolumeNames.push(dockerVolumeName);
16940
16943
  logger.debug(`Created docker volume ${displayIdent(dockerVolumeName)} for task volume ${displayIdent(v.name)}`);
16941
16944
  } catch (err) {
@@ -17335,11 +17338,11 @@ function notFoundError$1(target, stack, resources, parsed) {
17335
17338
 
17336
17339
  //#endregion
17337
17340
  //#region src/local/docker-inspect.ts
17338
- const execFileAsync$2 = promisify(execFile);
17341
+ const execFileAsync$3 = promisify(execFile);
17339
17342
  async function getContainerNetworkIp(containerId, networkName) {
17340
17343
  const format = `{{with index .NetworkSettings.Networks "${networkName}"}}{{.IPAddress}}{{end}}`;
17341
17344
  try {
17342
- const { stdout } = await execFileAsync$2(getDockerCmd(), [
17345
+ const { stdout } = await execFileAsync$3(getDockerCmd(), [
17343
17346
  "inspect",
17344
17347
  "--format",
17345
17348
  format,
@@ -17355,7 +17358,7 @@ async function getContainerNetworkIp(containerId, networkName) {
17355
17358
  async function getPublishedHostPort(containerId, containerPort, protocol = "tcp") {
17356
17359
  const format = `{{with index .NetworkSettings.Ports "${`${containerPort}/${protocol}`}"}}{{with index . 0}}{{.HostPort}}{{end}}{{end}}`;
17357
17360
  try {
17358
- const { stdout } = await execFileAsync$2(getDockerCmd(), [
17361
+ const { stdout } = await execFileAsync$3(getDockerCmd(), [
17359
17362
  "inspect",
17360
17363
  "--format",
17361
17364
  format,
@@ -18653,7 +18656,7 @@ function buildHeaderMaps(headers) {
18653
18656
  multi
18654
18657
  };
18655
18658
  }
18656
- function firstHeader(headers, name) {
18659
+ function firstHeader$1(headers, name) {
18657
18660
  const lower = name.toLowerCase();
18658
18661
  for (const [k, v] of Object.entries(headers)) if (k.toLowerCase() === lower) return v[0];
18659
18662
  }
@@ -18661,8 +18664,8 @@ function buildAlbLambdaEvent(req, opts) {
18661
18664
  const { path, rawQuery } = splitRawUrl(req.rawUrl);
18662
18665
  const query = parseQuery(rawQuery);
18663
18666
  const headerMaps = buildHeaderMaps(req.headers);
18664
- const contentEncoding = firstHeader(req.headers, "content-encoding");
18665
- const contentType = firstHeader(req.headers, "content-type") ?? "";
18667
+ const contentEncoding = firstHeader$1(req.headers, "content-encoding");
18668
+ const contentType = firstHeader$1(req.headers, "content-type") ?? "";
18666
18669
  const isBase64Encoded = req.body.length > 0 && (contentEncoding !== void 0 ? true : !isTextualContentType(contentType));
18667
18670
  const body = isBase64Encoded ? req.body.toString("base64") : req.body.toString("utf-8");
18668
18671
  const event = {
@@ -18765,7 +18768,7 @@ async function startFrontDoorServer(opts) {
18765
18768
  const scheme = opts.tls ? "https" : "http";
18766
18769
  server.on("connection", (socket) => socket.setNoDelay(true));
18767
18770
  server.on("upgrade", (req, clientSocket, head) => {
18768
- handleUpgrade(req, clientSocket, head, effectiveOpts).catch((err) => {
18771
+ handleUpgrade$1(req, clientSocket, head, effectiveOpts).catch((err) => {
18769
18772
  logger.debug(`front-door upgrade error: ${err instanceof Error ? err.message : String(err)}`);
18770
18773
  if (!clientSocket.destroyed) clientSocket.destroy();
18771
18774
  });
@@ -19109,7 +19112,7 @@ function writeError(res, statusCode, message) {
19109
19112
  res.writeHead(statusCode, { "content-type": "text/plain; charset=utf-8" });
19110
19113
  res.end(`${message}\n`);
19111
19114
  }
19112
- function handleUpgrade(req, clientSocket, head, opts) {
19115
+ function handleUpgrade$1(req, clientSocket, head, opts) {
19113
19116
  clientSocket.setNoDelay?.(true);
19114
19117
  if (!opts.route) {
19115
19118
  writeRawHttpError(clientSocket, 404, "Not Found");
@@ -19296,7 +19299,9 @@ function sanitizeRawHeaderValue(value) {
19296
19299
  const STATUS_TEXT$1 = {
19297
19300
  301: "Moved Permanently",
19298
19301
  302: "Found",
19302
+ 400: "Bad Request",
19299
19303
  401: "Unauthorized",
19304
+ 403: "Forbidden",
19300
19305
  404: "Not Found",
19301
19306
  502: "Bad Gateway",
19302
19307
  503: "Service Unavailable"
@@ -19550,7 +19555,7 @@ function matchBitPrefix(a, b, prefixLength) {
19550
19555
 
19551
19556
  //#endregion
19552
19557
  //#region src/local/front-door-tls.ts
19553
- const execFileAsync$1 = promisify(execFile);
19558
+ const execFileAsync$2 = promisify(execFile);
19554
19559
  async function resolveFrontDoorTlsMaterials(opts) {
19555
19560
  const hasCert = opts.certPath !== void 0 && opts.certPath !== "";
19556
19561
  const hasKey = opts.keyPath !== void 0 && opts.keyPath !== "";
@@ -19640,7 +19645,7 @@ function readCertNotAfter(certPath) {
19640
19645
  }
19641
19646
  }
19642
19647
  async function runOpenssl(args) {
19643
- await execFileAsync$1("openssl", args, { timeout: 3e4 });
19648
+ await execFileAsync$2("openssl", args, { timeout: 3e4 });
19644
19649
  }
19645
19650
  function readPemOrThrow(path, flagName) {
19646
19651
  try {
@@ -21843,7 +21848,7 @@ function resolveAlbFrontDoor(stack, albLogicalId) {
21843
21848
  if (resource.Type !== LISTENER_TYPE) continue;
21844
21849
  const props = resource.Properties ?? {};
21845
21850
  if (refOf(props["LoadBalancerArn"]) !== albLogicalId) continue;
21846
- const port = parsePort$2(props["Port"]);
21851
+ const port = parsePort$3(props["Port"]);
21847
21852
  if (port === void 0) continue;
21848
21853
  const protocol = typeof props["Protocol"] === "string" ? props["Protocol"] : "HTTP";
21849
21854
  if (protocol !== "HTTP" && protocol !== "HTTPS") {
@@ -22208,7 +22213,7 @@ function refOf(raw) {
22208
22213
  if (typeof ref === "string" && ref.length > 0) return ref;
22209
22214
  }
22210
22215
  }
22211
- function parsePort$2(raw) {
22216
+ function parsePort$3(raw) {
22212
22217
  if (typeof raw === "number" && Number.isInteger(raw) && raw >= 1 && raw <= 65535) return raw;
22213
22218
  if (typeof raw === "string" && /^\d+$/.test(raw)) {
22214
22219
  const n = parseInt(raw, 10);
@@ -22216,7 +22221,7 @@ function parsePort$2(raw) {
22216
22221
  }
22217
22222
  }
22218
22223
  function parseContainerPort(raw) {
22219
- return parsePort$2(raw);
22224
+ return parsePort$3(raw);
22220
22225
  }
22221
22226
  function parseWeight(raw) {
22222
22227
  if (typeof raw === "number" && Number.isFinite(raw)) return raw < 0 ? 0 : raw;
@@ -22459,6 +22464,93 @@ function addAlbSpecificOptions(cmd) {
22459
22464
  return cmd.addOption(new Option("--lb-port <listenerPort=hostPort...>", "Bind the local front-door on a specific host port (e.g. 80=8080); repeatable. Default: host port == ALB listener port. Use this on macOS to remap a privileged listener port (< 1024) to a non-privileged host port.")).addOption(new Option("--tls", "Terminate TLS locally for cloud-HTTPS listeners. Default: a cloud-HTTPS listener is served over plain HTTP locally (X-Forwarded-Proto: https is preserved so the upstream app still sees the deployed listener protocol). Implied by --tls-cert / --tls-key. Use this when local-dev cookies need Secure / SameSite=None, when the upstream app inspects TLS metadata, or for mTLS / SNI testing — otherwise plain HTTP is friendlier (no self-signed cert warnings in curl / browser).")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Implies --tls. Must be set together with --tls-key. Pass --tls alone (without --tls-cert / --tls-key) to auto-generate a self-signed cert (cached under $XDG_CACHE_HOME/cdk-local/alb-https/, default ~/.cache/cdk-local/alb-https/); requires openssl on PATH. The deployed Listener Certificates[] are NOT fetched (ACM private keys are not retrievable by design). The auto-generated cert lists DNS:localhost,IP:127.0.0.1 as SubjectAltName, so a client validating a non-loopback --container-host will fail the SAN check — pass --tls-cert / --tls-key with a SAN covering that host instead.")).addOption(new Option("--tls-key <path>", "PEM-encoded server private key matching --tls-cert. Implies --tls. Must be set together with --tls-cert.")).addOption(new Option("--no-verify-auth", "Disable local enforcement of authenticate-cognito / authenticate-oidc actions. Every request is served as if the auth check passed. Useful for local dev where you do not want to mint a Bearer token at all.")).addOption(new Option("--bearer-token <jwt>", "Default Bearer JWT this command INJECTS only when an inbound request has none (the default-when-missing role) — `cdkl start-alb` is the local ALB front-door RECEIVING outside-in requests, so this token is the fallback the front-door slots in as Authorization: Bearer <jwt> if the caller did not already supply one. Verified against the same JWKS / OIDC discovery URL the deployed ALB would (signature + iss + aud + exp). Cookie pass-through (AWSELBAuthSessionCookie-*) also bypasses the guard. Contrast with `cdkl invoke-agentcore --bearer-token`, where the role is reversed — that command is the outbound client and ALWAYS presents this token (the supplier).")).addOption(new Option("--watch", "Hot-reload: re-synth + per-replica reload of every ECS service behind the ALB when the CDK source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). A per-firing classifier picks the per-replica primitive: source-only edits on interpreted-language handlers (Node/Python/Ruby/shell) take a bind-mount FAST PATH (`docker cp` the new source into each replica + restart it; no rebuild, its front-door pool entry is drained and re-published once it listens again). Dockerfile / dependency manifest / compiled-language source / ambiguous edits fall through to the rebuild rolling primitive — boot a shadow under a bumped generation suffix, wait for its container port to accept a TCP connection, atomically register it in the front-door pool, then drop the old entry and retire the old container. Either path rolls one replica at a time, so a continuous external request stream against the listener port sees zero connection refusals across the reload. The host front-door (TLS, JWKS cache, Lambda-target containers, listener sockets) stays up across the reload. Lambda target groups behind the ALB are a no-op on reload (the warm RIE container keeps its boot-time image). Off by default; existing replica(s) keep serving when synth fails mid-reload.").default(false));
22460
22465
  }
22461
22466
 
22467
+ //#endregion
22468
+ //#region src/local/s3-object-asset.ts
22469
+ function findFileAssetForObjectKey(manifest, key) {
22470
+ for (const [hash, asset] of Object.entries(manifest.files ?? {})) for (const dest of Object.values(asset.destinations ?? {})) if (dest.objectKey === key || dest.objectKey.endsWith(`/${key}`)) return {
22471
+ hash,
22472
+ asset
22473
+ };
22474
+ const hash = key.replace(/^.*\//, "").replace(/\.zip$/, "");
22475
+ const byHash = Object.hasOwn(manifest.files ?? {}, hash) ? manifest.files[hash] : void 0;
22476
+ return byHash ? {
22477
+ hash,
22478
+ asset: byHash
22479
+ } : void 0;
22480
+ }
22481
+ function isBucketDeploymentResource(resource) {
22482
+ return resource !== void 0 && String(resource.Type).startsWith("Custom::CDKBucketDeployment");
22483
+ }
22484
+ function bucketDeploymentSourceObjectKeys(resource) {
22485
+ const keys = resource.Properties?.["SourceObjectKeys"];
22486
+ return Array.isArray(keys) ? keys : [];
22487
+ }
22488
+ function resolveBucketDeploymentObjectKey(template, value) {
22489
+ const select = asObject$3(value)?.["Fn::Select"];
22490
+ if (!Array.isArray(select) || select.length !== 2) return void 0;
22491
+ const index = selectIndex(select[0]);
22492
+ const deploymentLogicalId = sourceObjectKeysGetAttTarget(select[1]);
22493
+ if (index === void 0 || deploymentLogicalId === void 0) return void 0;
22494
+ const resources = template.Resources ?? {};
22495
+ const deployment = Object.hasOwn(resources, deploymentLogicalId) ? resources[deploymentLogicalId] : void 0;
22496
+ if (!deployment || !isBucketDeploymentResource(deployment)) return void 0;
22497
+ const props = deployment.Properties ?? {};
22498
+ if (handlerFlag(props["Extract"])) return {
22499
+ kind: "extracts",
22500
+ deploymentLogicalId,
22501
+ index
22502
+ };
22503
+ if (!handlerFlag(props["OutputObjectKeys"])) return {
22504
+ kind: "object-keys-not-output",
22505
+ deploymentLogicalId,
22506
+ index
22507
+ };
22508
+ const prefix = props["DestinationBucketKeyPrefix"];
22509
+ if (prefix !== void 0 && prefix !== "" && prefix !== "/") return {
22510
+ kind: "prefixed-destination",
22511
+ deploymentLogicalId,
22512
+ index
22513
+ };
22514
+ const keys = bucketDeploymentSourceObjectKeys(deployment);
22515
+ if (index >= keys.length) return {
22516
+ kind: "index-out-of-range",
22517
+ deploymentLogicalId,
22518
+ index,
22519
+ sourceCount: keys.length
22520
+ };
22521
+ const sourceObjectKey = keys[index];
22522
+ if (typeof sourceObjectKey !== "string" || sourceObjectKey.length === 0) return {
22523
+ kind: "source-key-not-literal",
22524
+ deploymentLogicalId,
22525
+ index
22526
+ };
22527
+ return {
22528
+ kind: "resolved",
22529
+ deploymentLogicalId,
22530
+ index,
22531
+ sourceObjectKey
22532
+ };
22533
+ }
22534
+ function handlerFlag(value) {
22535
+ if (value === void 0 || value === true || value === "true") return true;
22536
+ return typeof value === "object" && value !== null;
22537
+ }
22538
+ function selectIndex(value) {
22539
+ const n = typeof value === "number" ? value : typeof value === "string" && /^\d+$/.test(value) ? Number(value) : NaN;
22540
+ return Number.isSafeInteger(n) && n >= 0 ? n : void 0;
22541
+ }
22542
+ function sourceObjectKeysGetAttTarget(value) {
22543
+ const getAtt = asObject$3(value)?.["Fn::GetAtt"];
22544
+ if (Array.isArray(getAtt)) return getAtt.length === 2 && typeof getAtt[0] === "string" && getAtt[1] === "SourceObjectKeys" ? getAtt[0] : void 0;
22545
+ if (typeof getAtt === "string" && getAtt.endsWith(".SourceObjectKeys")) {
22546
+ const id = getAtt.slice(0, -17);
22547
+ return id.length > 0 && !id.includes(".") ? id : void 0;
22548
+ }
22549
+ }
22550
+ function asObject$3(value) {
22551
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
22552
+ }
22553
+
22462
22554
  //#endregion
22463
22555
  //#region src/local/cloudfront-kvs.ts
22464
22556
  function createCloudFrontModule(sources) {
@@ -22760,6 +22852,177 @@ function decodePart(s) {
22760
22852
  }
22761
22853
  }
22762
22854
 
22855
+ //#endregion
22856
+ //#region src/local/web-function-resolver.ts
22857
+ const WEB_FUNCTION_TYPE = "AWS::Lambda::WebFunction";
22858
+ const WEB_FUNCTION_REVISION_TYPE = "AWS::Lambda::WebFunctionRevision";
22859
+ const WEB_FUNCTION_ENDPOINT_TYPE = "AWS::Lambda::WebFunctionEndpoint";
22860
+ const WEB_TYPES = [
22861
+ WEB_FUNCTION_ENDPOINT_TYPE,
22862
+ WEB_FUNCTION_TYPE,
22863
+ WEB_FUNCTION_REVISION_TYPE
22864
+ ];
22865
+ var WebFunctionResolutionError = class WebFunctionResolutionError extends Error {
22866
+ constructor(message) {
22867
+ super(message);
22868
+ this.name = "WebFunctionResolutionError";
22869
+ Object.setPrototypeOf(this, WebFunctionResolutionError.prototype);
22870
+ }
22871
+ };
22872
+ function resolveWebFunctionTarget(target, stacks, revisionFlag) {
22873
+ if (stacks.length === 0) throw new WebFunctionResolutionError("No stacks found in the synthesized assembly.");
22874
+ const parsed = parseTarget(target);
22875
+ const stack = pickStack$1(parsed, stacks, target);
22876
+ const resources = stack.template.Resources ?? {};
22877
+ const { logicalId, resource } = matchWebResource(parsed, target, stack, resources);
22878
+ const functions = logicalIdsOfType(resources, WEB_FUNCTION_TYPE);
22879
+ const linked = (value) => linkedFunctionLogicalId(value, resources, functions);
22880
+ let functionLogicalId;
22881
+ let endpoint;
22882
+ let revisionLogicalId;
22883
+ let selection;
22884
+ if (resource.Type === "AWS::Lambda::WebFunctionRevision") {
22885
+ functionLogicalId = linked(resource.Properties?.["FunctionName"]);
22886
+ revisionLogicalId = logicalId;
22887
+ selection = "target-revision";
22888
+ } else if (resource.Type === "AWS::Lambda::WebFunctionEndpoint") {
22889
+ functionLogicalId = linked(resource.Properties?.["FunctionName"]);
22890
+ endpoint = readEndpoint(logicalId, resource, resources);
22891
+ } else functionLogicalId = logicalId;
22892
+ const revisionsOfFunction = () => functionLogicalId === void 0 ? [] : logicalIdsOfType(resources, WEB_FUNCTION_REVISION_TYPE).filter((id) => linked(resources[id]?.Properties?.["FunctionName"]) === functionLogicalId);
22893
+ const endpointsOfFunction = () => functionLogicalId === void 0 ? [] : logicalIdsOfType(resources, WEB_FUNCTION_ENDPOINT_TYPE).filter((id) => linked(resources[id]?.Properties?.["FunctionName"]) === functionLogicalId);
22894
+ if (revisionFlag !== void 0) {
22895
+ const flagged = resources[revisionFlag];
22896
+ if (!flagged || flagged.Type !== "AWS::Lambda::WebFunctionRevision") throw new WebFunctionResolutionError(`--revision ${displayIdent(revisionFlag)} is not an ${WEB_FUNCTION_REVISION_TYPE} logical id in ${displayStackName(stack.stackName)}. ${describeRevisions(revisionsOfFunction(), stack)}`);
22897
+ const owner = linked(flagged.Properties?.["FunctionName"]);
22898
+ if (functionLogicalId !== void 0 && owner !== functionLogicalId) throw new WebFunctionResolutionError(`--revision ${displayIdent(revisionFlag)} belongs to a different web function than ${displayIdent(logicalId)}. ${describeRevisions(revisionsOfFunction(), stack)}`);
22899
+ functionLogicalId ??= owner;
22900
+ revisionLogicalId = revisionFlag;
22901
+ selection = "revision-flag";
22902
+ }
22903
+ if (revisionLogicalId === void 0 && endpoint !== void 0) {
22904
+ revisionLogicalId = pickHighestWeight(endpoint);
22905
+ if (revisionLogicalId !== void 0) selection = "endpoint-weight";
22906
+ }
22907
+ if (revisionLogicalId === void 0) {
22908
+ const revisions = revisionsOfFunction();
22909
+ if (revisions.length === 1) {
22910
+ revisionLogicalId = revisions[0];
22911
+ selection = "only-revision";
22912
+ } else if (endpoint === void 0 && revisions.length > 1) {
22913
+ const endpoints = endpointsOfFunction();
22914
+ if (endpoints.length === 1) {
22915
+ endpoint = readEndpoint(endpoints[0], resources[endpoints[0]], resources);
22916
+ revisionLogicalId = pickHighestWeight(endpoint);
22917
+ if (revisionLogicalId !== void 0) selection = "endpoint-weight";
22918
+ }
22919
+ }
22920
+ if (revisionLogicalId === void 0) throw new WebFunctionResolutionError(revisions.length === 0 ? `${displayIdent(logicalId)} in ${displayStackName(stack.stackName)} resolves to no ${WEB_FUNCTION_REVISION_TYPE} in this template, so there is no code to serve. Target the revision itself, or pass --revision <logicalId>.` : `${displayIdent(logicalId)} has ${revisions.length} revisions and no single endpoint that picks one. Pass --revision <logicalId>, or target one of the function's endpoints. ${describeRevisions(revisions, stack)}`);
22921
+ }
22922
+ if (endpoint === void 0) {
22923
+ const routing = endpointsOfFunction().map((id) => readEndpoint(id, resources[id], resources)).filter((e) => e.weights.some((w) => w.revisionLogicalId === revisionLogicalId));
22924
+ if (routing.length === 1) endpoint = routing[0];
22925
+ }
22926
+ const fnName = (functionLogicalId !== void 0 ? resources[functionLogicalId] : void 0)?.Properties?.["FunctionName"];
22927
+ return {
22928
+ stack,
22929
+ functionLogicalId,
22930
+ functionName: typeof fnName === "string" && fnName.length > 0 ? fnName : functionLogicalId ?? revisionLogicalId,
22931
+ revisionLogicalId,
22932
+ revision: resources[revisionLogicalId],
22933
+ endpoint,
22934
+ selection: selection ?? "only-revision"
22935
+ };
22936
+ }
22937
+ function pickStack$1(parsed, stacks, target) {
22938
+ const selection = selectTargetStack(parsed, target, stacks);
22939
+ switch (selection.kind) {
22940
+ case "found": return selection.stack;
22941
+ case "no-prefix": throw new WebFunctionResolutionError(`Multiple stacks in app, target ${displayIdent(parsed.pathOrId)} is missing a stack prefix. Use StackName:<logicalId> or StackName/... (path form). Available stacks: ${describeStacks(stacks)}.`);
22942
+ case "not-found": throw new WebFunctionResolutionError(`Stack ${displayStackName(selection.pattern)} not found. Available stacks: ${describeStacks(stacks)}.`);
22943
+ case "ambiguous": throw new WebFunctionResolutionError(`Stack pattern ${displayStackName(selection.pattern)} matched ${selection.matched.length} stacks: ${describeStacks(selection.matched)}. Use a more specific pattern, or the stack's display path.`);
22944
+ }
22945
+ }
22946
+ function matchWebResource(parsed, target, stack, resources) {
22947
+ if (parsed.isPath) {
22948
+ const matches = resolveCdkPathToLogicalIds(parsed.pathOrId, buildCdkPathIndex(stack.template)).filter(({ logicalId }) => WEB_TYPES.includes(resources[logicalId]?.Type ?? ""));
22949
+ for (const type of WEB_TYPES) {
22950
+ const ofType = matches.filter(({ logicalId }) => resources[logicalId].Type === type);
22951
+ if (ofType.length === 1) {
22952
+ const id = ofType[0].logicalId;
22953
+ return {
22954
+ logicalId: id,
22955
+ resource: resources[id]
22956
+ };
22957
+ }
22958
+ if (ofType.length > 1) throw new WebFunctionResolutionError(`Target ${displayStackName(target)} matches ${ofType.length} ${type} resources: ${ofType.map((m) => displayIdent(m.logicalId)).join(", ")}. Refine the path or use the StackName:<logicalId> form.`);
22959
+ }
22960
+ throw notFoundError(target, stack, resources);
22961
+ }
22962
+ const resource = resources[parsed.pathOrId];
22963
+ if (!resource) throw notFoundError(target, stack, resources);
22964
+ if (!WEB_TYPES.includes(resource.Type)) throw new WebFunctionResolutionError(`Resource ${displayIdent(parsed.pathOrId)} in ${displayStackName(stack.stackName)} is ${displayIdent(resource.Type)}, not a Lambda web function. ${getEmbedConfig().cliName} start-web-function serves ${WEB_TYPES.join(" / ")} targets.`);
22965
+ return {
22966
+ logicalId: parsed.pathOrId,
22967
+ resource
22968
+ };
22969
+ }
22970
+ function notFoundError(target, stack, resources) {
22971
+ const available = WEB_TYPES.flatMap((t) => logicalIdsOfType(resources, t));
22972
+ const hint = available.length > 0 ? `Web function resources in ${displayStackName(stack.stackName)}: ${available.map((id) => displayIdent(id)).join(", ")}.` : `No ${WEB_FUNCTION_TYPE} resources found in ${displayStackName(stack.stackName)}.`;
22973
+ return new WebFunctionResolutionError(`Target ${displayStackName(target)} not found. ${hint}`);
22974
+ }
22975
+ function logicalIdsOfType(resources, type) {
22976
+ return Object.keys(resources).filter((id) => resources[id]?.Type === type);
22977
+ }
22978
+ function linkedFunctionLogicalId(value, resources, functions) {
22979
+ const ref = referencedLogicalId(value);
22980
+ if (ref !== void 0) return functions.includes(ref) ? ref : void 0;
22981
+ if (typeof value !== "string") return void 0;
22982
+ const byName = functions.filter((id) => resources[id]?.Properties?.["FunctionName"] === value);
22983
+ return byName.length === 1 ? byName[0] : void 0;
22984
+ }
22985
+ function referencedLogicalId(value) {
22986
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
22987
+ const obj = value;
22988
+ if (typeof obj["Ref"] === "string") return obj["Ref"];
22989
+ const getAtt = obj["Fn::GetAtt"];
22990
+ if (Array.isArray(getAtt) && typeof getAtt[0] === "string") return getAtt[0];
22991
+ if (typeof getAtt === "string") return getAtt.split(".")[0];
22992
+ }
22993
+ function readEndpoint(logicalId, resource, resources) {
22994
+ const props = resource.Properties ?? {};
22995
+ const rawWeights = props["RevisionWeights"];
22996
+ const weights = [];
22997
+ if (Array.isArray(rawWeights)) for (const entry of rawWeights) {
22998
+ if (!entry || typeof entry !== "object") continue;
22999
+ const e = entry;
23000
+ const ref = referencedLogicalId(e["RevisionId"]);
23001
+ weights.push({
23002
+ revisionLogicalId: ref !== void 0 && resources[ref]?.Type === "AWS::Lambda::WebFunctionRevision" ? ref : void 0,
23003
+ weight: typeof e["Weight"] === "number" ? e["Weight"] : 0
23004
+ });
23005
+ }
23006
+ const str = (v) => typeof v === "string" ? v : void 0;
23007
+ return {
23008
+ logicalId,
23009
+ endpointType: str(props["EndpointType"]),
23010
+ authType: str(props["AuthType"]),
23011
+ weights
23012
+ };
23013
+ }
23014
+ function pickHighestWeight(endpoint) {
23015
+ let best;
23016
+ for (const w of endpoint.weights) {
23017
+ if (w.revisionLogicalId === void 0) continue;
23018
+ if (best === void 0 || w.weight > best.weight) best = w;
23019
+ }
23020
+ return best?.revisionLogicalId;
23021
+ }
23022
+ function describeRevisions(revisions, stack) {
23023
+ return revisions.length > 0 ? `Revisions: ${revisions.map((id) => displayIdent(id)).join(", ")}.` : `No ${WEB_FUNCTION_REVISION_TYPE} linked to it in ${displayStackName(stack.stackName)}.`;
23024
+ }
23025
+
22763
23026
  //#endregion
22764
23027
  //#region src/local/cloudfront-resolver.ts
22765
23028
  const CLOUDFRONT_DISTRIBUTION_TYPE = "AWS::CloudFront::Distribution";
@@ -22944,6 +23207,18 @@ function resolveOrigins(dc, template, stack, overrides) {
22944
23207
  out.set(originId, lambdaUrl);
22945
23208
  continue;
22946
23209
  }
23210
+ const endpointLogicalId = pickWebFunctionEndpointLogicalIdFromOrigin(origin["DomainName"], template);
23211
+ if (endpointLogicalId) {
23212
+ const customConfig = origin["CustomOriginConfig"];
23213
+ const readTimeout = customConfig && typeof customConfig === "object" ? customConfig["OriginReadTimeout"] : void 0;
23214
+ out.set(originId, {
23215
+ kind: "web-function",
23216
+ originId,
23217
+ endpointLogicalId,
23218
+ ...typeof readTimeout === "number" && readTimeout > 0 && { readTimeoutSeconds: readTimeout }
23219
+ });
23220
+ continue;
23221
+ }
22947
23222
  out.set(originId, {
22948
23223
  kind: "custom",
22949
23224
  originId,
@@ -23012,13 +23287,11 @@ function resolveBucketDeploymentDirs(template, manifest, stack, bucketLogicalId)
23012
23287
  const { manifestDir, assetOutdir } = assetPathDirs(stack);
23013
23288
  const dirs = [];
23014
23289
  for (const resource of Object.values(template.Resources ?? {})) {
23015
- if (!String(resource.Type).startsWith("Custom::CDKBucketDeployment")) continue;
23016
- const props = resource.Properties ?? {};
23017
- if (refLogicalId(props["DestinationBucketName"]) !== bucketLogicalId) continue;
23018
- const keys = Array.isArray(props["SourceObjectKeys"]) ? props["SourceObjectKeys"] : [];
23019
- for (const key of keys) {
23290
+ if (!isBucketDeploymentResource(resource)) continue;
23291
+ if (refLogicalId((resource.Properties ?? {})["DestinationBucketName"]) !== bucketLogicalId) continue;
23292
+ for (const key of bucketDeploymentSourceObjectKeys(resource)) {
23020
23293
  if (typeof key !== "string") continue;
23021
- const asset = findFileAssetByObjectKey(manifest, key);
23294
+ const asset = findFileAssetForObjectKey(manifest, key)?.asset;
23022
23295
  if (!asset) continue;
23023
23296
  dirs.push(resolveAssetSourcePath({
23024
23297
  manifestDir,
@@ -23035,11 +23308,6 @@ function resolveBucketDeploymentDirs(template, manifest, stack, bucketLogicalId)
23035
23308
  }
23036
23309
  return dirs;
23037
23310
  }
23038
- function findFileAssetByObjectKey(manifest, objectKey) {
23039
- for (const asset of Object.values(manifest.files ?? {})) for (const dest of Object.values(asset.destinations ?? {})) if (dest.objectKey === objectKey) return asset;
23040
- const hash = objectKey.replace(/\.zip$/, "");
23041
- return manifest.files?.[hash];
23042
- }
23043
23311
  function pickBucketLogicalIdFromOrigin(origin, template) {
23044
23312
  const candidate = getAttLogicalId(origin["DomainName"]);
23045
23313
  if (!candidate) return void 0;
@@ -23067,6 +23335,11 @@ function resolveLambdaUrlOrigin(origin, template, originId) {
23067
23335
  functionUrlLogicalId: urlLogicalId
23068
23336
  };
23069
23337
  }
23338
+ function pickWebFunctionEndpointLogicalIdFromOrigin(value, template) {
23339
+ const getAtt = asIntrinsic(value, "Fn::GetAtt");
23340
+ if (!Array.isArray(getAtt) || getAtt.length !== 2 || typeof getAtt[0] !== "string" || getAtt[1] !== "DomainName") return;
23341
+ return (template.Resources ?? {})[getAtt[0]]?.Type === "AWS::Lambda::WebFunctionEndpoint" ? getAtt[0] : void 0;
23342
+ }
23070
23343
  function pickFunctionUrlLogicalIdFromOrigin(value) {
23071
23344
  const select = asIntrinsic(value, "Fn::Select");
23072
23345
  if (!Array.isArray(select) || select.length !== 2 || select[0] !== 2) return void 0;
@@ -23384,34 +23657,115 @@ function normalizeHeaders(headers) {
23384
23657
  }
23385
23658
 
23386
23659
  //#endregion
23387
- //#region src/local/cloudfront-static-origin.ts
23388
- function serveFromStaticOrigin(input) {
23389
- const key = uriToKey(input.uri, input.defaultRootObject);
23390
- const containLinks = input.containLinks === true;
23391
- const hideIn = new Set(input.hideDotfilesIn ?? []);
23392
- const direct = readKey(input.localDirs, key, containLinks, hideIn);
23393
- if (direct) return {
23394
- statusCode: 200,
23395
- headers: { "content-type": contentTypeForKey(key) },
23396
- body: direct
23397
- };
23398
- for (const candidate of resolveErrorResponseCandidates(input.customErrorResponses)) {
23399
- const body = readKey(input.localDirs, candidate.errorKey, containLinks, hideIn);
23400
- if (body) return {
23401
- statusCode: candidate.responseCode,
23402
- headers: { "content-type": contentTypeForKey(candidate.errorKey) },
23403
- body
23660
+ //#region src/local/cloudfront-web-function-origin.ts
23661
+ const DEFAULT_ORIGIN_READ_TIMEOUT_SECONDS = 30;
23662
+ function serveWebFunctionOrigin(args) {
23663
+ const { target, request: request$3 } = args;
23664
+ const timeoutMs = (args.readTimeoutSeconds ?? 30) * 1e3;
23665
+ const headers = buildOriginRequestHeaders(request$3, target);
23666
+ if (request$3.body.length > 0) headers["content-length"] = String(request$3.body.length);
23667
+ const path = request$3.querystring ? `${request$3.uri}?${request$3.querystring}` : request$3.uri;
23668
+ return new Promise((resolveResult) => {
23669
+ let settled = false;
23670
+ const settle = (result) => {
23671
+ if (settled) return;
23672
+ settled = true;
23673
+ resolveResult(result);
23404
23674
  };
23405
- }
23406
- return {
23407
- statusCode: 404,
23408
- headers: { "content-type": "text/plain; charset=utf-8" },
23409
- body: Buffer.from(`Not found: ${input.uri}\n`)
23410
- };
23411
- }
23412
- function resolveErrorResponseCandidates(customErrorResponses) {
23413
- const errorResponses = customErrorResponses ?? [];
23414
- const out = [];
23675
+ const req = request({
23676
+ host: target.host,
23677
+ port: target.port,
23678
+ method: request$3.method,
23679
+ path,
23680
+ headers,
23681
+ agent: false
23682
+ }, (res) => {
23683
+ const chunks = [];
23684
+ res.on("data", (c) => chunks.push(c));
23685
+ res.on("end", () => settle({
23686
+ statusCode: res.statusCode ?? 502,
23687
+ ...splitResponseHeaders(res.headers),
23688
+ body: Buffer.concat(chunks)
23689
+ }));
23690
+ res.on("error", (err) => settle(originError(502, err.message)));
23691
+ });
23692
+ req.setTimeout(timeoutMs, () => {
23693
+ settle(originError(504, `the web function server did not answer within the origin read timeout (${timeoutMs / 1e3}s)`));
23694
+ req.destroy();
23695
+ });
23696
+ req.on("error", (err) => settle(originError(502, err.message)));
23697
+ req.end(request$3.body.length > 0 ? request$3.body : void 0);
23698
+ });
23699
+ }
23700
+ function buildOriginRequestHeaders(request, target) {
23701
+ const headers = {};
23702
+ for (const [name, value] of Object.entries(request.headers)) if (value !== void 0) headers[name.toLowerCase()] = value;
23703
+ stripHopByHopHeaders(headers);
23704
+ delete headers["content-length"];
23705
+ headers["host"] = formatAuthority(target.host, target.port);
23706
+ if (request.sourceIp !== void 0) {
23707
+ const prior = headers["x-forwarded-for"];
23708
+ const priorValue = Array.isArray(prior) ? prior.join(", ") : prior;
23709
+ headers["x-forwarded-for"] = priorValue ? `${priorValue}, ${request.sourceIp}` : request.sourceIp;
23710
+ }
23711
+ const out = {};
23712
+ for (const [name, value] of Object.entries(headers)) if (value !== void 0) out[name] = value;
23713
+ return out;
23714
+ }
23715
+ function splitResponseHeaders(raw) {
23716
+ const copy = { ...raw };
23717
+ stripHopByHopHeaders(copy);
23718
+ delete copy["content-length"];
23719
+ const cookies = copy["set-cookie"];
23720
+ delete copy["set-cookie"];
23721
+ const headers = {};
23722
+ for (const [name, value] of Object.entries(copy)) {
23723
+ if (value === void 0) continue;
23724
+ headers[name] = Array.isArray(value) ? value.join(", ") : value;
23725
+ }
23726
+ return {
23727
+ headers,
23728
+ cookies: cookies === void 0 ? [] : Array.isArray(cookies) ? cookies : [cookies]
23729
+ };
23730
+ }
23731
+ function originError(statusCode, detail) {
23732
+ return {
23733
+ statusCode,
23734
+ headers: { "content-type": "text/plain; charset=utf-8" },
23735
+ cookies: [],
23736
+ body: Buffer.from(`${statusCode === 502 ? "Bad Gateway" : "Gateway Timeout"}: ${detail.replace(/[\r\n]+/g, " ")}\n`)
23737
+ };
23738
+ }
23739
+
23740
+ //#endregion
23741
+ //#region src/local/cloudfront-static-origin.ts
23742
+ function serveFromStaticOrigin(input) {
23743
+ const key = uriToKey(input.uri, input.defaultRootObject);
23744
+ const containLinks = input.containLinks === true;
23745
+ const hideIn = new Set(input.hideDotfilesIn ?? []);
23746
+ const direct = readKey(input.localDirs, key, containLinks, hideIn);
23747
+ if (direct) return {
23748
+ statusCode: 200,
23749
+ headers: { "content-type": contentTypeForKey(key) },
23750
+ body: direct
23751
+ };
23752
+ for (const candidate of resolveErrorResponseCandidates(input.customErrorResponses)) {
23753
+ const body = readKey(input.localDirs, candidate.errorKey, containLinks, hideIn);
23754
+ if (body) return {
23755
+ statusCode: candidate.responseCode,
23756
+ headers: { "content-type": contentTypeForKey(candidate.errorKey) },
23757
+ body
23758
+ };
23759
+ }
23760
+ return {
23761
+ statusCode: 404,
23762
+ headers: { "content-type": "text/plain; charset=utf-8" },
23763
+ body: Buffer.from(`Not found: ${input.uri}\n`)
23764
+ };
23765
+ }
23766
+ function resolveErrorResponseCandidates(customErrorResponses) {
23767
+ const errorResponses = customErrorResponses ?? [];
23768
+ const out = [];
23415
23769
  for (const code of [403, 404]) {
23416
23770
  const match = errorResponses.find((e) => e.errorCode === code);
23417
23771
  if (!match || !match.responsePagePath) continue;
@@ -23552,9 +23906,10 @@ async function startCloudFrontServer(options) {
23552
23906
  const lambdaInvokers = options.lambdaInvokers ?? /* @__PURE__ */ new Map();
23553
23907
  const edgeInvokers = options.edgeInvokers ?? /* @__PURE__ */ new Map();
23554
23908
  const s3OriginReaders = options.s3OriginReaders ?? /* @__PURE__ */ new Map();
23909
+ const webFunctionOrigins = options.webFunctionOrigins ?? /* @__PURE__ */ new Map();
23555
23910
  const state = { distribution: options.distribution };
23556
23911
  const handler = (req, res) => {
23557
- handleRequest$1(req, res, state, lambdaInvokers, edgeInvokers, s3OriginReaders, logger).catch((err) => {
23912
+ handleRequest$2(req, res, state, lambdaInvokers, edgeInvokers, s3OriginReaders, webFunctionOrigins, logger).catch((err) => {
23558
23913
  logger.warn(`Request handling failed: ${err instanceof Error ? err.message : String(err)}`);
23559
23914
  if (!res.headersSent) {
23560
23915
  res.statusCode = 500;
@@ -23583,7 +23938,7 @@ async function startCloudFrontServer(options) {
23583
23938
  }
23584
23939
  };
23585
23940
  }
23586
- async function handleRequest$1(req, res, state, lambdaInvokers, edgeInvokers, s3OriginReaders, logger) {
23941
+ async function handleRequest$2(req, res, state, lambdaInvokers, edgeInvokers, s3OriginReaders, webFunctionOrigins, logger) {
23587
23942
  const distribution = state.distribution;
23588
23943
  const rawUrl = req.url ?? "/";
23589
23944
  const queryIdx = rawUrl.indexOf("?");
@@ -23675,6 +24030,7 @@ async function handleRequest$1(req, res, state, lambdaInvokers, edgeInvokers, s3
23675
24030
  distribution,
23676
24031
  lambdaInvokers,
23677
24032
  s3OriginReaders,
24033
+ webFunctionOrigins,
23678
24034
  logger
23679
24035
  });
23680
24036
  if (!originResult) return writePlain(res, 502, originUnavailableMessage(origin, behavior));
@@ -23830,6 +24186,28 @@ async function serveFromOrigin(origin, args) {
23830
24186
  ...result.cookies.length > 0 && { setCookies: result.cookies }
23831
24187
  };
23832
24188
  }
24189
+ if (origin.kind === "web-function") {
24190
+ const target = args.webFunctionOrigins.get(origin.endpointLogicalId);
24191
+ if (!target) return void 0;
24192
+ const result = await serveWebFunctionOrigin({
24193
+ target,
24194
+ request: {
24195
+ method: args.method,
24196
+ uri: args.uri,
24197
+ querystring: args.querystring,
24198
+ headers: args.headers,
24199
+ body: await args.readBody(),
24200
+ sourceIp: args.sourceIp
24201
+ },
24202
+ ...origin.readTimeoutSeconds !== void 0 && { readTimeoutSeconds: origin.readTimeoutSeconds }
24203
+ });
24204
+ return {
24205
+ statusCode: result.statusCode,
24206
+ headers: result.headers,
24207
+ body: result.body,
24208
+ ...result.cookies.length > 0 && { setCookies: result.cookies }
24209
+ };
24210
+ }
23833
24211
  if (origin.kind === "s3-deployed") {
23834
24212
  const reader = args.s3OriginReaders.get(origin.originId);
23835
24213
  if (!reader) return void 0;
@@ -23862,7 +24240,8 @@ async function serveFromOrigin(origin, args) {
23862
24240
  function originUnavailableMessage(origin, behavior) {
23863
24241
  if (!origin) return `Behavior ${behavior.pathPattern === void 0 ? "(default)" : displayUntrustedValue(behavior.pathPattern)} targets unknown origin ${displayIdent(behavior.targetOriginId)}.\n`;
23864
24242
  if (origin.kind === "lambda-url") return `Origin ${displayIdent(origin.originId)} is a Lambda Function URL origin whose backing function ${displayIdent(origin.functionLogicalId)} was not booted (it was added after start-up). Restart start-cloudfront.\n`;
23865
- if (origin.kind === "custom") return `Origin ${displayIdent(origin.originId)} is a custom (non-S3) origin (${displayUntrustedValue(String(origin.domainName))}). cdkl start-cloudfront serves S3 origins and Lambda Function URL origins only.
24243
+ if (origin.kind === "web-function") return `Origin ${displayIdent(origin.originId)} is a Lambda Web Function endpoint origin whose endpoint ${displayIdent(origin.endpointLogicalId)} was not booted (it was added after start-up). Restart start-cloudfront.\n`;
24244
+ if (origin.kind === "custom") return `Origin ${displayIdent(origin.originId)} is a custom (non-S3) origin (${displayUntrustedValue(String(origin.domainName))}). cdkl start-cloudfront serves S3, Lambda Function URL and Lambda Web Function endpoint origins only.
23866
24245
  `;
23867
24246
  if (origin.kind === "s3-deployed") return `Origin ${displayIdent(origin.originId)} is a deployed-S3 origin (bucket ${displayIdent(origin.bucketName)}) whose reader was not booted (it was promoted after start-up). Restart start-cloudfront.
23868
24247
  `;
@@ -24156,252 +24535,857 @@ function idFromArn(arn) {
24156
24535
  }
24157
24536
 
24158
24537
  //#endregion
24159
- //#region src/cli/commands/local-start-cloudfront.ts
24160
- var LocalStartCloudFrontError = class extends CdkLocalError {
24161
- constructor(message, cause) {
24162
- super(message, "LOCAL_START_CLOUDFRONT_ERROR", cause);
24163
- }
24164
- };
24165
- function parseOriginOverrides(values) {
24166
- const out = /* @__PURE__ */ new Map();
24167
- for (const raw of values ?? []) {
24168
- const eq = raw.indexOf("=");
24169
- if (eq <= 0 || eq === raw.length - 1) throw new LocalStartCloudFrontError(`Invalid --origin '${raw}'. Expected <originId>=<dir> (e.g. MyOrigin=./site).`);
24170
- out.set(raw.slice(0, eq).trim(), raw.slice(eq + 1).trim());
24171
- }
24172
- return out;
24538
+ //#region src/local/web-function-service-config.ts
24539
+ const WEB_FUNCTION_HTTP_ENDPOINT_ENV = "AWS_LAMBDA_HTTP_ENDPOINT";
24540
+ const APPLICATION_MANAGED_AUTH = "ApplicationManaged";
24541
+ const HOME_REGION_ENDPOINT = "HomeRegion";
24542
+ function readWebFunctionServiceConfig(revision) {
24543
+ const sc = asObject$2(revision.Properties?.["ServiceConfig"]) ?? {};
24544
+ const envRaw = sc["EnvironmentVariables"];
24545
+ const envObj = asObject$2(envRaw);
24546
+ const envKeys = envObj ? Object.keys(envObj) : [];
24547
+ const env = envKeys.length === 1 && (envKeys[0] === "Ref" || envKeys[0].startsWith("Fn::")) ? void 0 : envObj;
24548
+ const logGroup = asObject$2(asObject$2(sc["TelemetryConfig"])?.["LoggingConfig"])?.["LogGroup"];
24549
+ return {
24550
+ environmentVariables: env,
24551
+ environmentUnreadable: envRaw !== void 0 && env === void 0,
24552
+ executionRoleArn: sc["ExecutionRoleArn"],
24553
+ egressNetworkConnectorArn: sc["EgressNetworkConnectorArn"],
24554
+ timeoutSeconds: typeof sc["TimeoutSeconds"] === "number" ? sc["TimeoutSeconds"] : void 0,
24555
+ logGroup: typeof logGroup === "string" && logGroup.length > 0 ? logGroup : void 0
24556
+ };
24173
24557
  }
24174
- function parseKvsFileOverrides(values) {
24175
- const out = /* @__PURE__ */ new Map();
24176
- for (const raw of values ?? []) {
24177
- const eq = raw.indexOf("=");
24178
- if (eq <= 0 || eq === raw.length - 1) throw new LocalStartCloudFrontError(`Invalid --kvs-file '${raw}'. Expected <key>=<file.json> (e.g. RoutesKvs=./kvs.json).`);
24179
- out.set(raw.slice(0, eq).trim(), raw.slice(eq + 1).trim());
24180
- }
24181
- return out;
24558
+ function buildWebFunctionLambdaView(resolved, config, runtime) {
24559
+ return {
24560
+ kind: "zip",
24561
+ stack: resolved.stack,
24562
+ logicalId: resolved.revisionLogicalId,
24563
+ resource: {
24564
+ Type: resolved.revision.Type,
24565
+ Properties: {
24566
+ ...config.environmentVariables !== void 0 && { Environment: { Variables: config.environmentVariables } },
24567
+ ...config.executionRoleArn !== void 0 && { Role: config.executionRoleArn }
24568
+ },
24569
+ ...resolved.revision.Metadata !== void 0 && { Metadata: resolved.revision.Metadata }
24570
+ },
24571
+ memoryMb: 128,
24572
+ timeoutSec: config.timeoutSeconds ?? 0,
24573
+ layers: [],
24574
+ runtime,
24575
+ handler: "",
24576
+ codePath: null,
24577
+ architecture: "arm64"
24578
+ };
24182
24579
  }
24183
- function collectKvsStoreIdentities(template) {
24184
- const out = [];
24185
- for (const [logicalId, resource] of Object.entries(template.Resources ?? {})) {
24186
- if (resource.Type !== "AWS::CloudFront::KeyValueStore") continue;
24187
- const cdkPath = readCdkPath(resource) || void 0;
24188
- const constructPath = cdkPath?.endsWith("/Resource") ? cdkPath.slice(0, -9) : cdkPath;
24189
- const constructId = constructPath?.split("/").pop();
24190
- out.push({
24191
- logicalId,
24192
- ...cdkPath !== void 0 && { cdkPath },
24193
- ...constructPath !== void 0 && { constructPath },
24194
- ...constructId !== void 0 && constructId !== "" && { constructId }
24195
- });
24196
- }
24197
- return out;
24580
+ function webFunctionAssumeRole(assumeRole, config) {
24581
+ if (assumeRole === true && isIamRoleArn(config.executionRoleArn)) return config.executionRoleArn;
24582
+ return assumeRole;
24198
24583
  }
24199
- function normalizeKvsFileKeys(kvsFiles, template) {
24200
- if (kvsFiles.size === 0) return kvsFiles;
24201
- const stores = collectKvsStoreIdentities(template);
24202
- const candidates = () => stores.map((s) => s.constructPath ? `${displayIdent(s.logicalId, { listMember: true })} (${displayStackName(s.constructPath)})` : displayIdent(s.logicalId, { listMember: true })).join(", ") || "(none in this distribution)";
24203
- const out = /* @__PURE__ */ new Map();
24204
- for (const [key, filePath] of kvsFiles) {
24205
- const matched = [
24206
- stores.filter((s) => s.logicalId === key),
24207
- stores.filter((s) => s.constructPath === key || s.cdkPath === key),
24208
- stores.filter((s) => s.constructId === key)
24209
- ].find((t) => t.length > 0) ?? [];
24210
- if (matched.length === 0) throw new LocalStartCloudFrontError(`--kvs-file key '${key}' matched no AWS::CloudFront::KeyValueStore in the distribution. Pass a store's logical id, construct path, or bare construct id. Candidates: ${candidates()}.`);
24211
- if (matched.length > 1) throw new LocalStartCloudFrontError(`--kvs-file key '${key}' is ambiguous — it matched ${matched.length} KeyValueStores (${matched.map((s) => displayIdent(s.logicalId, { listMember: true })).join(", ")}). Use the logical id or full construct path.`);
24212
- out.set(matched[0].logicalId, filePath);
24584
+ function finalizeWebFunctionEnv(lambdaEnv, opts) {
24585
+ const env = { ...lambdaEnv };
24586
+ delete env["AWS_LAMBDA_FUNCTION_MEMORY_SIZE"];
24587
+ delete env["AWS_LAMBDA_FUNCTION_VERSION"];
24588
+ delete env["AWS_LAMBDA_FUNCTION_TIMEOUT"];
24589
+ env["AWS_LAMBDA_FUNCTION_NAME"] = opts.functionName;
24590
+ env["AWS_LAMBDA_LOG_GROUP_NAME"] = opts.logGroup ?? `/aws/lambda/${opts.functionName}`;
24591
+ if (opts.timeoutSeconds !== void 0) env["AWS_LAMBDA_FUNCTION_TIMEOUT"] = String(opts.timeoutSeconds);
24592
+ const replacedHttpEndpoint = Object.hasOwn(lambdaEnv, WEB_FUNCTION_HTTP_ENDPOINT_ENV);
24593
+ env[WEB_FUNCTION_HTTP_ENDPOINT_ENV] = `0.0.0.0:${opts.containerPort}`;
24594
+ return {
24595
+ env,
24596
+ replacedHttpEndpoint
24597
+ };
24598
+ }
24599
+ function webFunctionFidelityWarnings(resolved, config) {
24600
+ const out = [];
24601
+ const rev = displayIdent(resolved.revisionLogicalId);
24602
+ if (config.egressNetworkConnectorArn !== void 0) out.push(`Revision ${rev} sets ServiceConfig.EgressNetworkConnectorArn; the network connector is not emulated, so the container reaches the network through Docker's default bridge.`);
24603
+ if (config.environmentUnreadable) out.push(`Revision ${rev} declares ServiceConfig.EnvironmentVariables as an intrinsic rather than a map; none of its variables reach the container. Supply them with --env-vars.`);
24604
+ const endpoint = resolved.endpoint;
24605
+ if (endpoint) {
24606
+ const ep = displayIdent(endpoint.logicalId);
24607
+ if (endpoint.endpointType !== void 0 && endpoint.endpointType !== "HomeRegion") out.push(`Endpoint ${ep} has EndpointType ${displayIdent(endpoint.endpointType)}; only a single ${HOME_REGION_ENDPOINT} endpoint is reproduced, on one local port.`);
24608
+ if (endpoint.authType !== void 0 && endpoint.authType !== "ApplicationManaged") out.push(`Endpoint ${ep} has AuthType ${displayIdent(endpoint.authType)}; it is not enforced locally — every request reaches the server unauthenticated.`);
24609
+ const routed = endpoint.weights.filter((w) => w.weight > 0);
24610
+ if (routed.length > 1) out.push(`Endpoint ${ep} splits traffic across ${routed.length} revisions; weighted routing is not emulated — every request goes to revision ${rev}.`);
24213
24611
  }
24214
24612
  return out;
24215
24613
  }
24216
- async function attachKvsModules(distribution, stacks, options, profileCredentials, logger, extraStateProviders) {
24217
- const stack = stacks.find((s) => s.stackName === distribution.stackName);
24218
- const synthRegion = stack?.region;
24219
- const kvsFiles = stack ? normalizeKvsFileKeys(parseKvsFileOverrides(options.kvsFile), stack.template) : parseKvsFileOverrides(options.kvsFile);
24220
- const resolveDeployedKvs = isStateSourceActive(options, extraStateProviders) ? async (kvsLogicalId) => {
24221
- const provider = createLocalStateProvider(options, distribution.stackName, synthRegion, extraStateProviders);
24222
- if (!provider) return void 0;
24223
- let record;
24224
- try {
24225
- record = await provider.load(distribution.stackName, synthRegion);
24226
- } finally {
24227
- provider.dispose();
24228
- }
24229
- const physicalId = record?.resources[kvsLogicalId]?.physicalId;
24230
- if (!physicalId) return void 0;
24231
- if (physicalId.startsWith("arn:")) {
24232
- const id = idFromArn(physicalId);
24233
- return {
24234
- arn: physicalId,
24235
- ...id !== void 0 && { id }
24236
- };
24237
- }
24238
- return resolveDeployedKvsArnByName(physicalId, { ...profileCredentials !== void 0 && { credentials: profileCredentials } });
24239
- } : void 0;
24240
- const { warnings } = await resolveKvsModulesForDistribution(distribution, {
24241
- ...kvsFiles.size > 0 && { kvsFiles },
24242
- ...resolveDeployedKvs !== void 0 && { resolveDeployedKvs },
24243
- ...profileCredentials !== void 0 && { credentials: profileCredentials }
24244
- });
24245
- for (const warning of warnings) logger.warn(warning);
24614
+ function asObject$2(value) {
24615
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
24246
24616
  }
24247
- function resolveCloudFrontTarget(target, stacks) {
24248
- if (stacks.length === 0) throw new LocalStartCloudFrontError("No stacks found in the synthesized assembly.");
24249
- const parsed = parseEcsTarget(target);
24250
- const stack = pickStack$1(parsed, stacks, target);
24251
- const resources = stack.template.Resources ?? {};
24252
- if (parsed.isPath) {
24253
- const index = buildCdkPathIndex(stack.template);
24254
- const dists = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId }) => {
24255
- const r = resources[logicalId];
24256
- return r !== void 0 && r.Type === "AWS::CloudFront::Distribution";
24257
- });
24258
- if (dists.length === 0) throw notFound(target, stack, resources);
24259
- if (dists.length > 1) throw new LocalStartCloudFrontError(`Target '${target}' matches ${dists.length} distributions in ${stack.stackName}: ${dists.map((d) => d.logicalId).join(", ")}. Refine the path or use the stack:LogicalId form.`);
24260
- return {
24261
- stack,
24262
- logicalId: dists[0].logicalId
24617
+
24618
+ //#endregion
24619
+ //#region src/local/web-function-artifact.ts
24620
+ const SUPPORTED_CODE_CONFIG_KEYS = ["S3Object"];
24621
+ function parseWebFunctionBuildConfig(revisionLogicalId, buildConfig, template) {
24622
+ const subject = `Web function revision ${displayIdent(revisionLogicalId)}`;
24623
+ const build = asObject$1(buildConfig);
24624
+ const runtime = asObject$1(build?.["RuntimeConfig"])?.["Runtime"];
24625
+ if (typeof runtime !== "string" || runtime.length === 0) throw new CdkLocalError(`${subject} has no literal BuildConfig.RuntimeConfig.Runtime.`, "LOCAL_START_WEB_FUNCTION_NO_RUNTIME");
24626
+ const codeConfig = asObject$1(build?.["CodeConfig"]);
24627
+ if (!codeConfig) throw new CdkLocalError(`${subject} has no BuildConfig.CodeConfig object.`, "LOCAL_START_WEB_FUNCTION_NO_CODE_CONFIG");
24628
+ const unknownKeys = Object.keys(codeConfig).filter((k) => !SUPPORTED_CODE_CONFIG_KEYS.includes(k));
24629
+ if (unknownKeys.length > 0) throw new CdkLocalError(`${subject} uses BuildConfig.CodeConfig.${unknownKeys.map((k) => displayIdent(k)).join(", ")}, which ${getEmbedConfig().cliName} start-web-function does not support yet. Supported: ${SUPPORTED_CODE_CONFIG_KEYS.join(", ")}.`, "LOCAL_START_WEB_FUNCTION_UNSUPPORTED_CODE_CONFIG");
24630
+ const s3 = asObject$1(codeConfig["S3Object"]);
24631
+ if (!s3) throw new CdkLocalError(`${subject} has no BuildConfig.CodeConfig.S3Object.`, "LOCAL_START_WEB_FUNCTION_NO_CODE_CONFIG");
24632
+ const rawKey = s3["Key"];
24633
+ let key;
24634
+ let deployment;
24635
+ if (typeof rawKey === "string" && rawKey.length > 0) key = rawKey;
24636
+ else {
24637
+ const viaDeployment = template ? resolveBucketDeploymentObjectKey(template, rawKey) : void 0;
24638
+ if (viaDeployment === void 0) throw new CdkLocalError(`${subject} has a CodeConfig.S3Object.Key that is not a literal string (${describeValue$1(rawKey)}). The code must come from a CDK asset in this app: an s3assets.Asset passing asset.s3BucketName / asset.s3ObjectKey, whose key synthesizes as a literal` + (template ? ", or a BucketDeployment with extract: false passing Fn.select(<i>, deployment.objectKeys), which resolves to its i-th source asset." : "."), "LOCAL_START_WEB_FUNCTION_CODE_KEY_NOT_LITERAL");
24639
+ key = bucketDeploymentSourceKey(subject, viaDeployment);
24640
+ deployment = {
24641
+ logicalId: viaDeployment.deploymentLogicalId,
24642
+ index: viaDeployment.index
24263
24643
  };
24264
24644
  }
24265
- const res = resources[parsed.pathOrId];
24266
- if (!res || res.Type !== "AWS::CloudFront::Distribution") throw notFound(target, stack, resources);
24645
+ const versionId = s3["VersionId"];
24267
24646
  return {
24268
- stack,
24269
- logicalId: parsed.pathOrId
24647
+ runtime,
24648
+ s3Object: {
24649
+ bucket: s3["Bucket"],
24650
+ key,
24651
+ ...typeof versionId === "string" && { versionId },
24652
+ ...deployment !== void 0 && { deployment }
24653
+ }
24270
24654
  };
24271
24655
  }
24272
- function pickStack$1(parsed, stacks, target) {
24273
- const selection = selectTargetStack(parsed, target, stacks);
24274
- switch (selection.kind) {
24275
- case "found": return selection.stack;
24276
- case "no-prefix": throw new LocalStartCloudFrontError(`Target '${target}' has no stack prefix, and the assembly contains ${stacks.length} stacks: ${describeStacks(stacks)}. Pass it as 'Stack/Path' or 'Stack:LogicalId'.`);
24277
- case "not-found": throw new LocalStartCloudFrontError(`No stack matches '${selection.pattern}'. Available stacks: ${describeStacks(stacks)}.`);
24278
- case "ambiguous": throw new LocalStartCloudFrontError(`Multiple stacks match '${selection.pattern}': ${describeStacks(selection.matched)}. Refine the pattern, or use the stack's display path (path form).`);
24656
+ function bucketDeploymentSourceKey(subject, r) {
24657
+ const named = `CodeConfig.S3Object.Key selects source ${r.index} of BucketDeployment ${displayIdent(r.deploymentLogicalId)}`;
24658
+ switch (r.kind) {
24659
+ case "resolved": return r.sourceObjectKey;
24660
+ case "extracts": throw new CdkLocalError(`${subject} ${named}, which does not set extract to a literal false, so it unpacks its sources and no object in the destination bucket holds that source's zip (Lambda could not read it either). Set extract: false on the BucketDeployment so it copies the zip as-is.`, "LOCAL_START_WEB_FUNCTION_CODE_KEY_DEPLOYMENT_EXTRACTS");
24661
+ case "prefixed-destination": throw new CdkLocalError(`${subject} ${named}, which sets a destinationKeyPrefix: the zip lands at <prefix><key> while objectKeys returns <key>, so the selected key names no object (Lambda could not read it either). Remove destinationKeyPrefix from the deployment.`, "LOCAL_START_WEB_FUNCTION_CODE_KEY_DEPLOYMENT_SOURCE_UNRESOLVED");
24662
+ case "object-keys-not-output": throw new CdkLocalError(`${subject} ${named}, which sets outputObjectKeys: false, so its SourceObjectKeys attribute is empty and the selection has no value. Remove outputObjectKeys: false.`, "LOCAL_START_WEB_FUNCTION_CODE_KEY_DEPLOYMENT_SOURCE_UNRESOLVED");
24663
+ case "index-out-of-range": throw new CdkLocalError(`${subject} ${named}, which has only ${r.sourceCount} source(s).`, "LOCAL_START_WEB_FUNCTION_CODE_KEY_DEPLOYMENT_SOURCE_UNRESOLVED");
24664
+ case "source-key-not-literal": throw new CdkLocalError(`${subject} ${named}, whose SourceObjectKeys entry is not a literal string, so it is not a CDK asset of this app. Deploy the code with s3deploy.Source.asset(...).`, "LOCAL_START_WEB_FUNCTION_CODE_KEY_DEPLOYMENT_SOURCE_UNRESOLVED");
24279
24665
  }
24280
24666
  }
24281
- function notFound(target, stack, resources) {
24282
- const dists = Object.entries(resources).filter(([, r]) => r.Type === CLOUDFRONT_DISTRIBUTION_TYPE).map(([logicalId]) => logicalId);
24283
- const available = dists.length > 0 ? ` Available distributions in ${stack.stackName}: ${dists.join(", ")}.` : ` ${stack.stackName} declares no ${CLOUDFRONT_DISTRIBUTION_TYPE} resources.`;
24284
- return new LocalStartCloudFrontError(`Target '${target}' did not match a CloudFront distribution in ${stack.stackName}.${available}`);
24285
- }
24286
- async function bootLambdaUrlOrigins(distribution, stacks, opts) {
24287
- const logger = getLogger();
24288
- const invokers = /* @__PURE__ */ new Map();
24289
- const runners = [];
24290
- const functionLogicalIds = /* @__PURE__ */ new Set();
24291
- for (const origin of distribution.origins.values()) if (origin.kind === "lambda-url") functionLogicalIds.add(origin.functionLogicalId);
24292
- for (const functionLogicalId of functionLogicalIds) {
24293
- let runner;
24294
- try {
24295
- const lambda = resolveLambdaTarget(`${distribution.stackName}:${functionLogicalId}`, stacks);
24296
- const containerEnv = await resolveLambdaContainerEnv(lambda, opts.envOptions, opts.profileCredentials, opts.extraStateProviders);
24297
- runner = createFrontDoorLambdaRunner(lambda, {
24298
- containerHost: opts.containerHost,
24299
- skipPull: opts.skipPull,
24300
- ...opts.envOptions.region !== void 0 && { region: opts.envOptions.region },
24301
- containerEnv: containerEnv.env,
24302
- ...containerEnv.sensitiveEnvKeys.length > 0 && { sensitiveEnvKeys: new Set(containerEnv.sensitiveEnvKeys) }
24303
- });
24304
- logger.info(`Booting Lambda Function URL origin container for ${functionLogicalId} (the backing Lambda runs locally via RIE)...`);
24305
- await runner.start();
24306
- } catch (err) {
24307
- await Promise.all(runners.map((r) => r.stop().catch(() => void 0)));
24308
- throw new LocalStartCloudFrontError(`Failed to boot the Lambda Function URL origin's backing function ${displayIdent(functionLogicalId)}: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : void 0);
24309
- }
24310
- runners.push(runner);
24311
- invokers.set(functionLogicalId, (event) => runner.invoke(event));
24312
- }
24667
+ async function resolveWebFunctionArtifact(stack, revisionLogicalId, code, loader = new AssetManifestLoader()) {
24668
+ const subject = `Web function revision ${displayUntrustedValue(revisionLogicalId)} code bundle`;
24669
+ const { deployment } = code.s3Object;
24670
+ const keyOrigin = deployment ? ` (source ${deployment.index} of BucketDeployment ${displayIdent(deployment.logicalId)})` : "";
24671
+ const notFound = () => new CdkLocalError(`${subject} (S3Object bucket ${describeValue$1(code.s3Object.bucket)}, key ${displayUntrustedValue(code.s3Object.key)}${keyOrigin}) matches no file asset in this app's cdk.out. ${getEmbedConfig().cliName} start-web-function runs code that comes from a CDK asset in this app (for example an s3assets.Asset of a directory); it does not download deployed code. Re-synthesize the app, or point the revision at an asset.`, "LOCAL_START_WEB_FUNCTION_CODE_ASSET_NOT_FOUND");
24672
+ const manifestPath = stack.assetManifestPath;
24673
+ if (!manifestPath) throw notFound();
24674
+ const manifestDir = dirname$1(manifestPath);
24675
+ const manifest = await loader.loadStackManifest(stack, manifestDir);
24676
+ const match = manifest ? findFileAssetForObjectKey(manifest, code.s3Object.key) : void 0;
24677
+ if (!match) throw notFound();
24678
+ if (typeof match.asset.source.path !== "string" || match.asset.source.path.length === 0) throw new CdkLocalError(`${subject} matches file asset ${displayIdent(match.hash)}, which has no source.path (it is produced by source.executable at publish time), so there is no staged bundle in cdk.out to mount. Give the revision an asset of a directory or a .zip file (for example s3assets.Asset with path: '<dir>').`, "LOCAL_START_WEB_FUNCTION_CODE_ASSET_NO_PATH");
24679
+ const sourcePath = resolveAssetSourcePath({
24680
+ manifestDir,
24681
+ value: match.asset.source.path,
24682
+ assetOutdir: assetPathDirs(stack).assetOutdir,
24683
+ absolute: "honour-warn",
24684
+ field: "source.path",
24685
+ subject,
24686
+ action: "mount it",
24687
+ sink: "bind-mount that directory into the web function container",
24688
+ wrapError: (message) => new CdkLocalError(message, "LOCAL_START_WEB_FUNCTION_CODE_SOURCE_ESCAPES_ASSEMBLY")
24689
+ });
24690
+ if (!existsSync$1(sourcePath)) throw new CdkLocalError(`${subject} source ${displayUntrustedValue(sourcePath)} does not exist. Re-synthesize the app and retry.`, "LOCAL_START_WEB_FUNCTION_CODE_SOURCE_MISSING");
24691
+ if (!statSync$1(sourcePath).isDirectory() && !sourcePath.endsWith(".zip")) throw new CdkLocalError(`${subject} source ${displayUntrustedValue(sourcePath)} is neither a directory nor a .zip archive, so it is not a code bundle.`, "LOCAL_START_WEB_FUNCTION_CODE_SOURCE_NOT_BUNDLE");
24692
+ const materialized = materializeAssetCodeDir(sourcePath);
24313
24693
  return {
24314
- invokers,
24315
- runners
24694
+ kind: "zip",
24695
+ runtime: code.runtime,
24696
+ codeDir: materialized.dir,
24697
+ ...materialized.tmpDir !== void 0 && { tmpDir: materialized.tmpDir },
24698
+ assetHash: match.hash
24316
24699
  };
24317
24700
  }
24318
- async function bootLambdaEdgeFunctions(distribution, stacks, opts) {
24319
- const logger = getLogger();
24320
- const invokers = /* @__PURE__ */ new Map();
24321
- const runners = [];
24322
- const functionLogicalIds = /* @__PURE__ */ new Set();
24323
- for (const behavior of distribution.behaviors) {
24324
- const edge = behavior.lambdaEdge;
24325
- if (!edge) continue;
24326
- for (const assoc of [
24327
- edge.viewerRequest,
24328
- edge.originRequest,
24329
- edge.originResponse,
24330
- edge.viewerResponse
24331
- ]) if (assoc) functionLogicalIds.add(assoc.functionLogicalId);
24332
- }
24333
- for (const functionLogicalId of functionLogicalIds) try {
24334
- const lambda = resolveLambdaTarget(`${distribution.stackName}:${functionLogicalId}`, stacks);
24335
- const containerEnv = await resolveLambdaContainerEnv(lambda, opts.envOptions, opts.profileCredentials, opts.extraStateProviders);
24336
- const runner = createFrontDoorLambdaRunner(lambda, {
24337
- containerHost: opts.containerHost,
24338
- skipPull: opts.skipPull,
24339
- ...opts.envOptions.region !== void 0 && { region: opts.envOptions.region },
24340
- containerEnv: containerEnv.env,
24341
- ...containerEnv.sensitiveEnvKeys.length > 0 && { sensitiveEnvKeys: new Set(containerEnv.sensitiveEnvKeys) }
24701
+ function disposeWebFunctionArtifact(artifact) {
24702
+ if (artifact?.kind !== "zip" || artifact.tmpDir === void 0) return;
24703
+ try {
24704
+ if (existsSync$1(artifact.tmpDir)) rmSync$1(artifact.tmpDir, {
24705
+ recursive: true,
24706
+ force: true
24342
24707
  });
24343
- logger.info(`Booting Lambda@Edge container for ${functionLogicalId} (the function runs locally via RIE)...`);
24344
- await runner.start();
24345
- runners.push(runner);
24346
- invokers.set(functionLogicalId, (event) => runner.invoke(event));
24347
- } catch (err) {
24348
- logger.warn(`Could not boot Lambda@Edge function ${displayIdent(functionLogicalId)}; its stage will be skipped: ${err instanceof Error ? err.message : String(err)}`);
24708
+ } catch {}
24709
+ }
24710
+ function asObject$1(value) {
24711
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
24712
+ }
24713
+ function describeValue$1(value) {
24714
+ if (typeof value === "string") return displayUntrustedValue(value);
24715
+ let json;
24716
+ try {
24717
+ json = JSON.stringify(value);
24718
+ } catch {
24719
+ json = void 0;
24349
24720
  }
24350
- return {
24351
- invokers,
24352
- runners
24353
- };
24721
+ return displayUntrustedValue(json ?? String(value));
24354
24722
  }
24355
- function buildLambdaEnvOptions(options, extraStateProviders) {
24723
+
24724
+ //#endregion
24725
+ //#region src/local/web-function-runtime.ts
24726
+ const NODE_CODE_MOUNT = "/var/task";
24727
+ const NODE_ENTRYPOINT_ENV = "AWS_LAMBDA_NODEJS_ENTRYPOINT";
24728
+ const NODE_DEFAULT_ENTRY = "index.js";
24729
+ function buildNodeEntryFileStart(runtime, env, codeDir, fileExists = existsSync$1) {
24730
+ const configured = env[NODE_ENTRYPOINT_ENV];
24731
+ const entryFile = configured !== void 0 && configured.length > 0 ? configured : NODE_DEFAULT_ENTRY;
24732
+ const hostPath = resolve$1(codeDir, entryFile);
24733
+ const rel = relative$1(codeDir, hostPath);
24734
+ if (isAbsolute$1(entryFile) || rel === "" || rel === ".." || rel.startsWith(`..${sep$1}`) || isAbsolute$1(rel)) throw new CdkLocalError(`${NODE_ENTRYPOINT_ENV}=${displayUntrustedValue(entryFile)} must name a file inside the code bundle, relative to its root.`, "LOCAL_START_WEB_FUNCTION_ENTRYPOINT_OUTSIDE_BUNDLE");
24735
+ if (!fileExists(hostPath)) throw new CdkLocalError(`The ${runtime} web function entry file ${displayUntrustedValue(entryFile)} was not found in the code bundle ${displayUntrustedValue(codeDir)}. The runtime loads ${NODE_DEFAULT_ENTRY} from the package root unless ${NODE_ENTRYPOINT_ENV} names another file.`, "LOCAL_START_WEB_FUNCTION_ENTRYPOINT_MISSING");
24736
+ const posixRel = sep$1 === "\\" ? rel.split("\\").join("/") : rel;
24356
24737
  return {
24357
- ...hostStateSourceFields(options, extraStateProviders),
24358
- ...options.fromCfnStack !== void 0 && { fromCfnStack: options.fromCfnStack },
24359
- ...options.assumeRole !== void 0 && { assumeRole: options.assumeRole },
24360
- ...options.region !== void 0 && { region: options.region },
24361
- ...options.profile !== void 0 && { profile: options.profile },
24362
- ...options.stackRegion !== void 0 && { stackRegion: options.stackRegion }
24738
+ entryPoint: ["/var/lang/bin/node"],
24739
+ cmd: [posix.join(NODE_CODE_MOUNT, posixRel)],
24740
+ label: entryFile
24363
24741
  };
24364
24742
  }
24365
- function buildLambdaBootOptions(options, profileCredentials, extraStateProviders) {
24743
+ const WEB_FUNCTION_RUNTIMES = { "nodejs24.x": {
24744
+ runtime: "nodejs24.x",
24745
+ baseImage: "public.ecr.aws/lambda/nodejs:24",
24746
+ codeMountPath: NODE_CODE_MOUNT,
24747
+ architectures: ["arm64"],
24748
+ buildStart: (env, codeDir, deps) => buildNodeEntryFileStart("nodejs24.x", env, codeDir, deps?.fileExists)
24749
+ } };
24750
+ function supportedWebFunctionRuntimes(table = WEB_FUNCTION_RUNTIMES) {
24751
+ return Object.keys(table).sort();
24752
+ }
24753
+ function resolveWebFunctionRuntime(runtime, table = WEB_FUNCTION_RUNTIMES) {
24754
+ const spec = Object.hasOwn(table, runtime) ? table[runtime] : void 0;
24755
+ if (!spec) throw new CdkLocalError(`Web function runtime ${displayIdent(runtime)} is not supported by ${getEmbedConfig().cliName} start-web-function yet. Supported: ${supportedWebFunctionRuntimes(table).join(", ")}.`, "LOCAL_START_WEB_FUNCTION_UNSUPPORTED_RUNTIME");
24756
+ return spec;
24757
+ }
24758
+ function defaultWebFunctionPlatform(spec) {
24759
+ return spec.architectures[0] === "x86_64" ? "linux/amd64" : "linux/arm64";
24760
+ }
24761
+
24762
+ //#endregion
24763
+ //#region src/local/web-function-runner.ts
24764
+ const execFileAsync$1 = promisify$1(execFile$1);
24765
+ const WEB_FUNCTION_CONTAINER_PORT = 8080;
24766
+ const READY_FAILURE_LOG_LINES = 20;
24767
+ const MAX_PROBE_TIMEOUT_MS = 1e4;
24768
+ function planWebFunctionContainer(artifact, spec, env, deps) {
24769
+ if (artifact.kind !== "zip") throw new CdkLocalError("A container-image web function source is not supported yet; only a zip bundle from CodeConfig.S3Object runs locally.", "LOCAL_START_WEB_FUNCTION_UNSUPPORTED_ARTIFACT");
24770
+ const start = spec.buildStart(env, artifact.codeDir, deps);
24366
24771
  return {
24367
- containerHost: options.host,
24368
- skipPull: options.pull === false,
24369
- envOptions: buildLambdaEnvOptions(options, extraStateProviders),
24370
- ...profileCredentials !== void 0 && { profileCredentials },
24371
- ...extraStateProviders !== void 0 && { extraStateProviders }
24772
+ image: spec.baseImage,
24773
+ mounts: [{
24774
+ hostPath: artifact.codeDir,
24775
+ containerPath: spec.codeMountPath,
24776
+ readOnly: true
24777
+ }],
24778
+ entryPoint: start.entryPoint,
24779
+ cmd: start.cmd,
24780
+ workingDir: spec.codeMountPath,
24781
+ label: start.label
24372
24782
  };
24373
24783
  }
24374
- function warnUnsupported(distribution, extraStateProviders) {
24375
- const logger = getLogger();
24376
- for (const origin of distribution.origins.values()) if (origin.kind === "custom") logger.warn(`Origin ${displayIdent(origin.originId)} is a custom (non-S3, non-Lambda-Function-URL) origin (${displayUntrustedValue(String(origin.domainName))}); ${getEmbedConfig().cliName} start-cloudfront serves S3 origins and Lambda Function URL origins only. Requests routed to it return 502.`);
24377
- else if (origin.kind === "s3-unresolved") logger.warn(`Origin ${displayIdent(origin.originId)} is an S3 origin with no resolvable local source (no BucketDeployment found, or its source could not be located in the cloud assembly). Pass ${stateSourceFlagNames(extraStateProviders).join(" or ")} to serve the deployed bucket from real S3 on demand, or point it at a local directory with --origin ${origin.originId}=<dir>. Requests routed to it return 502.`);
24784
+ async function startWebFunctionContainer(opts) {
24785
+ const { plan } = opts;
24786
+ return runDetached({
24787
+ image: plan.image,
24788
+ mounts: plan.mounts,
24789
+ env: opts.env,
24790
+ ...opts.sensitiveEnvKeys.size > 0 && { sensitiveEnvKeys: opts.sensitiveEnvKeys },
24791
+ entryPoint: plan.entryPoint,
24792
+ cmd: plan.cmd,
24793
+ ...plan.workingDir !== void 0 && { workingDir: plan.workingDir },
24794
+ hostPort: opts.hostPort,
24795
+ containerPort: WEB_FUNCTION_CONTAINER_PORT,
24796
+ host: opts.host,
24797
+ platform: opts.platform,
24798
+ name: opts.name,
24799
+ autoRemove: false
24800
+ });
24378
24801
  }
24379
- function warnUnusedCacheOrigin(options, extraStateProviders) {
24380
- if (options.cacheOrigin === true && !isStateSourceActive(options, extraStateProviders)) {
24381
- const flags = stateSourceFlagNames(extraStateProviders).join(" or ");
24382
- getLogger().warn(`--cache-origin has no effect without ${flags}: it caches a deployed-S3 read-through origin, which is only built under a state source. A local BucketDeployment / --origin <id>=<dir> origin serves from disk and is not cached. Pass ${flags} to enable the deployed-S3 origin (then --cache-origin applies), or drop --cache-origin.`);
24802
+ var RecentOutput = class {
24803
+ lines = [];
24804
+ partial = "";
24805
+ max;
24806
+ constructor(max = 20) {
24807
+ this.max = max;
24383
24808
  }
24384
- }
24385
- async function resolveDeployedS3Origins(distribution, stacks, options, profileCredentials, logger, extraStateProviders) {
24386
- const readers = /* @__PURE__ */ new Map();
24387
- const buckets = /* @__PURE__ */ new Map();
24388
- const stateFlag = activeStateSourceFlag(options, extraStateProviders);
24389
- if (stateFlag === void 0) return {
24390
- readers,
24391
- buckets
24392
- };
24393
- const synthRegion = stacks.find((s) => s.stackName === distribution.stackName)?.region;
24394
- const provider = createLocalStateProvider(options, distribution.stackName, synthRegion, extraStateProviders);
24395
- let record;
24396
- try {
24397
- record = provider ? await provider.load(distribution.stackName, synthRegion) : void 0;
24398
- } finally {
24399
- provider?.dispose();
24809
+ push(chunk) {
24810
+ const parts = (this.partial + chunk.toString()).split("\n");
24811
+ this.partial = parts.pop() ?? "";
24812
+ for (const line of parts) this.add(line);
24813
+ if (this.partial.length > 4096) {
24814
+ this.add(this.partial);
24815
+ this.partial = "";
24816
+ }
24400
24817
  }
24401
- const region = options.stackRegion ?? options.region ?? synthRegion ?? record?.region;
24402
- const distributionId = record?.resources[distribution.logicalId]?.physicalId;
24403
- for (const origin of [...distribution.origins.values()]) {
24404
- if (origin.kind !== "s3-unresolved") continue;
24818
+ tail() {
24819
+ return this.partial.length > 0 ? [...this.lines, this.partial].slice(-this.max) : this.lines;
24820
+ }
24821
+ add(line) {
24822
+ this.lines.push(line.replace(/\r$/, ""));
24823
+ if (this.lines.length > this.max) this.lines.shift();
24824
+ }
24825
+ };
24826
+ async function waitForWebFunctionReady(args, deps = defaultReadyDeps) {
24827
+ const deadline = deps.now() + args.timeoutMs;
24828
+ let attempt = 0;
24829
+ let last = "refused";
24830
+ while (deps.now() < deadline) {
24831
+ const remaining = Math.max(1, deadline - deps.now());
24832
+ last = await deps.probe(args.host, args.port, Math.min(remaining, MAX_PROBE_TIMEOUT_MS));
24833
+ if (last === "answered") return;
24834
+ attempt++;
24835
+ if (attempt % 4 === 0 && !await deps.isRunning(args.containerId)) throw await containerFailure(`The web function container exited before its server listened on AWS_LAMBDA_HTTP_ENDPOINT (started ${args.label}).`, args.containerId, args.recent, deps.fetchLogs);
24836
+ if (last === "refused") await deps.sleep(250);
24837
+ }
24838
+ const authority = formatAuthority(args.host, args.port);
24839
+ throw await containerFailure(last === "timeout" ? `The web function server on ${authority} accepted the connection but did not answer GET / within ${args.timeoutMs}ms — it is listening but slow to respond. Raise --timeout, or make / answer sooner (started ${args.label}).` : `The web function server never accepted an HTTP connection on ${authority} within ${args.timeoutMs}ms. It must listen on the host:port given in AWS_LAMBDA_HTTP_ENDPOINT (started ${args.label}).`, args.containerId, args.recent, deps.fetchLogs);
24840
+ }
24841
+ async function containerFailure(message, containerId, recent, fetchLogs = readContainerLogTail, code = "LOCAL_START_WEB_FUNCTION_NOT_READY") {
24842
+ let lines = [];
24843
+ try {
24844
+ lines = await fetchLogs(containerId, 20);
24845
+ } catch {
24846
+ lines = [];
24847
+ }
24848
+ if (lines.length === 0) lines = recent.tail();
24849
+ return new CdkLocalError(`${message}${lines.length > 0 ? ` Last ${lines.length} line(s) of container output:\n${lines.map((l) => ` ${displayToolOutput(l)}`).join("\n")}` : " The container printed nothing."}`, code);
24850
+ }
24851
+ function watchContainerExit(containerId) {
24852
+ let stopped = false;
24853
+ const proc = spawn$1(getDockerCmd(), ["wait", containerId], { stdio: [
24854
+ "ignore",
24855
+ "pipe",
24856
+ "ignore"
24857
+ ] });
24858
+ let out = "";
24859
+ proc.stdout?.on("data", (c) => out += c.toString());
24860
+ return {
24861
+ exited: new Promise((resolveExit) => {
24862
+ const done = () => {
24863
+ if (stopped) return;
24864
+ const code = Number.parseInt(out.trim(), 10);
24865
+ resolveExit(Number.isNaN(code) ? void 0 : code);
24866
+ };
24867
+ proc.on("close", done);
24868
+ proc.on("error", done);
24869
+ }),
24870
+ stop: () => {
24871
+ stopped = true;
24872
+ if (!proc.killed) proc.kill("SIGTERM");
24873
+ }
24874
+ };
24875
+ }
24876
+ function probeHttp(host, port, timeoutMs) {
24877
+ return new Promise((resolveProbe) => {
24878
+ let timedOut = false;
24879
+ let connected = false;
24880
+ const req = request$2({
24881
+ host,
24882
+ port,
24883
+ path: "/",
24884
+ method: "GET",
24885
+ timeout: timeoutMs,
24886
+ headers: { connection: "close" }
24887
+ }, (res) => {
24888
+ res.resume();
24889
+ resolveProbe("answered");
24890
+ });
24891
+ req.on("socket", (socket) => {
24892
+ socket.once("connect", () => {
24893
+ connected = true;
24894
+ });
24895
+ });
24896
+ req.on("timeout", () => {
24897
+ timedOut = true;
24898
+ req.destroy();
24899
+ });
24900
+ req.on("error", () => resolveProbe(timedOut && connected ? "timeout" : "refused"));
24901
+ req.end();
24902
+ });
24903
+ }
24904
+ async function containerIsRunning(containerId) {
24905
+ try {
24906
+ const { stdout } = await execFileAsync$1(getDockerCmd(), [
24907
+ "inspect",
24908
+ "-f",
24909
+ "{{.State.Running}}",
24910
+ containerId
24911
+ ]);
24912
+ return stdout.trim() === "true";
24913
+ } catch {
24914
+ return false;
24915
+ }
24916
+ }
24917
+ function readContainerLogTail(containerId, lines, spawnFn = spawn$1) {
24918
+ return new Promise((resolveLogs, rejectLogs) => {
24919
+ const proc = spawnFn(getDockerCmd(), [
24920
+ "logs",
24921
+ "--tail",
24922
+ String(lines),
24923
+ containerId
24924
+ ], { stdio: [
24925
+ "ignore",
24926
+ "pipe",
24927
+ "pipe"
24928
+ ] });
24929
+ const chunks = [];
24930
+ let size = 0;
24931
+ const take = (c) => {
24932
+ if (size > 4 * 1024 * 1024) return;
24933
+ chunks.push(c);
24934
+ size += c.length;
24935
+ };
24936
+ proc.stdout?.on("data", take);
24937
+ proc.stderr?.on("data", take);
24938
+ proc.on("error", rejectLogs);
24939
+ proc.on("close", (code) => {
24940
+ if (code !== 0) {
24941
+ rejectLogs(/* @__PURE__ */ new Error(`docker logs exited ${code}`));
24942
+ return;
24943
+ }
24944
+ resolveLogs(Buffer.concat(chunks).toString().split("\n").map((l) => l.replace(/\r$/, "")).filter((l) => l.length > 0).slice(-lines));
24945
+ });
24946
+ });
24947
+ }
24948
+ const defaultReadyDeps = {
24949
+ probe: probeHttp,
24950
+ isRunning: containerIsRunning,
24951
+ fetchLogs: readContainerLogTail,
24952
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
24953
+ now: () => Date.now()
24954
+ };
24955
+
24956
+ //#endregion
24957
+ //#region src/cli/commands/web-function-serve.ts
24958
+ function prepareWebFunctionServe(resolved) {
24959
+ const code = parseWebFunctionBuildConfig(resolved.revisionLogicalId, resolved.revision.Properties?.["BuildConfig"], resolved.stack.template);
24960
+ return {
24961
+ resolved,
24962
+ code,
24963
+ spec: resolveWebFunctionRuntime(code.runtime),
24964
+ serviceConfig: readWebFunctionServiceConfig(resolved.revision)
24965
+ };
24966
+ }
24967
+ function webFunctionConnectHost(host) {
24968
+ if (host === "0.0.0.0") return "127.0.0.1";
24969
+ if (host === "::" || host === "[::]") return "::1";
24970
+ return host;
24971
+ }
24972
+ var WebFunctionServe = class {
24973
+ artifact;
24974
+ artifactInFlight;
24975
+ runInFlight;
24976
+ id;
24977
+ name;
24978
+ stopLogs;
24979
+ exitWatch;
24980
+ teardownPromise;
24981
+ waitingOnRunFlag = false;
24982
+ recent = new RecentOutput();
24983
+ prepared;
24984
+ options;
24985
+ constructor(prepared, options) {
24986
+ this.prepared = prepared;
24987
+ this.options = options;
24988
+ }
24989
+ get waitingOnRun() {
24990
+ return this.waitingOnRunFlag;
24991
+ }
24992
+ get containerName() {
24993
+ return this.name;
24994
+ }
24995
+ get tornDown() {
24996
+ return this.teardownPromise !== void 0;
24997
+ }
24998
+ get exited() {
24999
+ return this.exitWatch?.exited;
25000
+ }
25001
+ async start() {
25002
+ const logger = getLogger();
25003
+ const { resolved, code, spec, serviceConfig } = this.prepared;
25004
+ const opts = this.options;
25005
+ this.artifactInFlight = resolveWebFunctionArtifact(resolved.stack, resolved.revisionLogicalId, code);
25006
+ this.artifact = await this.artifactInFlight;
25007
+ if (this.tornDown) return void 0;
25008
+ const assumeRole = webFunctionAssumeRole(opts.envOptions.assumeRole, serviceConfig);
25009
+ const containerEnv = await resolveLambdaContainerEnv(buildWebFunctionLambdaView(resolved, serviceConfig, spec.runtime), {
25010
+ ...opts.envOptions,
25011
+ ...assumeRole !== void 0 && { assumeRole },
25012
+ deployedFunctionLookups: false
25013
+ }, opts.profileCredentials, opts.extraStateProviders);
25014
+ const { env, replacedHttpEndpoint } = finalizeWebFunctionEnv(containerEnv.env, {
25015
+ functionName: resolved.functionName,
25016
+ containerPort: WEB_FUNCTION_CONTAINER_PORT,
25017
+ timeoutSeconds: serviceConfig.timeoutSeconds,
25018
+ logGroup: serviceConfig.logGroup
25019
+ });
25020
+ if (replacedHttpEndpoint) logger.warn(`The revision's ${WEB_FUNCTION_HTTP_ENDPOINT_ENV} is replaced with 0.0.0.0:${WEB_FUNCTION_CONTAINER_PORT}: the runtime owns that variable, and the local port is published to it.`);
25021
+ const plan = planWebFunctionContainer(this.artifact, spec, env);
25022
+ const platform = opts.platform ?? defaultWebFunctionPlatform(spec);
25023
+ await pullImage(plan.image, opts.skipPull, platform);
25024
+ if (this.tornDown) return void 0;
25025
+ const hostPort = opts.port === 0 ? await pickFreePort() : opts.port;
25026
+ if (this.tornDown) return void 0;
25027
+ const name = this.name = `${getEmbedConfig().resourceNamePrefix}-webfn-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
25028
+ logger.info(`Starting web function container (image=${plan.image}, start=${plan.label}, port=${hostPort} -> ${WEB_FUNCTION_CONTAINER_PORT})...`);
25029
+ this.runInFlight = startWebFunctionContainer({
25030
+ plan,
25031
+ env,
25032
+ sensitiveEnvKeys: new Set(containerEnv.sensitiveEnvKeys),
25033
+ host: opts.host,
25034
+ hostPort,
25035
+ platform,
25036
+ name
25037
+ });
25038
+ const id = await this.runInFlight;
25039
+ if (this.tornDown) return void 0;
25040
+ this.id = id;
25041
+ this.stopLogs = streamLogs(id, { onOutput: (chunk) => this.recent.push(chunk) });
25042
+ this.exitWatch = watchContainerExit(id);
25043
+ const host = webFunctionConnectHost(opts.host);
25044
+ await waitForWebFunctionReady({
25045
+ containerId: id,
25046
+ host,
25047
+ port: hostPort,
25048
+ timeoutMs: opts.timeoutMs,
25049
+ recent: this.recent,
25050
+ label: plan.label
25051
+ });
25052
+ if (this.tornDown) return void 0;
25053
+ return {
25054
+ host,
25055
+ port: hostPort
25056
+ };
25057
+ }
25058
+ exitFailure(message) {
25059
+ return containerFailure(message, this.id ?? "", this.recent, void 0, "LOCAL_START_WEB_FUNCTION_EXITED");
25060
+ }
25061
+ disposeArtifactNow() {
25062
+ disposeWebFunctionArtifact(this.artifact);
25063
+ }
25064
+ teardown() {
25065
+ return this.teardownPromise ??= this.doTeardown();
25066
+ }
25067
+ async doTeardown() {
25068
+ const logger = getLogger();
25069
+ this.exitWatch?.stop();
25070
+ if (this.artifact === void 0 && this.artifactInFlight !== void 0) try {
25071
+ this.artifact = await this.artifactInFlight;
25072
+ } catch {}
25073
+ if (this.id === void 0 && this.runInFlight !== void 0) {
25074
+ this.waitingOnRunFlag = true;
25075
+ try {
25076
+ this.id = await this.runInFlight;
25077
+ } catch {} finally {
25078
+ this.waitingOnRunFlag = false;
25079
+ }
25080
+ }
25081
+ if (this.stopLogs) try {
25082
+ this.stopLogs();
25083
+ } catch (err) {
25084
+ logger.debug(`streamLogs stop failed: ${err instanceof Error ? err.message : String(err)}`);
25085
+ }
25086
+ if (this.id) await removeContainer(this.id);
25087
+ disposeWebFunctionArtifact(this.artifact);
25088
+ }
25089
+ };
25090
+
25091
+ //#endregion
25092
+ //#region src/cli/commands/local-start-cloudfront.ts
25093
+ var LocalStartCloudFrontError = class extends CdkLocalError {
25094
+ constructor(message, cause) {
25095
+ super(message, "LOCAL_START_CLOUDFRONT_ERROR", cause);
25096
+ }
25097
+ };
25098
+ function parseOriginOverrides(values) {
25099
+ const out = /* @__PURE__ */ new Map();
25100
+ for (const raw of values ?? []) {
25101
+ const eq = raw.indexOf("=");
25102
+ if (eq <= 0 || eq === raw.length - 1) throw new LocalStartCloudFrontError(`Invalid --origin '${raw}'. Expected <originId>=<dir> (e.g. MyOrigin=./site).`);
25103
+ out.set(raw.slice(0, eq).trim(), raw.slice(eq + 1).trim());
25104
+ }
25105
+ return out;
25106
+ }
25107
+ function parseKvsFileOverrides(values) {
25108
+ const out = /* @__PURE__ */ new Map();
25109
+ for (const raw of values ?? []) {
25110
+ const eq = raw.indexOf("=");
25111
+ if (eq <= 0 || eq === raw.length - 1) throw new LocalStartCloudFrontError(`Invalid --kvs-file '${raw}'. Expected <key>=<file.json> (e.g. RoutesKvs=./kvs.json).`);
25112
+ out.set(raw.slice(0, eq).trim(), raw.slice(eq + 1).trim());
25113
+ }
25114
+ return out;
25115
+ }
25116
+ function collectKvsStoreIdentities(template) {
25117
+ const out = [];
25118
+ for (const [logicalId, resource] of Object.entries(template.Resources ?? {})) {
25119
+ if (resource.Type !== "AWS::CloudFront::KeyValueStore") continue;
25120
+ const cdkPath = readCdkPath(resource) || void 0;
25121
+ const constructPath = cdkPath?.endsWith("/Resource") ? cdkPath.slice(0, -9) : cdkPath;
25122
+ const constructId = constructPath?.split("/").pop();
25123
+ out.push({
25124
+ logicalId,
25125
+ ...cdkPath !== void 0 && { cdkPath },
25126
+ ...constructPath !== void 0 && { constructPath },
25127
+ ...constructId !== void 0 && constructId !== "" && { constructId }
25128
+ });
25129
+ }
25130
+ return out;
25131
+ }
25132
+ function normalizeKvsFileKeys(kvsFiles, template) {
25133
+ if (kvsFiles.size === 0) return kvsFiles;
25134
+ const stores = collectKvsStoreIdentities(template);
25135
+ const candidates = () => stores.map((s) => s.constructPath ? `${displayIdent(s.logicalId, { listMember: true })} (${displayStackName(s.constructPath)})` : displayIdent(s.logicalId, { listMember: true })).join(", ") || "(none in this distribution)";
25136
+ const out = /* @__PURE__ */ new Map();
25137
+ for (const [key, filePath] of kvsFiles) {
25138
+ const matched = [
25139
+ stores.filter((s) => s.logicalId === key),
25140
+ stores.filter((s) => s.constructPath === key || s.cdkPath === key),
25141
+ stores.filter((s) => s.constructId === key)
25142
+ ].find((t) => t.length > 0) ?? [];
25143
+ if (matched.length === 0) throw new LocalStartCloudFrontError(`--kvs-file key '${key}' matched no AWS::CloudFront::KeyValueStore in the distribution. Pass a store's logical id, construct path, or bare construct id. Candidates: ${candidates()}.`);
25144
+ if (matched.length > 1) throw new LocalStartCloudFrontError(`--kvs-file key '${key}' is ambiguous — it matched ${matched.length} KeyValueStores (${matched.map((s) => displayIdent(s.logicalId, { listMember: true })).join(", ")}). Use the logical id or full construct path.`);
25145
+ out.set(matched[0].logicalId, filePath);
25146
+ }
25147
+ return out;
25148
+ }
25149
+ async function attachKvsModules(distribution, stacks, options, profileCredentials, logger, extraStateProviders) {
25150
+ const stack = stacks.find((s) => s.stackName === distribution.stackName);
25151
+ const synthRegion = stack?.region;
25152
+ const kvsFiles = stack ? normalizeKvsFileKeys(parseKvsFileOverrides(options.kvsFile), stack.template) : parseKvsFileOverrides(options.kvsFile);
25153
+ const resolveDeployedKvs = isStateSourceActive(options, extraStateProviders) ? async (kvsLogicalId) => {
25154
+ const provider = createLocalStateProvider(options, distribution.stackName, synthRegion, extraStateProviders);
25155
+ if (!provider) return void 0;
25156
+ let record;
25157
+ try {
25158
+ record = await provider.load(distribution.stackName, synthRegion);
25159
+ } finally {
25160
+ provider.dispose();
25161
+ }
25162
+ const physicalId = record?.resources[kvsLogicalId]?.physicalId;
25163
+ if (!physicalId) return void 0;
25164
+ if (physicalId.startsWith("arn:")) {
25165
+ const id = idFromArn(physicalId);
25166
+ return {
25167
+ arn: physicalId,
25168
+ ...id !== void 0 && { id }
25169
+ };
25170
+ }
25171
+ return resolveDeployedKvsArnByName(physicalId, { ...profileCredentials !== void 0 && { credentials: profileCredentials } });
25172
+ } : void 0;
25173
+ const { warnings } = await resolveKvsModulesForDistribution(distribution, {
25174
+ ...kvsFiles.size > 0 && { kvsFiles },
25175
+ ...resolveDeployedKvs !== void 0 && { resolveDeployedKvs },
25176
+ ...profileCredentials !== void 0 && { credentials: profileCredentials }
25177
+ });
25178
+ for (const warning of warnings) logger.warn(warning);
25179
+ }
25180
+ function resolveCloudFrontTarget(target, stacks) {
25181
+ if (stacks.length === 0) throw new LocalStartCloudFrontError("No stacks found in the synthesized assembly.");
25182
+ const parsed = parseEcsTarget(target);
25183
+ const stack = pickStack(parsed, stacks, target);
25184
+ const resources = stack.template.Resources ?? {};
25185
+ if (parsed.isPath) {
25186
+ const index = buildCdkPathIndex(stack.template);
25187
+ const dists = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId }) => {
25188
+ const r = resources[logicalId];
25189
+ return r !== void 0 && r.Type === "AWS::CloudFront::Distribution";
25190
+ });
25191
+ if (dists.length === 0) throw notFound(target, stack, resources);
25192
+ if (dists.length > 1) throw new LocalStartCloudFrontError(`Target '${target}' matches ${dists.length} distributions in ${stack.stackName}: ${dists.map((d) => d.logicalId).join(", ")}. Refine the path or use the stack:LogicalId form.`);
25193
+ return {
25194
+ stack,
25195
+ logicalId: dists[0].logicalId
25196
+ };
25197
+ }
25198
+ const res = resources[parsed.pathOrId];
25199
+ if (!res || res.Type !== "AWS::CloudFront::Distribution") throw notFound(target, stack, resources);
25200
+ return {
25201
+ stack,
25202
+ logicalId: parsed.pathOrId
25203
+ };
25204
+ }
25205
+ function pickStack(parsed, stacks, target) {
25206
+ const selection = selectTargetStack(parsed, target, stacks);
25207
+ switch (selection.kind) {
25208
+ case "found": return selection.stack;
25209
+ case "no-prefix": throw new LocalStartCloudFrontError(`Target '${target}' has no stack prefix, and the assembly contains ${stacks.length} stacks: ${describeStacks(stacks)}. Pass it as 'Stack/Path' or 'Stack:LogicalId'.`);
25210
+ case "not-found": throw new LocalStartCloudFrontError(`No stack matches '${selection.pattern}'. Available stacks: ${describeStacks(stacks)}.`);
25211
+ case "ambiguous": throw new LocalStartCloudFrontError(`Multiple stacks match '${selection.pattern}': ${describeStacks(selection.matched)}. Refine the pattern, or use the stack's display path (path form).`);
25212
+ }
25213
+ }
25214
+ function notFound(target, stack, resources) {
25215
+ const dists = Object.entries(resources).filter(([, r]) => r.Type === CLOUDFRONT_DISTRIBUTION_TYPE).map(([logicalId]) => logicalId);
25216
+ const available = dists.length > 0 ? ` Available distributions in ${stack.stackName}: ${dists.join(", ")}.` : ` ${stack.stackName} declares no ${CLOUDFRONT_DISTRIBUTION_TYPE} resources.`;
25217
+ return new LocalStartCloudFrontError(`Target '${target}' did not match a CloudFront distribution in ${stack.stackName}.${available}`);
25218
+ }
25219
+ async function bootLambdaUrlOrigins(distribution, stacks, opts) {
25220
+ const logger = getLogger();
25221
+ const invokers = /* @__PURE__ */ new Map();
25222
+ const runners = [];
25223
+ const functionLogicalIds = /* @__PURE__ */ new Set();
25224
+ for (const origin of distribution.origins.values()) if (origin.kind === "lambda-url") functionLogicalIds.add(origin.functionLogicalId);
25225
+ for (const functionLogicalId of functionLogicalIds) {
25226
+ let runner;
25227
+ try {
25228
+ const lambda = resolveLambdaTarget(`${distribution.stackName}:${functionLogicalId}`, stacks);
25229
+ const containerEnv = await resolveLambdaContainerEnv(lambda, opts.envOptions, opts.profileCredentials, opts.extraStateProviders);
25230
+ runner = createFrontDoorLambdaRunner(lambda, {
25231
+ containerHost: opts.containerHost,
25232
+ skipPull: opts.skipPull,
25233
+ ...opts.envOptions.region !== void 0 && { region: opts.envOptions.region },
25234
+ containerEnv: containerEnv.env,
25235
+ ...containerEnv.sensitiveEnvKeys.length > 0 && { sensitiveEnvKeys: new Set(containerEnv.sensitiveEnvKeys) }
25236
+ });
25237
+ logger.info(`Booting Lambda Function URL origin container for ${functionLogicalId} (the backing Lambda runs locally via RIE)...`);
25238
+ await runner.start();
25239
+ } catch (err) {
25240
+ await Promise.all(runners.map((r) => r.stop().catch(() => void 0)));
25241
+ throw new LocalStartCloudFrontError(`Failed to boot the Lambda Function URL origin's backing function ${displayIdent(functionLogicalId)}: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : void 0);
25242
+ }
25243
+ runners.push(runner);
25244
+ invokers.set(functionLogicalId, (event) => runner.invoke(event));
25245
+ }
25246
+ return {
25247
+ invokers,
25248
+ runners
25249
+ };
25250
+ }
25251
+ async function drainWebFunctionServes(serves) {
25252
+ let done = 0;
25253
+ while (done < serves.length) {
25254
+ const batch = serves.slice(done);
25255
+ done = serves.length;
25256
+ await Promise.all(batch.map((s) => s.teardown().catch(() => void 0)));
25257
+ }
25258
+ }
25259
+ const WEB_FUNCTION_ORIGIN_READY_TIMEOUT_MS = 6e4;
25260
+ async function bootWebFunctionOrigins(distribution, stacks, opts) {
25261
+ const logger = getLogger();
25262
+ const targets = /* @__PURE__ */ new Map();
25263
+ const serves = opts.sink ?? [];
25264
+ const endpointLogicalIds = /* @__PURE__ */ new Set();
25265
+ for (const origin of distribution.origins.values()) if (origin.kind === "web-function") endpointLogicalIds.add(origin.endpointLogicalId);
25266
+ if (endpointLogicalIds.size === 0) return {
25267
+ targets,
25268
+ serves
25269
+ };
25270
+ for (const endpointLogicalId of endpointLogicalIds) try {
25271
+ if (serves.length === 0) await ensureDockerAvailable();
25272
+ const resolved = resolveWebFunctionTarget(`${distribution.stackName}:${endpointLogicalId}`, stacks);
25273
+ const prepared = prepareWebFunctionServe(resolved);
25274
+ for (const line of webFunctionFidelityWarnings(resolved, prepared.serviceConfig)) logger.warn(line);
25275
+ logger.info(`Booting Lambda Web Function endpoint origin server for ${displayIdent(endpointLogicalId)} (revision ${displayIdent(resolved.revisionLogicalId)}, ${prepared.spec.runtime})...`);
25276
+ const serve = new WebFunctionServe(prepared, {
25277
+ envOptions: opts.envOptions,
25278
+ ...opts.profileCredentials !== void 0 && { profileCredentials: opts.profileCredentials },
25279
+ ...opts.extraStateProviders !== void 0 && { extraStateProviders: opts.extraStateProviders },
25280
+ host: opts.containerHost,
25281
+ port: 0,
25282
+ skipPull: opts.skipPull,
25283
+ timeoutMs: opts.timeoutMs ?? WEB_FUNCTION_ORIGIN_READY_TIMEOUT_MS
25284
+ });
25285
+ serves.push(serve);
25286
+ const address = await serve.start();
25287
+ if (!address) throw new Error("the server was stopped while starting");
25288
+ targets.set(endpointLogicalId, address);
25289
+ serve.exited?.then((code) => {
25290
+ if (serve.tornDown) return;
25291
+ logger.warn(`The Lambda Web Function endpoint origin server for ${displayIdent(endpointLogicalId)} exited (exit code ${code ?? "unknown"}); requests routed to it return 502. Restart start-cloudfront.`);
25292
+ });
25293
+ } catch (err) {
25294
+ await Promise.all(serves.map((s) => s.teardown().catch(() => void 0)));
25295
+ throw new LocalStartCloudFrontError(`Failed to boot the Lambda Web Function endpoint origin ${displayIdent(endpointLogicalId)}: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : void 0);
25296
+ }
25297
+ return {
25298
+ targets,
25299
+ serves
25300
+ };
25301
+ }
25302
+ async function bootLambdaEdgeFunctions(distribution, stacks, opts) {
25303
+ const logger = getLogger();
25304
+ const invokers = /* @__PURE__ */ new Map();
25305
+ const runners = [];
25306
+ const functionLogicalIds = /* @__PURE__ */ new Set();
25307
+ for (const behavior of distribution.behaviors) {
25308
+ const edge = behavior.lambdaEdge;
25309
+ if (!edge) continue;
25310
+ for (const assoc of [
25311
+ edge.viewerRequest,
25312
+ edge.originRequest,
25313
+ edge.originResponse,
25314
+ edge.viewerResponse
25315
+ ]) if (assoc) functionLogicalIds.add(assoc.functionLogicalId);
25316
+ }
25317
+ for (const functionLogicalId of functionLogicalIds) try {
25318
+ const lambda = resolveLambdaTarget(`${distribution.stackName}:${functionLogicalId}`, stacks);
25319
+ const containerEnv = await resolveLambdaContainerEnv(lambda, opts.envOptions, opts.profileCredentials, opts.extraStateProviders);
25320
+ const runner = createFrontDoorLambdaRunner(lambda, {
25321
+ containerHost: opts.containerHost,
25322
+ skipPull: opts.skipPull,
25323
+ ...opts.envOptions.region !== void 0 && { region: opts.envOptions.region },
25324
+ containerEnv: containerEnv.env,
25325
+ ...containerEnv.sensitiveEnvKeys.length > 0 && { sensitiveEnvKeys: new Set(containerEnv.sensitiveEnvKeys) }
25326
+ });
25327
+ logger.info(`Booting Lambda@Edge container for ${functionLogicalId} (the function runs locally via RIE)...`);
25328
+ await runner.start();
25329
+ runners.push(runner);
25330
+ invokers.set(functionLogicalId, (event) => runner.invoke(event));
25331
+ } catch (err) {
25332
+ logger.warn(`Could not boot Lambda@Edge function ${displayIdent(functionLogicalId)}; its stage will be skipped: ${err instanceof Error ? err.message : String(err)}`);
25333
+ }
25334
+ return {
25335
+ invokers,
25336
+ runners
25337
+ };
25338
+ }
25339
+ function buildLambdaEnvOptions(options, extraStateProviders) {
25340
+ return {
25341
+ ...hostStateSourceFields(options, extraStateProviders),
25342
+ ...options.fromCfnStack !== void 0 && { fromCfnStack: options.fromCfnStack },
25343
+ ...options.assumeRole !== void 0 && { assumeRole: options.assumeRole },
25344
+ ...options.region !== void 0 && { region: options.region },
25345
+ ...options.profile !== void 0 && { profile: options.profile },
25346
+ ...options.stackRegion !== void 0 && { stackRegion: options.stackRegion }
25347
+ };
25348
+ }
25349
+ function buildLambdaBootOptions(options, profileCredentials, extraStateProviders) {
25350
+ return {
25351
+ containerHost: options.host,
25352
+ skipPull: options.pull === false,
25353
+ envOptions: buildLambdaEnvOptions(options, extraStateProviders),
25354
+ ...profileCredentials !== void 0 && { profileCredentials },
25355
+ ...extraStateProviders !== void 0 && { extraStateProviders }
25356
+ };
25357
+ }
25358
+ function warnUnsupported(distribution, extraStateProviders) {
25359
+ const logger = getLogger();
25360
+ for (const origin of distribution.origins.values()) if (origin.kind === "custom") logger.warn(`Origin ${displayIdent(origin.originId)} is a custom (non-S3, non-Lambda) origin (${displayUntrustedValue(String(origin.domainName))}); ${getEmbedConfig().cliName} start-cloudfront serves S3, Lambda Function URL and Lambda Web Function endpoint origins only. Requests routed to it return 502.`);
25361
+ else if (origin.kind === "s3-unresolved") logger.warn(`Origin ${displayIdent(origin.originId)} is an S3 origin with no resolvable local source (no BucketDeployment found, or its source could not be located in the cloud assembly). Pass ${stateSourceFlagNames(extraStateProviders).join(" or ")} to serve the deployed bucket from real S3 on demand, or point it at a local directory with --origin ${origin.originId}=<dir>. Requests routed to it return 502.`);
25362
+ }
25363
+ function warnUnusedCacheOrigin(options, extraStateProviders) {
25364
+ if (options.cacheOrigin === true && !isStateSourceActive(options, extraStateProviders)) {
25365
+ const flags = stateSourceFlagNames(extraStateProviders).join(" or ");
25366
+ getLogger().warn(`--cache-origin has no effect without ${flags}: it caches a deployed-S3 read-through origin, which is only built under a state source. A local BucketDeployment / --origin <id>=<dir> origin serves from disk and is not cached. Pass ${flags} to enable the deployed-S3 origin (then --cache-origin applies), or drop --cache-origin.`);
25367
+ }
25368
+ }
25369
+ async function resolveDeployedS3Origins(distribution, stacks, options, profileCredentials, logger, extraStateProviders) {
25370
+ const readers = /* @__PURE__ */ new Map();
25371
+ const buckets = /* @__PURE__ */ new Map();
25372
+ const stateFlag = activeStateSourceFlag(options, extraStateProviders);
25373
+ if (stateFlag === void 0) return {
25374
+ readers,
25375
+ buckets
25376
+ };
25377
+ const synthRegion = stacks.find((s) => s.stackName === distribution.stackName)?.region;
25378
+ const provider = createLocalStateProvider(options, distribution.stackName, synthRegion, extraStateProviders);
25379
+ let record;
25380
+ try {
25381
+ record = provider ? await provider.load(distribution.stackName, synthRegion) : void 0;
25382
+ } finally {
25383
+ provider?.dispose();
25384
+ }
25385
+ const region = options.stackRegion ?? options.region ?? synthRegion ?? record?.region;
25386
+ const distributionId = record?.resources[distribution.logicalId]?.physicalId;
25387
+ for (const origin of [...distribution.origins.values()]) {
25388
+ if (origin.kind !== "s3-unresolved") continue;
24405
25389
  let bucketName;
24406
25390
  let via;
24407
25391
  if (origin.bucketLogicalId !== void 0) {
@@ -24497,10 +25481,32 @@ async function localStartCloudFrontCommand(target, options, extraStateProviders,
24497
25481
  warnUnusedCacheOrigin(options, extraStateProviders);
24498
25482
  await attachKvsModules(initial.distribution, initial.stacks, options, profileCredentials, logger, extraStateProviders);
24499
25483
  const bootOpts = buildLambdaBootOptions(options, profileCredentials, extraStateProviders);
25484
+ const webFunctionServes = [];
25485
+ let bootAborted = false;
25486
+ const onBootSignal = (signal) => {
25487
+ if (bootAborted) return;
25488
+ bootAborted = true;
25489
+ logger.info(`Received ${signal} during start-up; removing the web function containers...`);
25490
+ drainWebFunctionServes(webFunctionServes).then(() => process.exit(signal === "SIGINT" ? 130 : 0));
25491
+ };
25492
+ const bootSigint = () => onBootSignal("SIGINT");
25493
+ const bootSigterm = () => onBootSignal("SIGTERM");
25494
+ const disarmBootSignals = () => {
25495
+ process.off("SIGINT", bootSigint);
25496
+ process.off("SIGTERM", bootSigterm);
25497
+ };
25498
+ if ([...initial.distribution.origins.values()].some((o) => o.kind === "web-function")) {
25499
+ process.on("SIGINT", bootSigint);
25500
+ process.on("SIGTERM", bootSigterm);
25501
+ }
24500
25502
  const { invokers: lambdaInvokers, runners: lambdaRunners } = await bootLambdaUrlOrigins(initial.distribution, initial.stacks, bootOpts);
24501
25503
  const { invokers: edgeInvokers, runners: edgeRunners } = await bootLambdaEdgeFunctions(initial.distribution, initial.stacks, bootOpts);
24502
25504
  let server;
24503
25505
  try {
25506
+ const webFunctions = await bootWebFunctionOrigins(initial.distribution, initial.stacks, {
25507
+ ...bootOpts,
25508
+ sink: webFunctionServes
25509
+ });
24504
25510
  let tls;
24505
25511
  if (tlsRequested) tls = await resolveFrontDoorTlsMaterials({
24506
25512
  certPath: options.tlsCert,
@@ -24513,12 +25519,17 @@ async function localStartCloudFrontCommand(target, options, extraStateProviders,
24513
25519
  ...tls && { tls },
24514
25520
  ...lambdaInvokers.size > 0 && { lambdaInvokers },
24515
25521
  ...edgeInvokers.size > 0 && { edgeInvokers },
24516
- ...deployedS3.readers.size > 0 && { s3OriginReaders: deployedS3.readers }
25522
+ ...deployedS3.readers.size > 0 && { s3OriginReaders: deployedS3.readers },
25523
+ ...webFunctions.targets.size > 0 && { webFunctionOrigins: webFunctions.targets }
24517
25524
  });
24518
25525
  } catch (err) {
24519
- await Promise.all([...lambdaRunners, ...edgeRunners].map((r) => r.stop().catch(() => void 0)));
25526
+ await Promise.all([...[...lambdaRunners, ...edgeRunners].map((r) => r.stop().catch(() => void 0)), ...webFunctionServes.map((s) => s.teardown().catch(() => void 0))]);
25527
+ if (bootAborted) return await new Promise(() => void 0);
25528
+ disarmBootSignals();
24520
25529
  throw err;
24521
25530
  }
25531
+ disarmBootSignals();
25532
+ if (bootAborted) return await new Promise(() => void 0);
24522
25533
  process.stdout.write(`CloudFront distribution serving on ${server.url} (${displayIdent(initial.distribution.logicalId)})\n`);
24523
25534
  process.stdout.write("^C to stop.\n");
24524
25535
  let watcher;
@@ -24564,9 +25575,11 @@ async function localStartCloudFrontCommand(target, options, extraStateProviders,
24564
25575
  try {
24565
25576
  await server.close();
24566
25577
  } catch {}
24567
- await Promise.all([...lambdaRunners, ...edgeRunners].map((r) => r.stop().catch((err) => {
25578
+ await Promise.all([...[...lambdaRunners, ...edgeRunners].map((r) => r.stop().catch((err) => {
24568
25579
  logger.debug(`Lambda runner stop failed: ${err instanceof Error ? err.message : String(err)}`);
24569
- })));
25580
+ })), ...webFunctionServes.map((s) => s.teardown().catch((err) => {
25581
+ logger.debug(`Web function server teardown failed: ${err instanceof Error ? err.message : String(err)}`);
25582
+ }))]);
24570
25583
  await Promise.all([...deployedS3.readers.values()].map((reader) => reader.close()));
24571
25584
  process.exit(exitCode);
24572
25585
  };
@@ -24576,7 +25589,7 @@ async function localStartCloudFrontCommand(target, options, extraStateProviders,
24576
25589
  }
24577
25590
  function createLocalStartCloudFrontCommand(opts = {}) {
24578
25591
  setEmbedConfig(opts.embedConfig);
24579
- const cmd = new Command("start-cloudfront").description("Run a long-running local server that serves a CloudFront distribution: its S3 origin content (resolved from the BucketDeployment source in the cloud assembly) and its Lambda Function URL origins (the backing Lambda is run locally via RIE), plus its viewer-request / viewer-response CloudFront Functions, reproducing the distribution routing locally so a rewrite / routing change is verifiable in seconds. Serves S3 and Lambda Function URL origins, and runs Lambda@Edge functions in local RIE containers. Both Lambda kinds need Docker: a Function URL origin that fails to boot stops start-up, while a Lambda@Edge function that fails to boot is warn-and-skipped (its stage does not run). Other custom origins are not run (warn-and-skip; requests routed to one return 502). Tip: omit the target in a terminal to pick interactively.").argument("[target]", "CloudFront distribution to serve. Accepts the CDK Construct path ('MyStack/MyDist'), an ancestor prefix, or the stack-qualified logical id ('MyStack:MyDist'). When omitted in a TTY, an interactive picker opens.").action(withErrorHandling(async (target, options) => {
25592
+ const cmd = new Command("start-cloudfront").description("Run a long-running local server that serves a CloudFront distribution: its S3 origin content (resolved from the BucketDeployment source in the cloud assembly), its Lambda Function URL origins (the backing Lambda is run locally via RIE) and its Lambda Web Function endpoint origins (the revision the endpoint weights pick, run as start-web-function runs it), plus its viewer-request / viewer-response CloudFront Functions, reproducing the distribution routing locally so a rewrite / routing change is verifiable in seconds. Serves S3, Lambda Function URL and Web Function endpoint origins, and runs Lambda@Edge functions in local RIE containers. The Lambda kinds need Docker: a Function URL origin that fails to boot stops start-up (as does a Web Function origin), while a Lambda@Edge function that fails to boot is warn-and-skipped (its stage does not run). Other custom origins are not run (warn-and-skip; requests routed to one return 502). Tip: omit the target in a terminal to pick interactively.").argument("[target]", "CloudFront distribution to serve. Accepts the CDK Construct path ('MyStack/MyDist'), an ancestor prefix, or the stack-qualified logical id ('MyStack:MyDist'). When omitted in a TTY, an interactive picker opens.").action(withErrorHandling(async (target, options) => {
24580
25593
  await localStartCloudFrontCommand(target, options, opts.extraStateProviders, opts.synthesizer);
24581
25594
  }));
24582
25595
  addStartCloudFrontSpecificOptions(cmd);
@@ -24589,7 +25602,7 @@ function createLocalStartCloudFrontCommand(opts = {}) {
24589
25602
  return cmd;
24590
25603
  }
24591
25604
  function addStartCloudFrontSpecificOptions(cmd) {
24592
- return cmd.addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--origin <originId=dir>", "Point a distribution origin at a local directory (repeatable). Use when cdk-local cannot resolve the origin's BucketDeployment source automatically (content uploaded out of band, or a non-CDK bucket).").argParser((value, prev) => [...prev ?? [], value])).addOption(new Option("--kvs-file <key=file.json>", "Back a CloudFront Function's KeyValueStore reads (cf.kvs().get()) with a local JSON map (repeatable). The key is a KeyValueStore handle — its AWS::CloudFront::KeyValueStore resource logical id, its construct path, or its bare construct id; the file is a flat { \"key\": \"value\" } object. The AWS-free alternative to --from-cfn-stack, which instead reads the deployed store via the GetKey API.").argParser((value, prev) => [...prev ?? [], value])).addOption(new Option("--tls", "Terminate real TLS (HTTPS). Uses --tls-cert / --tls-key when supplied, else an auto-generated self-signed cert.").default(false)).addOption(new Option("--tls-cert <path>", "PEM server certificate for --tls (implies --tls).")).addOption(new Option("--tls-key <path>", "PEM server private key for --tls (implies --tls).")).addOption(new Option("--no-pull", "Skip 'docker pull' for a Lambda Function URL origin's base image (use the locally cached image).")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Bind to a deployed CloudFormation stack (ListStackResources). Resolves an S3 origin that has no local BucketDeployment source to its deployed bucket and serves it from real S3 on demand (the front/back-split case: files uploaded out of band), and resolves a Lambda Function URL / Lambda@Edge function's env vars to the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the resolved stack name; pass a value when the CFn stack name differs.")).addOption(new Option("--cache-origin", "For a deployed-S3 origin (served from real S3 under --from-cfn-stack): keep fetched objects in memory for the session instead of re-reading on every request. Faster repeat reads / fewer S3 GETs, but an out-of-band S3 content change is not reflected until a --watch reload (which clears the cache) or a restart. Off by default (every request re-reads, always current). Not CloudFront CDN caching.").default(false)).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).addOption(new Option("--assume-role [arn]", "Assume a Lambda Function URL origin's deployed execution role and forward STS-issued temp credentials into its container so the handler runs with the deployed permissions. Three forms: `--assume-role <arn>` (explicit ARN); `--assume-role` (bare, auto-resolves from state — requires --from-cfn-stack); `--no-assume-role` (opt out). Off by default (the dev shell credentials are forwarded).")).addOption(new Option("--watch", "Hot-reload: re-synth + re-resolve the distribution when the CDK app's source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). The server keeps the previous version serving when synth fails mid-reload.").default(false));
25605
+ return cmd.addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--origin <originId=dir>", "Point a distribution origin at a local directory (repeatable). Use when cdk-local cannot resolve the origin's BucketDeployment source automatically (content uploaded out of band, or a non-CDK bucket).").argParser((value, prev) => [...prev ?? [], value])).addOption(new Option("--kvs-file <key=file.json>", "Back a CloudFront Function's KeyValueStore reads (cf.kvs().get()) with a local JSON map (repeatable). The key is a KeyValueStore handle — its AWS::CloudFront::KeyValueStore resource logical id, its construct path, or its bare construct id; the file is a flat { \"key\": \"value\" } object. The AWS-free alternative to --from-cfn-stack, which instead reads the deployed store via the GetKey API.").argParser((value, prev) => [...prev ?? [], value])).addOption(new Option("--tls", "Terminate real TLS (HTTPS). Uses --tls-cert / --tls-key when supplied, else an auto-generated self-signed cert.").default(false)).addOption(new Option("--tls-cert <path>", "PEM server certificate for --tls (implies --tls).")).addOption(new Option("--tls-key <path>", "PEM server private key for --tls (implies --tls).")).addOption(new Option("--no-pull", "Skip 'docker pull' for a Lambda Function URL / Web Function origin's base image (use the locally cached image).")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Bind to a deployed CloudFormation stack (ListStackResources). Resolves an S3 origin that has no local BucketDeployment source to its deployed bucket and serves it from real S3 on demand (the front/back-split case: files uploaded out of band), and resolves a Lambda Function URL / Lambda@Edge function's or a Web Function origin revision's env vars to the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the resolved stack name; pass a value when the CFn stack name differs.")).addOption(new Option("--cache-origin", "For a deployed-S3 origin (served from real S3 under --from-cfn-stack): keep fetched objects in memory for the session instead of re-reading on every request. Faster repeat reads / fewer S3 GETs, but an out-of-band S3 content change is not reflected until a --watch reload (which clears the cache) or a restart. Off by default (every request re-reads, always current). Not CloudFront CDN caching.").default(false)).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).addOption(new Option("--assume-role [arn]", "Assume a Lambda Function URL origin's (or a Web Function origin revision's) execution role and forward STS-issued temp credentials into its container so the handler runs with the deployed permissions. Three forms: `--assume-role <arn>` (explicit ARN); `--assume-role` (bare, auto-resolves from state — requires --from-cfn-stack); `--no-assume-role` (opt out). Off by default (the dev shell credentials are forwarded).")).addOption(new Option("--watch", "Hot-reload: re-synth + re-resolve the distribution when the CDK app's source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). The server keeps the previous version serving when synth fails mid-reload.").default(false));
24593
25606
  }
24594
25607
 
24595
25608
  //#endregion
@@ -24892,7 +25905,7 @@ function readAuthorizationHeader(headers) {
24892
25905
 
24893
25906
  //#endregion
24894
25907
  //#region src/cli/commands/local-start-agentcore.ts
24895
- function parsePort$1(raw) {
25908
+ function parsePort$2(raw) {
24896
25909
  const n = Number(raw);
24897
25910
  if (!Number.isInteger(n) || n < 0 || n > 65535) throw new InvalidArgumentError(`--port must be an integer 0-65535, got '${raw}'.`);
24898
25911
  return n;
@@ -25219,629 +26232,1126 @@ function createLocalStartAgentCoreCommand(opts = {}) {
25219
26232
  cmd.addOption(regionOption);
25220
26233
  return cmd;
25221
26234
  }
25222
- function addStartAgentCoreSpecificOptions(cmd) {
25223
- return cmd.addOption(new Option("--port <n>", "Serve bind port the client connects to (HTTP contract + /ws on the same port). Default 0 (OS-assigned).").default(0).argParser(parsePort$1)).addOption(new Option("--host <ip>", "Serve bind host. Default 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--session-id <id>", "Pin one AgentCore runtime session id header value for every forwarded request / /ws connection (default: a fresh random UUID each, so each is its own session).")).addOption(new Option("--bearer-token <jwt>", "Bearer JWT to present when the runtime declares a customJwtAuthorizer. Verified against the runtime's OIDC discovery URL before the container starts, then injected as Authorization: Bearer <jwt> on every forwarded request and the container /ws upgrade.")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign each forwarded request with AWS SigV4 (service bedrock-agentcore) when the runtime declares NO customJwtAuthorizer, so the warm container sees the same Authorization / X-Amz-* headers the cloud receives. Mutually exclusive with --bearer-token; ignored (with a warning) when a customJwtAuthorizer is declared.")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64). Defaults to linux/arm64 because the cloud AgentCore Runtime requires arm64.").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--watch", "Re-synth + reload the warm container in place on a CDK source change, keeping the serve up. A per-firing classifier picks rebuild (boot a fresh container, re-point the serve) vs soft-reload (docker cp + docker restart the same container) — the same pattern as start-service / invoke-agentcore --ws --watch.")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host IP used to bind the agent container port. Must be a numeric IP. Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Maximum time in milliseconds to wait for the container to become ready (the GET /ping readiness deadline). Default 120000.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container. (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's literal RoleArn; (3) `--no-assume-role` opts out. Off by default.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack and substitute Ref / Fn::ImportValue in env vars / image URIs with the deployed physical IDs / exports. Bare form uses the resolved stack name.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
25224
- }
25225
-
25226
- //#endregion
25227
- //#region src/local/web-function-resolver.ts
25228
- const WEB_FUNCTION_TYPE = "AWS::Lambda::WebFunction";
25229
- const WEB_FUNCTION_REVISION_TYPE = "AWS::Lambda::WebFunctionRevision";
25230
- const WEB_FUNCTION_ENDPOINT_TYPE = "AWS::Lambda::WebFunctionEndpoint";
25231
- const WEB_TYPES = [
25232
- WEB_FUNCTION_ENDPOINT_TYPE,
25233
- WEB_FUNCTION_TYPE,
25234
- WEB_FUNCTION_REVISION_TYPE
25235
- ];
25236
- var WebFunctionResolutionError = class WebFunctionResolutionError extends Error {
25237
- constructor(message) {
25238
- super(message);
25239
- this.name = "WebFunctionResolutionError";
25240
- Object.setPrototypeOf(this, WebFunctionResolutionError.prototype);
25241
- }
25242
- };
25243
- function resolveWebFunctionTarget(target, stacks, revisionFlag) {
25244
- if (stacks.length === 0) throw new WebFunctionResolutionError("No stacks found in the synthesized assembly.");
25245
- const parsed = parseTarget(target);
25246
- const stack = pickStack(parsed, stacks, target);
25247
- const resources = stack.template.Resources ?? {};
25248
- const { logicalId, resource } = matchWebResource(parsed, target, stack, resources);
25249
- const functions = logicalIdsOfType(resources, WEB_FUNCTION_TYPE);
25250
- const linked = (value) => linkedFunctionLogicalId(value, resources, functions);
25251
- let functionLogicalId;
25252
- let endpoint;
25253
- let revisionLogicalId;
25254
- let selection;
25255
- if (resource.Type === "AWS::Lambda::WebFunctionRevision") {
25256
- functionLogicalId = linked(resource.Properties?.["FunctionName"]);
25257
- revisionLogicalId = logicalId;
25258
- selection = "target-revision";
25259
- } else if (resource.Type === "AWS::Lambda::WebFunctionEndpoint") {
25260
- functionLogicalId = linked(resource.Properties?.["FunctionName"]);
25261
- endpoint = readEndpoint(logicalId, resource, resources);
25262
- } else functionLogicalId = logicalId;
25263
- const revisionsOfFunction = () => functionLogicalId === void 0 ? [] : logicalIdsOfType(resources, WEB_FUNCTION_REVISION_TYPE).filter((id) => linked(resources[id]?.Properties?.["FunctionName"]) === functionLogicalId);
25264
- const endpointsOfFunction = () => functionLogicalId === void 0 ? [] : logicalIdsOfType(resources, WEB_FUNCTION_ENDPOINT_TYPE).filter((id) => linked(resources[id]?.Properties?.["FunctionName"]) === functionLogicalId);
25265
- if (revisionFlag !== void 0) {
25266
- const flagged = resources[revisionFlag];
25267
- if (!flagged || flagged.Type !== "AWS::Lambda::WebFunctionRevision") throw new WebFunctionResolutionError(`--revision ${displayIdent(revisionFlag)} is not an ${WEB_FUNCTION_REVISION_TYPE} logical id in ${displayStackName(stack.stackName)}. ${describeRevisions(revisionsOfFunction(), stack)}`);
25268
- const owner = linked(flagged.Properties?.["FunctionName"]);
25269
- if (functionLogicalId !== void 0 && owner !== functionLogicalId) throw new WebFunctionResolutionError(`--revision ${displayIdent(revisionFlag)} belongs to a different web function than ${displayIdent(logicalId)}. ${describeRevisions(revisionsOfFunction(), stack)}`);
25270
- functionLogicalId ??= owner;
25271
- revisionLogicalId = revisionFlag;
25272
- selection = "revision-flag";
25273
- }
25274
- if (revisionLogicalId === void 0 && endpoint !== void 0) {
25275
- revisionLogicalId = pickHighestWeight(endpoint);
25276
- if (revisionLogicalId !== void 0) selection = "endpoint-weight";
25277
- }
25278
- if (revisionLogicalId === void 0) {
25279
- const revisions = revisionsOfFunction();
25280
- if (revisions.length === 1) {
25281
- revisionLogicalId = revisions[0];
25282
- selection = "only-revision";
25283
- } else if (endpoint === void 0 && revisions.length > 1) {
25284
- const endpoints = endpointsOfFunction();
25285
- if (endpoints.length === 1) {
25286
- endpoint = readEndpoint(endpoints[0], resources[endpoints[0]], resources);
25287
- revisionLogicalId = pickHighestWeight(endpoint);
25288
- if (revisionLogicalId !== void 0) selection = "endpoint-weight";
25289
- }
25290
- }
25291
- if (revisionLogicalId === void 0) throw new WebFunctionResolutionError(revisions.length === 0 ? `${displayIdent(logicalId)} in ${displayStackName(stack.stackName)} resolves to no ${WEB_FUNCTION_REVISION_TYPE} in this template, so there is no code to serve. Target the revision itself, or pass --revision <logicalId>.` : `${displayIdent(logicalId)} has ${revisions.length} revisions and no single endpoint that picks one. Pass --revision <logicalId>, or target one of the function's endpoints. ${describeRevisions(revisions, stack)}`);
25292
- }
25293
- if (endpoint === void 0) {
25294
- const routing = endpointsOfFunction().map((id) => readEndpoint(id, resources[id], resources)).filter((e) => e.weights.some((w) => w.revisionLogicalId === revisionLogicalId));
25295
- if (routing.length === 1) endpoint = routing[0];
25296
- }
25297
- const fnName = (functionLogicalId !== void 0 ? resources[functionLogicalId] : void 0)?.Properties?.["FunctionName"];
25298
- return {
25299
- stack,
25300
- functionLogicalId,
25301
- functionName: typeof fnName === "string" && fnName.length > 0 ? fnName : functionLogicalId ?? revisionLogicalId,
25302
- revisionLogicalId,
25303
- revision: resources[revisionLogicalId],
25304
- endpoint,
25305
- selection: selection ?? "only-revision"
25306
- };
25307
- }
25308
- function pickStack(parsed, stacks, target) {
25309
- const selection = selectTargetStack(parsed, target, stacks);
25310
- switch (selection.kind) {
25311
- case "found": return selection.stack;
25312
- case "no-prefix": throw new WebFunctionResolutionError(`Multiple stacks in app, target ${displayIdent(parsed.pathOrId)} is missing a stack prefix. Use StackName:<logicalId> or StackName/... (path form). Available stacks: ${describeStacks(stacks)}.`);
25313
- case "not-found": throw new WebFunctionResolutionError(`Stack ${displayStackName(selection.pattern)} not found. Available stacks: ${describeStacks(stacks)}.`);
25314
- case "ambiguous": throw new WebFunctionResolutionError(`Stack pattern ${displayStackName(selection.pattern)} matched ${selection.matched.length} stacks: ${describeStacks(selection.matched)}. Use a more specific pattern, or the stack's display path.`);
25315
- }
25316
- }
25317
- function matchWebResource(parsed, target, stack, resources) {
25318
- if (parsed.isPath) {
25319
- const matches = resolveCdkPathToLogicalIds(parsed.pathOrId, buildCdkPathIndex(stack.template)).filter(({ logicalId }) => WEB_TYPES.includes(resources[logicalId]?.Type ?? ""));
25320
- for (const type of WEB_TYPES) {
25321
- const ofType = matches.filter(({ logicalId }) => resources[logicalId].Type === type);
25322
- if (ofType.length === 1) {
25323
- const id = ofType[0].logicalId;
25324
- return {
25325
- logicalId: id,
25326
- resource: resources[id]
25327
- };
25328
- }
25329
- if (ofType.length > 1) throw new WebFunctionResolutionError(`Target ${displayStackName(target)} matches ${ofType.length} ${type} resources: ${ofType.map((m) => displayIdent(m.logicalId)).join(", ")}. Refine the path or use the StackName:<logicalId> form.`);
25330
- }
25331
- throw notFoundError(target, stack, resources);
25332
- }
25333
- const resource = resources[parsed.pathOrId];
25334
- if (!resource) throw notFoundError(target, stack, resources);
25335
- if (!WEB_TYPES.includes(resource.Type)) throw new WebFunctionResolutionError(`Resource ${displayIdent(parsed.pathOrId)} in ${displayStackName(stack.stackName)} is ${displayIdent(resource.Type)}, not a Lambda web function. ${getEmbedConfig().cliName} start-web-function serves ${WEB_TYPES.join(" / ")} targets.`);
25336
- return {
25337
- logicalId: parsed.pathOrId,
25338
- resource
25339
- };
25340
- }
25341
- function notFoundError(target, stack, resources) {
25342
- const available = WEB_TYPES.flatMap((t) => logicalIdsOfType(resources, t));
25343
- const hint = available.length > 0 ? `Web function resources in ${displayStackName(stack.stackName)}: ${available.map((id) => displayIdent(id)).join(", ")}.` : `No ${WEB_FUNCTION_TYPE} resources found in ${displayStackName(stack.stackName)}.`;
25344
- return new WebFunctionResolutionError(`Target ${displayStackName(target)} not found. ${hint}`);
25345
- }
25346
- function logicalIdsOfType(resources, type) {
25347
- return Object.keys(resources).filter((id) => resources[id]?.Type === type);
25348
- }
25349
- function linkedFunctionLogicalId(value, resources, functions) {
25350
- const ref = referencedLogicalId(value);
25351
- if (ref !== void 0) return functions.includes(ref) ? ref : void 0;
25352
- if (typeof value !== "string") return void 0;
25353
- const byName = functions.filter((id) => resources[id]?.Properties?.["FunctionName"] === value);
25354
- return byName.length === 1 ? byName[0] : void 0;
25355
- }
25356
- function referencedLogicalId(value) {
25357
- if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
25358
- const obj = value;
25359
- if (typeof obj["Ref"] === "string") return obj["Ref"];
25360
- const getAtt = obj["Fn::GetAtt"];
25361
- if (Array.isArray(getAtt) && typeof getAtt[0] === "string") return getAtt[0];
25362
- if (typeof getAtt === "string") return getAtt.split(".")[0];
25363
- }
25364
- function readEndpoint(logicalId, resource, resources) {
25365
- const props = resource.Properties ?? {};
25366
- const rawWeights = props["RevisionWeights"];
25367
- const weights = [];
25368
- if (Array.isArray(rawWeights)) for (const entry of rawWeights) {
25369
- if (!entry || typeof entry !== "object") continue;
25370
- const e = entry;
25371
- const ref = referencedLogicalId(e["RevisionId"]);
25372
- weights.push({
25373
- revisionLogicalId: ref !== void 0 && resources[ref]?.Type === "AWS::Lambda::WebFunctionRevision" ? ref : void 0,
25374
- weight: typeof e["Weight"] === "number" ? e["Weight"] : 0
25375
- });
25376
- }
25377
- const str = (v) => typeof v === "string" ? v : void 0;
25378
- return {
25379
- logicalId,
25380
- endpointType: str(props["EndpointType"]),
25381
- authType: str(props["AuthType"]),
25382
- weights
25383
- };
25384
- }
25385
- function pickHighestWeight(endpoint) {
25386
- let best;
25387
- for (const w of endpoint.weights) {
25388
- if (w.revisionLogicalId === void 0) continue;
25389
- if (best === void 0 || w.weight > best.weight) best = w;
25390
- }
25391
- return best?.revisionLogicalId;
25392
- }
25393
- function describeRevisions(revisions, stack) {
25394
- return revisions.length > 0 ? `Revisions: ${revisions.map((id) => displayIdent(id)).join(", ")}.` : `No ${WEB_FUNCTION_REVISION_TYPE} linked to it in ${displayStackName(stack.stackName)}.`;
26235
+ function addStartAgentCoreSpecificOptions(cmd) {
26236
+ return cmd.addOption(new Option("--port <n>", "Serve bind port the client connects to (HTTP contract + /ws on the same port). Default 0 (OS-assigned).").default(0).argParser(parsePort$2)).addOption(new Option("--host <ip>", "Serve bind host. Default 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--session-id <id>", "Pin one AgentCore runtime session id header value for every forwarded request / /ws connection (default: a fresh random UUID each, so each is its own session).")).addOption(new Option("--bearer-token <jwt>", "Bearer JWT to present when the runtime declares a customJwtAuthorizer. Verified against the runtime's OIDC discovery URL before the container starts, then injected as Authorization: Bearer <jwt> on every forwarded request and the container /ws upgrade.")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign each forwarded request with AWS SigV4 (service bedrock-agentcore) when the runtime declares NO customJwtAuthorizer, so the warm container sees the same Authorization / X-Amz-* headers the cloud receives. Mutually exclusive with --bearer-token; ignored (with a warning) when a customJwtAuthorizer is declared.")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64). Defaults to linux/arm64 because the cloud AgentCore Runtime requires arm64.").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--watch", "Re-synth + reload the warm container in place on a CDK source change, keeping the serve up. A per-firing classifier picks rebuild (boot a fresh container, re-point the serve) vs soft-reload (docker cp + docker restart the same container) — the same pattern as start-service / invoke-agentcore --ws --watch.")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host IP used to bind the agent container port. Must be a numeric IP. Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Maximum time in milliseconds to wait for the container to become ready (the GET /ping readiness deadline). Default 120000.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container. (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's literal RoleArn; (3) `--no-assume-role` opts out. Off by default.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack and substitute Ref / Fn::ImportValue in env vars / image URIs with the deployed physical IDs / exports. Bare form uses the resolved stack name.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
25395
26237
  }
25396
26238
 
25397
26239
  //#endregion
25398
- //#region src/local/web-function-artifact.ts
25399
- const SUPPORTED_CODE_CONFIG_KEYS = ["S3Object"];
25400
- function parseWebFunctionBuildConfig(revisionLogicalId, buildConfig) {
25401
- const subject = `Web function revision ${displayIdent(revisionLogicalId)}`;
25402
- const build = asObject$1(buildConfig);
25403
- const runtime = asObject$1(build?.["RuntimeConfig"])?.["Runtime"];
25404
- if (typeof runtime !== "string" || runtime.length === 0) throw new CdkLocalError(`${subject} has no literal BuildConfig.RuntimeConfig.Runtime.`, "LOCAL_START_WEB_FUNCTION_NO_RUNTIME");
25405
- const codeConfig = asObject$1(build?.["CodeConfig"]);
25406
- if (!codeConfig) throw new CdkLocalError(`${subject} has no BuildConfig.CodeConfig object.`, "LOCAL_START_WEB_FUNCTION_NO_CODE_CONFIG");
25407
- const unknownKeys = Object.keys(codeConfig).filter((k) => !SUPPORTED_CODE_CONFIG_KEYS.includes(k));
25408
- if (unknownKeys.length > 0) throw new CdkLocalError(`${subject} uses BuildConfig.CodeConfig.${unknownKeys.map((k) => displayIdent(k)).join(", ")}, which ${getEmbedConfig().cliName} start-web-function does not support yet. Supported: ${SUPPORTED_CODE_CONFIG_KEYS.join(", ")}.`, "LOCAL_START_WEB_FUNCTION_UNSUPPORTED_CODE_CONFIG");
25409
- const s3 = asObject$1(codeConfig["S3Object"]);
25410
- if (!s3) throw new CdkLocalError(`${subject} has no BuildConfig.CodeConfig.S3Object.`, "LOCAL_START_WEB_FUNCTION_NO_CODE_CONFIG");
25411
- const key = s3["Key"];
25412
- if (typeof key !== "string" || key.length === 0) throw new CdkLocalError(`${subject} has a CodeConfig.S3Object.Key that is not a literal string (${describeValue(key)}). The code must come from a CDK asset in this app (for example an s3assets.Asset passing asset.s3BucketName / asset.s3ObjectKey), whose key synthesizes as a literal.`, "LOCAL_START_WEB_FUNCTION_CODE_KEY_NOT_LITERAL");
25413
- const versionId = s3["VersionId"];
25414
- return {
25415
- runtime,
25416
- s3Object: {
25417
- bucket: s3["Bucket"],
25418
- key,
25419
- ...typeof versionId === "string" && { versionId }
25420
- }
25421
- };
26240
+ //#region src/cli/commands/local-start-web-function.ts
26241
+ function parsePort$1(raw) {
26242
+ const n = Number(raw);
26243
+ if (!Number.isInteger(n) || n < 0 || n > 65535) throw new InvalidArgumentError(`--port must be an integer 0-65535, got '${raw}'.`);
26244
+ return n;
25422
26245
  }
25423
- function findFileAssetForObjectKey(manifest, key) {
25424
- for (const [hash, asset] of Object.entries(manifest.files ?? {})) for (const dest of Object.values(asset.destinations ?? {})) if (dest.objectKey === key || dest.objectKey.endsWith(`/${key}`)) return {
25425
- hash,
25426
- asset
26246
+ async function localStartWebFunctionCommand(target, options, extraStateProviders, hostSynthesizer) {
26247
+ const logger = getLogger();
26248
+ if (options.verbose) logger.setLevel("debug");
26249
+ const forever = () => new Promise(() => void 0);
26250
+ await applyRoleArnIfSet({
26251
+ roleArn: options.roleArn,
26252
+ region: options.region,
26253
+ profile: options.profile
26254
+ });
26255
+ await ensureDockerAvailable();
26256
+ const profileCredentials = options.profile ? await resolveProfileCredentials(options.profile) : void 0;
26257
+ const appCmd = resolveApp(options.app);
26258
+ if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
26259
+ logger.info("Synthesizing CDK app...");
26260
+ const synthesizer = hostSynthesizer ?? new Synthesizer();
26261
+ const context = parseContextOptions(options.context);
26262
+ const synthOpts = {
26263
+ app: appCmd,
26264
+ output: options.output,
26265
+ ...options.region && { region: options.region },
26266
+ ...options.profile && { profile: options.profile },
26267
+ ...Object.keys(context).length > 0 && { context }
25427
26268
  };
25428
- const hash = key.replace(/^.*\//, "").replace(/\.zip$/, "");
25429
- const byHash = Object.hasOwn(manifest.files ?? {}, hash) ? manifest.files[hash] : void 0;
25430
- return byHash ? {
25431
- hash,
25432
- asset: byHash
25433
- } : void 0;
25434
- }
25435
- async function resolveWebFunctionArtifact(stack, revisionLogicalId, code, loader = new AssetManifestLoader()) {
25436
- const subject = `Web function revision ${displayUntrustedValue(revisionLogicalId)} code bundle`;
25437
- const notFound = () => new CdkLocalError(`${subject} (S3Object bucket ${describeValue(code.s3Object.bucket)}, key ${displayUntrustedValue(code.s3Object.key)}) matches no file asset in this app's cdk.out. ${getEmbedConfig().cliName} start-web-function runs code that comes from a CDK asset in this app (for example an s3assets.Asset of a directory); it does not download deployed code. Re-synthesize the app, or point the revision at an asset.`, "LOCAL_START_WEB_FUNCTION_CODE_ASSET_NOT_FOUND");
25438
- const manifestPath = stack.assetManifestPath;
25439
- if (!manifestPath) throw notFound();
25440
- const manifestDir = dirname$1(manifestPath);
25441
- const manifest = await loader.loadStackManifest(stack, manifestDir);
25442
- const match = manifest ? findFileAssetForObjectKey(manifest, code.s3Object.key) : void 0;
25443
- if (!match) throw notFound();
25444
- if (typeof match.asset.source.path !== "string" || match.asset.source.path.length === 0) throw new CdkLocalError(`${subject} matches file asset ${displayIdent(match.hash)}, which has no source.path (it is produced by source.executable at publish time), so there is no staged bundle in cdk.out to mount. Give the revision an asset of a directory or a .zip file (for example s3assets.Asset with path: '<dir>').`, "LOCAL_START_WEB_FUNCTION_CODE_ASSET_NO_PATH");
25445
- const sourcePath = resolveAssetSourcePath({
25446
- manifestDir,
25447
- value: match.asset.source.path,
25448
- assetOutdir: assetPathDirs(stack).assetOutdir,
25449
- absolute: "honour-warn",
25450
- field: "source.path",
25451
- subject,
25452
- action: "mount it",
25453
- sink: "bind-mount that directory into the web function container",
25454
- wrapError: (message) => new CdkLocalError(message, "LOCAL_START_WEB_FUNCTION_CODE_SOURCE_ESCAPES_ASSEMBLY")
26269
+ const { stacks } = await synthesizer.synthesize(synthOpts);
26270
+ const resolved = resolveWebFunctionTarget(await resolveSingleTarget(target, {
26271
+ entries: listTargets(stacks).webFunctions,
26272
+ message: "Select a Lambda web function to serve",
26273
+ noun: "Lambda web functions",
26274
+ onMissing: () => new CdkLocalError(`${getEmbedConfig().cliName} start-web-function requires a <target> (a WebFunction, WebFunctionEndpoint or WebFunctionRevision display path or logical ID). Run \`${getEmbedConfig().cliName} list\` to see them, or run it in a TTY to pick interactively.`, "LOCAL_START_WEB_FUNCTION_TARGET_REQUIRED")
26275
+ }), stacks, options.revision);
26276
+ const prepared = prepareWebFunctionServe(resolved);
26277
+ const selectionNote = {
26278
+ "target-revision": "the target revision",
26279
+ "revision-flag": "--revision",
26280
+ "endpoint-weight": `highest weight on endpoint ${displayIdent(resolved.endpoint?.logicalId ?? "")}`,
26281
+ "only-revision": "the function's only revision"
26282
+ };
26283
+ logger.info(`Target: ${displayStackName(`${resolved.stack.stackName}/${resolved.revisionLogicalId}`)} (${prepared.spec.runtime}; revision chosen by ${selectionNote[resolved.selection]})`);
26284
+ for (const line of webFunctionFidelityWarnings(resolved, prepared.serviceConfig)) logger.warn(line);
26285
+ const serve = new WebFunctionServe(prepared, {
26286
+ envOptions: options,
26287
+ ...profileCredentials !== void 0 && { profileCredentials },
26288
+ ...extraStateProviders !== void 0 && { extraStateProviders },
26289
+ host: options.host,
26290
+ port: options.port,
26291
+ ...options.platform !== void 0 && { platform: options.platform },
26292
+ skipPull: options.pull === false,
26293
+ timeoutMs: options.timeout
25455
26294
  });
25456
- if (!existsSync$1(sourcePath)) throw new CdkLocalError(`${subject} source ${displayUntrustedValue(sourcePath)} does not exist. Re-synthesize the app and retry.`, "LOCAL_START_WEB_FUNCTION_CODE_SOURCE_MISSING");
25457
- if (!statSync$1(sourcePath).isDirectory() && !sourcePath.endsWith(".zip")) throw new CdkLocalError(`${subject} source ${displayUntrustedValue(sourcePath)} is neither a directory nor a .zip archive, so it is not a code bundle.`, "LOCAL_START_WEB_FUNCTION_CODE_SOURCE_NOT_BUNDLE");
25458
- const materialized = materializeAssetCodeDir(sourcePath);
25459
- return {
25460
- kind: "zip",
25461
- runtime: code.runtime,
25462
- codeDir: materialized.dir,
25463
- ...materialized.tmpDir !== void 0 && { tmpDir: materialized.tmpDir },
25464
- assetHash: match.hash
26295
+ let shuttingDown = false;
26296
+ const shutdown = async (signal, exitCode) => {
26297
+ if (shuttingDown) {
26298
+ if (!serve.waitingOnRun) {
26299
+ logger.info(`Received ${signal} again; still removing the container, please wait...`);
26300
+ return;
26301
+ }
26302
+ serve.disposeArtifactNow();
26303
+ logger.warn(`Received ${signal} again; exiting without waiting for docker run to return. The container may still be starting, and its environment holds the forwarded AWS credentials. Remove it by its name (this works even if it is created after this exit): docker rm -f ${serve.containerName ?? "<name>"}`);
26304
+ process.exit(signal === "SIGINT" ? 130 : 143);
26305
+ return;
26306
+ }
26307
+ shuttingDown = true;
26308
+ logger.info(`Received ${signal}, shutting down...`);
26309
+ await serve.teardown();
26310
+ process.exit(exitCode);
25465
26311
  };
25466
- }
25467
- function disposeWebFunctionArtifact(artifact) {
25468
- if (artifact?.kind !== "zip" || artifact.tmpDir === void 0) return;
25469
- try {
25470
- if (existsSync$1(artifact.tmpDir)) rmSync$1(artifact.tmpDir, {
25471
- recursive: true,
25472
- force: true
25473
- });
25474
- } catch {}
25475
- }
25476
- function asObject$1(value) {
25477
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
25478
- }
25479
- function describeValue(value) {
25480
- if (typeof value === "string") return displayUntrustedValue(value);
25481
- let json;
26312
+ process.on("SIGINT", () => void shutdown("SIGINT", 130));
26313
+ process.on("SIGTERM", () => void shutdown("SIGTERM", 0));
25482
26314
  try {
25483
- json = JSON.stringify(value);
25484
- } catch {
25485
- json = void 0;
26315
+ const address = await serve.start();
26316
+ if (!address) return await forever();
26317
+ logger.info(`Server listening on http://${formatAuthority(address.host, address.port)} (${displayIdent(resolved.functionName)} (Lambda web function))`);
26318
+ logger.info("Press ^C to shut down.");
26319
+ } catch (err) {
26320
+ if (shuttingDown) return await forever();
26321
+ await serve.teardown();
26322
+ throw err;
25486
26323
  }
25487
- return displayUntrustedValue(json ?? String(value));
25488
- }
25489
-
25490
- //#endregion
25491
- //#region src/local/web-function-runtime.ts
25492
- const NODE_CODE_MOUNT = "/var/task";
25493
- const NODE_ENTRYPOINT_ENV = "AWS_LAMBDA_NODEJS_ENTRYPOINT";
25494
- const NODE_DEFAULT_ENTRY = "index.js";
25495
- function buildNodeEntryFileStart(runtime, env, codeDir, fileExists = existsSync$1) {
25496
- const configured = env[NODE_ENTRYPOINT_ENV];
25497
- const entryFile = configured !== void 0 && configured.length > 0 ? configured : NODE_DEFAULT_ENTRY;
25498
- const hostPath = resolve$1(codeDir, entryFile);
25499
- const rel = relative$1(codeDir, hostPath);
25500
- if (isAbsolute$1(entryFile) || rel === "" || rel === ".." || rel.startsWith(`..${sep$1}`) || isAbsolute$1(rel)) throw new CdkLocalError(`${NODE_ENTRYPOINT_ENV}=${displayUntrustedValue(entryFile)} must name a file inside the code bundle, relative to its root.`, "LOCAL_START_WEB_FUNCTION_ENTRYPOINT_OUTSIDE_BUNDLE");
25501
- if (!fileExists(hostPath)) throw new CdkLocalError(`The ${runtime} web function entry file ${displayUntrustedValue(entryFile)} was not found in the code bundle ${displayUntrustedValue(codeDir)}. The runtime loads ${NODE_DEFAULT_ENTRY} from the package root unless ${NODE_ENTRYPOINT_ENV} names another file.`, "LOCAL_START_WEB_FUNCTION_ENTRYPOINT_MISSING");
25502
- const posixRel = sep$1 === "\\" ? rel.split("\\").join("/") : rel;
25503
- return {
25504
- entryPoint: ["/var/lang/bin/node"],
25505
- cmd: [posix.join(NODE_CODE_MOUNT, posixRel)],
25506
- label: entryFile
25507
- };
25508
- }
25509
- const WEB_FUNCTION_RUNTIMES = { "nodejs24.x": {
25510
- runtime: "nodejs24.x",
25511
- baseImage: "public.ecr.aws/lambda/nodejs:24",
25512
- codeMountPath: NODE_CODE_MOUNT,
25513
- architectures: ["arm64"],
25514
- buildStart: (env, codeDir, deps) => buildNodeEntryFileStart("nodejs24.x", env, codeDir, deps?.fileExists)
25515
- } };
25516
- function supportedWebFunctionRuntimes(table = WEB_FUNCTION_RUNTIMES) {
25517
- return Object.keys(table).sort();
26324
+ const exited = serve.exited;
26325
+ if (!exited) return await forever();
26326
+ const exitCode = await exited;
26327
+ if (shuttingDown || serve.tornDown) return await forever();
26328
+ const failure = await serve.exitFailure(`The web function container exited (exit code ${exitCode ?? "unknown"}) while serving.`);
26329
+ await serve.teardown();
26330
+ throw failure;
25518
26331
  }
25519
- function resolveWebFunctionRuntime(runtime, table = WEB_FUNCTION_RUNTIMES) {
25520
- const spec = Object.hasOwn(table, runtime) ? table[runtime] : void 0;
25521
- if (!spec) throw new CdkLocalError(`Web function runtime ${displayIdent(runtime)} is not supported by ${getEmbedConfig().cliName} start-web-function yet. Supported: ${supportedWebFunctionRuntimes(table).join(", ")}.`, "LOCAL_START_WEB_FUNCTION_UNSUPPORTED_RUNTIME");
25522
- return spec;
26332
+ function createLocalStartWebFunctionCommand(opts = {}) {
26333
+ setEmbedConfig(opts.embedConfig);
26334
+ const cmd = new Command("start-web-function").description("Serve an AWS Lambda Web Function locally. Resolves a WebFunction, WebFunctionEndpoint or WebFunctionRevision target to one revision (an endpoint serves its highest-weight revision; --revision overrides), runs its code bundle from cdk.out in a container whose server listens on AWS_LAMBDA_HTTP_ENDPOINT, and publishes it on a local port with the revision's ServiceConfig environment. Target accepts a CDK display path or stack-qualified logical ID; single-stack apps may omit the prefix. Omit <target> in a TTY to pick from a list. Runs until ^C.").argument("[target]", "CDK display path or stack-qualified logical ID of the WebFunction / WebFunctionEndpoint / WebFunctionRevision (omit to pick interactively in a TTY)").action(withErrorHandling(async (target, options) => {
26335
+ await localStartWebFunctionCommand(target, options, opts.extraStateProviders, opts.synthesizer);
26336
+ }));
26337
+ addStartWebFunctionSpecificOptions(cmd);
26338
+ [
26339
+ ...commonOptions(),
26340
+ ...appOptions(),
26341
+ ...contextOptions
26342
+ ].forEach((opt) => cmd.addOption(opt));
26343
+ cmd.addOption(regionOption);
26344
+ return cmd;
25523
26345
  }
25524
- function defaultWebFunctionPlatform(spec) {
25525
- return spec.architectures[0] === "x86_64" ? "linux/amd64" : "linux/arm64";
26346
+ function addStartWebFunctionSpecificOptions(cmd) {
26347
+ return cmd.addOption(new Option("--revision <logicalId>", "Serve this WebFunctionRevision (logical ID) instead of the one the endpoint weights pick.")).addOption(new Option("--port <n>", "Local port to serve on. Default 0 (OS-assigned).").default(0).argParser(parsePort$1)).addOption(new Option("--host <ip>", "Address to publish the port on.").default("127.0.0.1")).addOption(new Option("--platform <platform>", "docker --platform for the server container. Defaults to the runtime's cloud architecture (linux/arm64 for nodejs24.x).").choices(["linux/amd64", "linux/arm64"])).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"RevisionLogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--no-pull", "Skip docker pull of the runtime base image (use the cached image)")).addOption(new Option("--timeout <ms>", "Maximum time in milliseconds to wait for the server to answer HTTP. Default 60000.").default(6e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the revision's execution role and forward STS-issued temp credentials to the container. (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the revision's ServiceConfig.ExecutionRoleArn: a literal ARN as written, or the Fn::GetAtt / Ref CDK synthesizes resolved against --from-cfn-stack deployed state; (3) `--no-assume-role` opts out. Off by default.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. Bare form uses the resolved stack name.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
25526
26348
  }
25527
26349
 
25528
26350
  //#endregion
25529
- //#region src/local/web-function-service-config.ts
25530
- const WEB_FUNCTION_HTTP_ENDPOINT_ENV = "AWS_LAMBDA_HTTP_ENDPOINT";
25531
- const APPLICATION_MANAGED_AUTH = "ApplicationManaged";
25532
- const HOME_REGION_ENDPOINT = "HomeRegion";
25533
- function readWebFunctionServiceConfig(revision) {
25534
- const sc = asObject(revision.Properties?.["ServiceConfig"]) ?? {};
25535
- const envRaw = sc["EnvironmentVariables"];
25536
- const envObj = asObject(envRaw);
25537
- const envKeys = envObj ? Object.keys(envObj) : [];
25538
- const env = envKeys.length === 1 && (envKeys[0] === "Ref" || envKeys[0].startsWith("Fn::")) ? void 0 : envObj;
25539
- const logGroup = asObject(asObject(sc["TelemetryConfig"])?.["LoggingConfig"])?.["LogGroup"];
26351
+ //#region src/local/microvm-image.ts
26352
+ const MICROVM_IMAGE_TYPE = "AWS::Lambda::MicrovmImage";
26353
+ const MICROVM_DEFAULT_INGRESS_PORT = 8080;
26354
+ var MicrovmResolutionError = class MicrovmResolutionError extends Error {
26355
+ constructor(message) {
26356
+ super(message);
26357
+ this.name = "MicrovmResolutionError";
26358
+ Object.setPrototypeOf(this, MicrovmResolutionError.prototype);
26359
+ }
26360
+ };
26361
+ function resolveMicrovmImageTarget(target, stacks) {
26362
+ if (stacks.length === 0) throw new MicrovmResolutionError("No stacks found in the synthesized assembly.");
26363
+ const parsed = parseTarget(target);
26364
+ const selection = selectTargetStack(parsed, target, stacks);
26365
+ let stack;
26366
+ switch (selection.kind) {
26367
+ case "found":
26368
+ stack = selection.stack;
26369
+ break;
26370
+ case "no-prefix": throw new MicrovmResolutionError(`Multiple stacks in app, target ${displayIdent(parsed.pathOrId)} is missing a stack prefix. Use StackName:<logicalId> or StackName/... (path form). Available stacks: ${describeStacks(stacks)}.`);
26371
+ case "not-found": throw new MicrovmResolutionError(`Stack ${displayStackName(selection.pattern)} not found. Available stacks: ${describeStacks(stacks)}.`);
26372
+ case "ambiguous": throw new MicrovmResolutionError(`Stack pattern ${displayStackName(selection.pattern)} matched ${selection.matched.length} stacks: ${describeStacks(selection.matched)}. Use a more specific pattern, or the stack's display path.`);
26373
+ }
26374
+ const resources = stack.template.Resources ?? {};
26375
+ const images = Object.keys(resources).filter((id) => resources[id]?.Type === MICROVM_IMAGE_TYPE);
26376
+ const notFound = () => new MicrovmResolutionError(`Target ${displayStackName(target)} not found. ` + (images.length > 0 ? `MicroVM images in ${displayStackName(stack.stackName)}: ${images.map((id) => displayIdent(id)).join(", ")}.` : `No ${MICROVM_IMAGE_TYPE} resources found in ${displayStackName(stack.stackName)}.`));
26377
+ if (parsed.isPath) {
26378
+ const matches = resolveCdkPathToLogicalIds(parsed.pathOrId, buildCdkPathIndex(stack.template)).filter(({ logicalId }) => resources[logicalId]?.Type === MICROVM_IMAGE_TYPE);
26379
+ if (matches.length === 1) {
26380
+ const id = matches[0].logicalId;
26381
+ return {
26382
+ stack,
26383
+ logicalId: id,
26384
+ resource: resources[id]
26385
+ };
26386
+ }
26387
+ if (matches.length > 1) throw new MicrovmResolutionError(`Target ${displayStackName(target)} matches ${matches.length} ${MICROVM_IMAGE_TYPE} resources: ${matches.map((m) => displayIdent(m.logicalId)).join(", ")}. Refine the path or use the StackName:<logicalId> form.`);
26388
+ throw notFound();
26389
+ }
26390
+ const resource = resources[parsed.pathOrId];
26391
+ if (!resource) throw notFound();
26392
+ if (resource.Type !== "AWS::Lambda::MicrovmImage") throw new MicrovmResolutionError(`Resource ${displayIdent(parsed.pathOrId)} in ${displayStackName(stack.stackName)} is ${displayIdent(resource.Type)}, not a MicroVM image. ${getEmbedConfig().cliName} start-microvm runs ${MICROVM_IMAGE_TYPE} targets.`);
25540
26393
  return {
25541
- environmentVariables: env,
25542
- environmentUnreadable: envRaw !== void 0 && env === void 0,
25543
- executionRoleArn: sc["ExecutionRoleArn"],
25544
- egressNetworkConnectorArn: sc["EgressNetworkConnectorArn"],
25545
- timeoutSeconds: typeof sc["TimeoutSeconds"] === "number" ? sc["TimeoutSeconds"] : void 0,
25546
- logGroup: typeof logGroup === "string" && logGroup.length > 0 ? logGroup : void 0
26394
+ stack,
26395
+ logicalId: parsed.pathOrId,
26396
+ resource
25547
26397
  };
25548
26398
  }
25549
- function buildWebFunctionLambdaView(resolved, config, runtime) {
26399
+ const HOOK_KEYS = {
26400
+ ready: {
26401
+ group: "MicrovmImageHooks",
26402
+ key: "Ready"
26403
+ },
26404
+ validate: {
26405
+ group: "MicrovmImageHooks",
26406
+ key: "Validate"
26407
+ },
26408
+ run: {
26409
+ group: "MicrovmHooks",
26410
+ key: "Run"
26411
+ },
26412
+ suspend: {
26413
+ group: "MicrovmHooks",
26414
+ key: "Suspend"
26415
+ },
26416
+ resume: {
26417
+ group: "MicrovmHooks",
26418
+ key: "Resume"
26419
+ },
26420
+ terminate: {
26421
+ group: "MicrovmHooks",
26422
+ key: "Terminate"
26423
+ }
26424
+ };
26425
+ function readMicrovmImageConfig(logicalId, resource) {
26426
+ const subject = `MicroVM image ${displayIdent(logicalId)}`;
26427
+ const props = resource.Properties ?? {};
26428
+ const name = typeof props["Name"] === "string" && props["Name"].length > 0 ? props["Name"] : logicalId;
26429
+ const uri = asObject(props["CodeArtifact"])?.["Uri"];
26430
+ const codeKey = s3KeyOfUri(uri);
26431
+ if (codeKey === void 0) throw new CdkLocalError(`${subject} has a CodeArtifact.Uri whose object key is not a literal (${describeValue(uri)}). The code must come from a CDK asset in this app (for example an s3assets.Asset of a directory holding the Dockerfile, passing asset.s3ObjectUrl), whose key synthesizes as a literal.`, "LOCAL_START_MICROVM_CODE_URI_NOT_LITERAL");
26432
+ const cpu = props["CpuConfigurations"];
26433
+ const archRaw = Array.isArray(cpu) ? asObject(cpu[0])?.["Architecture"] : void 0;
26434
+ const architecture = typeof archRaw === "string" ? normalizeArchitecture(archRaw) : void 0;
26435
+ const environment = {};
26436
+ const envRaw = props["EnvironmentVariables"];
26437
+ const unemulated = [];
26438
+ if (Array.isArray(envRaw)) for (const entry of envRaw) {
26439
+ const e = asObject(entry);
26440
+ if (e && typeof e["Key"] === "string") environment[e["Key"]] = e["Value"];
26441
+ }
26442
+ else if (envRaw !== void 0) unemulated.push("EnvironmentVariables is an intrinsic rather than a list, so none of its variables reach the container (supply them with --env-vars)");
26443
+ const hooksObj = asObject(props["Hooks"]);
26444
+ const hooks = {};
26445
+ for (const [hook, where] of Object.entries(HOOK_KEYS)) {
26446
+ const group = asObject(hooksObj?.[where.group]);
26447
+ const value = group?.[where.key];
26448
+ if (value === void 0 || value === "DISABLED") continue;
26449
+ if (value !== "ENABLED") throw new CdkLocalError(`${subject} has Hooks.${where.group}.${where.key} ${describeValue(value)}; only the literal ENABLED or DISABLED can run locally.`, "LOCAL_START_MICROVM_HOOK_VALUE");
26450
+ const timeout = group?.[`${where.key}TimeoutInSeconds`];
26451
+ hooks[hook] = { timeoutSeconds: typeof timeout === "number" && timeout > 0 ? timeout : void 0 };
26452
+ }
26453
+ const port = hooksObj?.["Port"];
26454
+ const hooksPort = typeof port === "number" && Number.isInteger(port) && port > 0 && port < 65536 ? port : void 0;
26455
+ if (Object.keys(hooks).length > 0 && hooksPort === void 0) throw new CdkLocalError(`${subject} enables lifecycle hooks (${Object.keys(hooks).join(", ")}) but has no literal Hooks.Port. Lambda requires the hooks port whenever a hook is configured; set Hooks.Port to the port the application serves /aws/lambda-microvms/runtime/v1/ on.`, "LOCAL_START_MICROVM_NO_HOOKS_PORT");
26456
+ const present = (key) => {
26457
+ const v = props[key];
26458
+ if (v === void 0 || v === null) return false;
26459
+ if (Array.isArray(v)) return v.length > 0;
26460
+ if (typeof v === "object") return Object.keys(v).length > 0;
26461
+ return true;
26462
+ };
26463
+ if (present("EgressNetworkConnectors")) unemulated.push("EgressNetworkConnectors (the container uses Docker's default network egress)");
26464
+ if (present("AdditionalOsCapabilities")) unemulated.push("AdditionalOsCapabilities (the container runs with Docker's default capabilities)");
26465
+ if (present("Resources")) unemulated.push("Resources sizing (the container gets no memory limit)");
26466
+ if (present("BaseImageVersion")) unemulated.push("BaseImageVersion (the local build uses whatever the Dockerfile names)");
26467
+ if (present("Logging")) unemulated.push("Logging (container output is printed here instead of sent to CloudWatch Logs)");
25550
26468
  return {
25551
- kind: "zip",
25552
- stack: resolved.stack,
25553
- logicalId: resolved.revisionLogicalId,
25554
- resource: {
25555
- Type: resolved.revision.Type,
25556
- Properties: {
25557
- ...config.environmentVariables !== void 0 && { Environment: { Variables: config.environmentVariables } },
25558
- ...config.executionRoleArn !== void 0 && { Role: config.executionRoleArn }
25559
- },
25560
- ...resolved.revision.Metadata !== void 0 && { Metadata: resolved.revision.Metadata }
25561
- },
25562
- memoryMb: 128,
25563
- timeoutSec: config.timeoutSeconds ?? 0,
25564
- layers: [],
25565
- runtime,
25566
- handler: "",
25567
- codePath: null,
25568
- architecture: "arm64"
26469
+ logicalId,
26470
+ name,
26471
+ codeKey,
26472
+ architecture,
26473
+ environment,
26474
+ hooksPort,
26475
+ hooks,
26476
+ unemulated
25569
26477
  };
25570
26478
  }
25571
- function webFunctionAssumeRole(assumeRole, config) {
25572
- if (assumeRole === true && isIamRoleArn(config.executionRoleArn)) return config.executionRoleArn;
25573
- return assumeRole;
26479
+ function normalizeArchitecture(raw) {
26480
+ const s = raw.toLowerCase().replace(/_/g, "");
26481
+ if (s === "arm64") return "arm64";
26482
+ if (s === "x8664") return "x86_64";
25574
26483
  }
25575
- function finalizeWebFunctionEnv(lambdaEnv, opts) {
25576
- const env = { ...lambdaEnv };
25577
- delete env["AWS_LAMBDA_FUNCTION_MEMORY_SIZE"];
25578
- delete env["AWS_LAMBDA_FUNCTION_VERSION"];
25579
- delete env["AWS_LAMBDA_FUNCTION_TIMEOUT"];
25580
- env["AWS_LAMBDA_FUNCTION_NAME"] = opts.functionName;
25581
- env["AWS_LAMBDA_LOG_GROUP_NAME"] = opts.logGroup ?? `/aws/lambda/${opts.functionName}`;
25582
- if (opts.timeoutSeconds !== void 0) env["AWS_LAMBDA_FUNCTION_TIMEOUT"] = String(opts.timeoutSeconds);
25583
- const replacedHttpEndpoint = Object.hasOwn(lambdaEnv, WEB_FUNCTION_HTTP_ENDPOINT_ENV);
25584
- env[WEB_FUNCTION_HTTP_ENDPOINT_ENV] = `0.0.0.0:${opts.containerPort}`;
25585
- return {
25586
- env,
25587
- replacedHttpEndpoint
25588
- };
26484
+ function s3KeyOfUri(value) {
26485
+ const flat = flattenTemplateString(value);
26486
+ if (flat === void 0) return void 0;
26487
+ const m = /^s3:\/\/[^/]*\/(.+)$/.exec(flat);
26488
+ if (!m) return void 0;
26489
+ const key = m[1];
26490
+ return key.includes("\0") || key.includes("${") ? void 0 : key;
25589
26491
  }
25590
- function webFunctionFidelityWarnings(resolved, config) {
25591
- const out = [];
25592
- const rev = displayIdent(resolved.revisionLogicalId);
25593
- if (config.egressNetworkConnectorArn !== void 0) out.push(`Revision ${rev} sets ServiceConfig.EgressNetworkConnectorArn; the network connector is not emulated, so the container reaches the network through Docker's default bridge.`);
25594
- if (config.environmentUnreadable) out.push(`Revision ${rev} declares ServiceConfig.EnvironmentVariables as an intrinsic rather than a map; none of its variables reach the container. Supply them with --env-vars.`);
25595
- const endpoint = resolved.endpoint;
25596
- if (endpoint) {
25597
- const ep = displayIdent(endpoint.logicalId);
25598
- if (endpoint.endpointType !== void 0 && endpoint.endpointType !== "HomeRegion") out.push(`Endpoint ${ep} has EndpointType ${displayIdent(endpoint.endpointType)}; only a single ${HOME_REGION_ENDPOINT} endpoint is reproduced, on one local port.`);
25599
- if (endpoint.authType !== void 0 && endpoint.authType !== "ApplicationManaged") out.push(`Endpoint ${ep} has AuthType ${displayIdent(endpoint.authType)}; it is not enforced locally — every request reaches the server unauthenticated.`);
25600
- const routed = endpoint.weights.filter((w) => w.weight > 0);
25601
- if (routed.length > 1) out.push(`Endpoint ${ep} splits traffic across ${routed.length} revisions; weighted routing is not emulated — every request goes to revision ${rev}.`);
26492
+ function flattenTemplateString(value) {
26493
+ if (typeof value === "string") return value;
26494
+ const obj = asObject(value);
26495
+ if (!obj) return void 0;
26496
+ const join = obj["Fn::Join"];
26497
+ if (Array.isArray(join) && typeof join[0] === "string" && Array.isArray(join[1])) return join[1].map((p) => flattenTemplateString(p) ?? "\0").join(join[0]);
26498
+ const sub = obj["Fn::Sub"];
26499
+ if (typeof sub === "string") return sub.replace(/\$\{[^}]*\}/g, "\0");
26500
+ if (Array.isArray(sub) && typeof sub[0] === "string") return sub[0].replace(/\$\{[^}]*\}/g, "\0");
26501
+ return "\0";
26502
+ }
26503
+ async function resolveMicrovmCodeArtifact(stack, config, loader = new AssetManifestLoader()) {
26504
+ const subject = `MicroVM image ${displayUntrustedValue(config.logicalId)} code artifact`;
26505
+ const notFound = () => new CdkLocalError(`${subject} (key ${displayUntrustedValue(config.codeKey)}) matches no file asset in this app's cdk.out. ${getEmbedConfig().cliName} start-microvm builds code that comes from a CDK asset in this app (for example an s3assets.Asset of a directory holding the Dockerfile); it does not download deployed code. Re-synthesize the app, or point CodeArtifact.Uri at an asset.`, "LOCAL_START_MICROVM_CODE_ASSET_NOT_FOUND");
26506
+ const manifestPath = stack.assetManifestPath;
26507
+ if (!manifestPath) throw notFound();
26508
+ const manifestDir = dirname$1(manifestPath);
26509
+ const manifest = await loader.loadStackManifest(stack, manifestDir);
26510
+ const match = manifest ? findFileAssetForObjectKey(manifest, config.codeKey) : void 0;
26511
+ if (!match) throw notFound();
26512
+ if (typeof match.asset.source.path !== "string" || match.asset.source.path.length === 0) throw new CdkLocalError(`${subject} matches file asset ${displayIdent(match.hash)}, which has no source.path (it is produced by source.executable at publish time), so there is no staged bundle in cdk.out to build. Give the image an asset of a directory or a .zip file.`, "LOCAL_START_MICROVM_CODE_ASSET_NO_PATH");
26513
+ const sourcePath = resolveAssetSourcePath({
26514
+ manifestDir,
26515
+ value: match.asset.source.path,
26516
+ assetOutdir: assetPathDirs(stack).assetOutdir,
26517
+ absolute: "honour-warn",
26518
+ field: "source.path",
26519
+ subject,
26520
+ action: "build it",
26521
+ sink: "send that directory to docker build as the MicroVM image context",
26522
+ wrapError: (message) => new CdkLocalError(message, "LOCAL_START_MICROVM_CODE_SOURCE_ESCAPES_ASSEMBLY")
26523
+ });
26524
+ if (!existsSync$1(sourcePath)) throw new CdkLocalError(`${subject} source ${displayUntrustedValue(sourcePath)} does not exist. Re-synthesize the app and retry.`, "LOCAL_START_MICROVM_CODE_SOURCE_MISSING");
26525
+ if (!statSync$1(sourcePath).isDirectory() && !sourcePath.endsWith(".zip")) throw new CdkLocalError(`${subject} source ${displayUntrustedValue(sourcePath)} is neither a directory nor a .zip archive, so it is not a code bundle.`, "LOCAL_START_MICROVM_CODE_SOURCE_NOT_BUNDLE");
26526
+ const materialized = materializeAssetCodeDir(sourcePath);
26527
+ const artifact = {
26528
+ contextDir: materialized.dir,
26529
+ ...materialized.tmpDir !== void 0 && { tmpDir: materialized.tmpDir },
26530
+ assetHash: match.hash
26531
+ };
26532
+ if (!existsSync$1(join$1(materialized.dir, "Dockerfile"))) {
26533
+ disposeMicrovmCodeArtifact(artifact);
26534
+ throw new CdkLocalError(`${subject} has no Dockerfile at its root. A MicroVM image's code artifact is a zip with a Dockerfile at the root plus the application files.`, "LOCAL_START_MICROVM_NO_DOCKERFILE");
25602
26535
  }
25603
- return out;
26536
+ return artifact;
26537
+ }
26538
+ function disposeMicrovmCodeArtifact(artifact) {
26539
+ if (artifact?.tmpDir === void 0) return;
26540
+ try {
26541
+ if (existsSync$1(artifact.tmpDir)) rmSync$1(artifact.tmpDir, {
26542
+ recursive: true,
26543
+ force: true
26544
+ });
26545
+ } catch {}
25604
26546
  }
25605
26547
  function asObject(value) {
25606
26548
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
25607
26549
  }
26550
+ function describeValue(value) {
26551
+ if (typeof value === "string") return displayUntrustedValue(value);
26552
+ let json;
26553
+ try {
26554
+ json = JSON.stringify(value);
26555
+ } catch {
26556
+ json = void 0;
26557
+ }
26558
+ return displayUntrustedValue(json ?? String(value));
26559
+ }
25608
26560
 
25609
26561
  //#endregion
25610
- //#region src/local/web-function-runner.ts
25611
- const execFileAsync = promisify$1(execFile$1);
25612
- const WEB_FUNCTION_CONTAINER_PORT = 8080;
25613
- const READY_FAILURE_LOG_LINES = 20;
25614
- const MAX_PROBE_TIMEOUT_MS = 1e4;
25615
- function planWebFunctionContainer(artifact, spec, env, deps) {
25616
- if (artifact.kind !== "zip") throw new CdkLocalError("A container-image web function source is not supported yet; only a zip bundle from CodeConfig.S3Object runs locally.", "LOCAL_START_WEB_FUNCTION_UNSUPPORTED_ARTIFACT");
25617
- const start = spec.buildStart(env, artifact.codeDir, deps);
25618
- return {
25619
- image: spec.baseImage,
25620
- mounts: [{
25621
- hostPath: artifact.codeDir,
25622
- containerPath: spec.codeMountPath,
25623
- readOnly: true
25624
- }],
25625
- entryPoint: start.entryPoint,
25626
- cmd: start.cmd,
25627
- workingDir: spec.codeMountPath,
25628
- label: start.label
25629
- };
26562
+ //#region src/local/microvm-hooks.ts
26563
+ const MICROVM_HOOK_PATH_PREFIX = "/aws/lambda-microvms/runtime/v1/";
26564
+ const MAX_RUN_HOOK_PAYLOAD_BYTES = 16 * 1024;
26565
+ const HOOK_RETRY_INTERVAL_MS = 250;
26566
+ var MicrovmHookError = class MicrovmHookError extends CdkLocalError {
26567
+ constructor(message, code = "LOCAL_START_MICROVM_HOOK_FAILED") {
26568
+ super(message, code);
26569
+ this.name = "MicrovmHookError";
26570
+ Object.setPrototypeOf(this, MicrovmHookError.prototype);
26571
+ }
26572
+ };
26573
+ function generateMicrovmId() {
26574
+ return `mvm-${randomUUID$1()}`;
25630
26575
  }
25631
- async function startWebFunctionContainer(opts) {
25632
- const { plan } = opts;
25633
- return runDetached({
25634
- image: plan.image,
25635
- mounts: plan.mounts,
25636
- env: opts.env,
25637
- ...opts.sensitiveEnvKeys.size > 0 && { sensitiveEnvKeys: opts.sensitiveEnvKeys },
25638
- entryPoint: plan.entryPoint,
25639
- cmd: plan.cmd,
25640
- ...plan.workingDir !== void 0 && { workingDir: plan.workingDir },
25641
- hostPort: opts.hostPort,
25642
- containerPort: WEB_FUNCTION_CONTAINER_PORT,
25643
- host: opts.host,
25644
- platform: opts.platform,
25645
- name: opts.name,
25646
- autoRemove: false
26576
+ function runHookBody(microvmId, runHookPayload) {
26577
+ return JSON.stringify({
26578
+ microvmId,
26579
+ ...runHookPayload !== void 0 && { runHookPayload }
25647
26580
  });
25648
26581
  }
25649
- var RecentOutput = class {
25650
- lines = [];
25651
- partial = "";
25652
- max;
25653
- constructor(max = 20) {
25654
- this.max = max;
25655
- }
25656
- push(chunk) {
25657
- const parts = (this.partial + chunk.toString()).split("\n");
25658
- this.partial = parts.pop() ?? "";
25659
- for (const line of parts) this.add(line);
25660
- if (this.partial.length > 4096) {
25661
- this.add(this.partial);
25662
- this.partial = "";
25663
- }
25664
- }
25665
- tail() {
25666
- return this.partial.length > 0 ? [...this.lines, this.partial].slice(-this.max) : this.lines;
25667
- }
25668
- add(line) {
25669
- this.lines.push(line.replace(/\r$/, ""));
25670
- if (this.lines.length > this.max) this.lines.shift();
25671
- }
25672
- };
25673
- async function waitForWebFunctionReady(args, deps = defaultReadyDeps) {
25674
- const deadline = deps.now() + args.timeoutMs;
26582
+ async function pollBuildHook(hook, endpoint, timeoutMs, deps = defaultHookDeps) {
26583
+ const path = `${MICROVM_HOOK_PATH_PREFIX}${hook}`;
26584
+ const deadline = deps.now() + timeoutMs;
26585
+ let attempts = 0;
26586
+ let last = { kind: "refused" };
26587
+ while (deps.now() < deadline) {
26588
+ const remaining = Math.max(1, deadline - deps.now());
26589
+ last = await deps.post(endpoint.host, endpoint.port, path, void 0, remaining);
26590
+ attempts++;
26591
+ if (last.kind === "status" && last.status === 200) return attempts;
26592
+ if (last.kind === "timeout") throw new MicrovmHookError(`The /${hook} hook held the request open for ${timeoutMs}ms without answering. Answer 503 immediately while not ready (the cloud ends the image build when a held request reaches the timeout), or raise ${timeoutField(hook)}.`, "LOCAL_START_MICROVM_HOOK_TIMEOUT");
26593
+ if (last.kind === "status" && last.status !== 503) throw new MicrovmHookError(`The /${hook} hook answered HTTP ${last.status}; it must answer 200 when done or 503 to be retried.${bodySuffix(last.body)}`);
26594
+ if (deps.isAlive && attempts % 4 === 0 && !await deps.isAlive()) throw new MicrovmHookError(`The MicroVM container exited while cdk-local polled its /${hook} hook.`, "LOCAL_START_MICROVM_EXITED");
26595
+ await deps.sleep(250);
26596
+ }
26597
+ const lastSeen = last.kind === "status" ? `its last answer was HTTP ${last.status}` : `nothing answered on ${formatAuthority(endpoint.host, endpoint.port)} (the hooks port)`;
26598
+ throw new MicrovmHookError(`The /${hook} hook did not answer 200 within ${timeoutMs}ms (${timeoutField(hook)}); ${lastSeen}.`, "LOCAL_START_MICROVM_HOOK_TIMEOUT");
26599
+ }
26600
+ async function callRuntimeHook(hook, endpoint, timeoutMs, body, deps = defaultHookDeps) {
26601
+ const path = `${MICROVM_HOOK_PATH_PREFIX}${hook}`;
26602
+ const result = await deps.post(endpoint.host, endpoint.port, path, body, timeoutMs);
26603
+ if (result.kind === "status" && result.status === 200) return;
26604
+ if (result.kind === "timeout") throw new MicrovmHookError(`The /${hook} hook did not answer within ${timeoutMs}ms (${timeoutField(hook)}).`, "LOCAL_START_MICROVM_HOOK_TIMEOUT");
26605
+ if (result.kind === "refused") throw new MicrovmHookError(`The /${hook} hook could not be reached: nothing answered on the hooks port (${formatAuthority(endpoint.host, endpoint.port)}).`);
26606
+ throw new MicrovmHookError(`The /${hook} hook answered HTTP ${result.status}; it must answer 200.${bodySuffix(result.body)}`);
26607
+ }
26608
+ async function waitForAppAnswer(endpoint, timeoutMs, deps = {
26609
+ probe: probeHttp,
26610
+ sleep: defaultHookDeps.sleep,
26611
+ now: defaultHookDeps.now
26612
+ }) {
26613
+ const deadline = deps.now() + timeoutMs;
25675
26614
  let attempt = 0;
25676
- let last = "refused";
25677
26615
  while (deps.now() < deadline) {
25678
26616
  const remaining = Math.max(1, deadline - deps.now());
25679
- last = await deps.probe(args.host, args.port, Math.min(remaining, MAX_PROBE_TIMEOUT_MS));
25680
- if (last === "answered") return;
26617
+ const outcome = await deps.probe(endpoint.host, endpoint.port, Math.min(remaining, 1e4));
26618
+ if (outcome === "answered") return;
25681
26619
  attempt++;
25682
- if (attempt % 4 === 0 && !await deps.isRunning(args.containerId)) throw await containerFailure(`The web function container exited before its server listened on AWS_LAMBDA_HTTP_ENDPOINT (started ${args.label}).`, args.containerId, args.recent, deps.fetchLogs);
25683
- if (last === "refused") await deps.sleep(250);
26620
+ if (deps.isAlive && attempt % 4 === 0 && !await deps.isAlive()) throw new MicrovmHookError("The MicroVM container exited before its application answered HTTP.", "LOCAL_START_MICROVM_EXITED");
26621
+ if (outcome === "refused") await deps.sleep(250);
25684
26622
  }
25685
- const authority = formatAuthority(args.host, args.port);
25686
- throw await containerFailure(last === "timeout" ? `The web function server on ${authority} accepted the connection but did not answer GET / within ${args.timeoutMs}ms — it is listening but slow to respond. Raise --timeout, or make / answer sooner (started ${args.label}).` : `The web function server never accepted an HTTP connection on ${authority} within ${args.timeoutMs}ms. It must listen on the host:port given in AWS_LAMBDA_HTTP_ENDPOINT (started ${args.label}).`, args.containerId, args.recent, deps.fetchLogs);
26623
+ throw new MicrovmHookError(`The MicroVM application never answered HTTP on ${formatAuthority(endpoint.host, endpoint.port)} within ${timeoutMs}ms. With no /ready hook, cdk-local waits for the app to answer before driving the next hook; raise --timeout, or enable the /ready hook.`, "LOCAL_START_MICROVM_NOT_READY");
25687
26624
  }
25688
- async function containerFailure(message, containerId, recent, fetchLogs = readContainerLogTail, code = "LOCAL_START_WEB_FUNCTION_NOT_READY") {
25689
- let lines = [];
25690
- try {
25691
- lines = await fetchLogs(containerId, 20);
25692
- } catch {
25693
- lines = [];
25694
- }
25695
- if (lines.length === 0) lines = recent.tail();
25696
- return new CdkLocalError(`${message}${lines.length > 0 ? ` Last ${lines.length} line(s) of container output:\n${lines.map((l) => ` ${displayToolOutput(l)}`).join("\n")}` : " The container printed nothing."}`, code);
26625
+ function timeoutField(hook) {
26626
+ return `${hook.charAt(0).toUpperCase() + hook.slice(1)}TimeoutInSeconds, or --timeout when it is unset`;
25697
26627
  }
25698
- function watchContainerExit(containerId) {
25699
- let stopped = false;
25700
- const proc = spawn$1(getDockerCmd(), ["wait", containerId], { stdio: [
25701
- "ignore",
25702
- "pipe",
25703
- "ignore"
25704
- ] });
25705
- let out = "";
25706
- proc.stdout?.on("data", (c) => out += c.toString());
25707
- return {
25708
- exited: new Promise((resolveExit) => {
25709
- const done = () => {
25710
- if (stopped) return;
25711
- const code = Number.parseInt(out.trim(), 10);
25712
- resolveExit(Number.isNaN(code) ? void 0 : code);
25713
- };
25714
- proc.on("close", done);
25715
- proc.on("error", done);
25716
- }),
25717
- stop: () => {
25718
- stopped = true;
25719
- if (!proc.killed) proc.kill("SIGTERM");
25720
- }
25721
- };
26628
+ function bodySuffix(body) {
26629
+ const trimmed = body.trim();
26630
+ if (trimmed.length === 0) return "";
26631
+ return ` Body: ${displayToolOutput(trimmed.length > 500 ? `${trimmed.slice(0, 500)}...` : trimmed)}`;
25722
26632
  }
25723
- function probeHttp(host, port, timeoutMs) {
25724
- return new Promise((resolveProbe) => {
25725
- let timedOut = false;
25726
- let connected = false;
26633
+ function postHook(host, port, path, body, timeoutMs) {
26634
+ return new Promise((resolvePost) => {
26635
+ let settled = false;
26636
+ const settle = (r) => {
26637
+ if (settled) return;
26638
+ settled = true;
26639
+ clearTimeout(timer);
26640
+ resolvePost(r);
26641
+ };
26642
+ const payload = body ?? "";
25727
26643
  const req = request$2({
25728
26644
  host,
25729
26645
  port,
25730
- path: "/",
25731
- method: "GET",
25732
- timeout: timeoutMs,
25733
- headers: { connection: "close" }
26646
+ path,
26647
+ method: "POST",
26648
+ headers: {
26649
+ connection: "close",
26650
+ "content-length": Buffer.byteLength(payload),
26651
+ ...body !== void 0 && { "content-type": "application/json" }
26652
+ }
25734
26653
  }, (res) => {
25735
- res.resume();
25736
- resolveProbe("answered");
26654
+ const chunks = [];
26655
+ let size = 0;
26656
+ res.on("data", (c) => {
26657
+ if (size < 64 * 1024) chunks.push(c);
26658
+ size += c.length;
26659
+ });
26660
+ res.on("end", () => settle({
26661
+ kind: "status",
26662
+ status: res.statusCode ?? 0,
26663
+ body: Buffer.concat(chunks).toString("utf-8")
26664
+ }));
26665
+ res.on("error", () => settle({
26666
+ kind: "status",
26667
+ status: res.statusCode ?? 0,
26668
+ body: ""
26669
+ }));
25737
26670
  });
25738
- req.on("socket", (socket) => {
25739
- socket.once("connect", () => {
25740
- connected = true;
26671
+ const timer = setTimeout(() => {
26672
+ settle({ kind: "timeout" });
26673
+ req.destroy();
26674
+ }, timeoutMs);
26675
+ req.on("error", () => settle({ kind: "refused" }));
26676
+ req.end(payload);
26677
+ });
26678
+ }
26679
+ const defaultHookDeps = {
26680
+ post: postHook,
26681
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
26682
+ now: () => Date.now()
26683
+ };
26684
+
26685
+ //#endregion
26686
+ //#region src/local/microvm-front-door.ts
26687
+ const MICROVM_CONTROL_PATH = "/_cdkl/microvm";
26688
+ const PROXY_PORT_HEADER = "x-aws-proxy-port";
26689
+ const MICROVM_WS_BASE_PROTOCOL = "lambda-microvms";
26690
+ const CLOSE_GRACE_MS = 1e3;
26691
+ function selectTargetPort(headers) {
26692
+ const fromHeader = firstHeader(headers[PROXY_PORT_HEADER]);
26693
+ const fromProtocol = fromHeader === void 0 ? subprotocols(headers).find((p) => p.startsWith(`${MICROVM_WS_BASE_PROTOCOL}.port.`)) : void 0;
26694
+ const raw = fromHeader ?? (fromProtocol !== void 0 ? fromProtocol.slice(`${"lambda-microvms"}.port.`.length) : void 0);
26695
+ if (raw === void 0) return {
26696
+ ok: true,
26697
+ port: MICROVM_DEFAULT_INGRESS_PORT
26698
+ };
26699
+ const trimmed = raw.trim();
26700
+ const n = /^\d{1,5}$/.test(trimmed) ? Number(trimmed) : NaN;
26701
+ if (!Number.isInteger(n) || n < 1 || n > 65535) return {
26702
+ ok: false,
26703
+ status: 400,
26704
+ message: `Invalid ${fromHeader === void 0 ? "WebSocket subprotocol lambda-microvms.port." : "X-aws-proxy-port"} value '${trimmed.slice(0, 32)}': it must be a port number 1-65535.`
26705
+ };
26706
+ return {
26707
+ ok: true,
26708
+ port: n
26709
+ };
26710
+ }
26711
+ function stripMicrovmProxyHeaders(headers) {
26712
+ for (const name of Object.keys(headers)) if (name.toLowerCase().startsWith("x-aws-proxy-")) delete headers[name];
26713
+ const offered = subprotocols(headers);
26714
+ if (offered.length === 0) return false;
26715
+ const kept = offered.filter((p) => !isMicrovmProtocol(p));
26716
+ if (kept.length > 0) headers["sec-websocket-protocol"] = kept.join(", ");
26717
+ else delete headers["sec-websocket-protocol"];
26718
+ return offered.includes(MICROVM_WS_BASE_PROTOCOL);
26719
+ }
26720
+ function isMicrovmProtocol(p) {
26721
+ return p === "lambda-microvms" || p.startsWith(`${"lambda-microvms"}.`);
26722
+ }
26723
+ function subprotocols(headers) {
26724
+ const raw = headers["sec-websocket-protocol"];
26725
+ const joined = Array.isArray(raw) ? raw.join(",") : raw;
26726
+ if (!joined) return [];
26727
+ return joined.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
26728
+ }
26729
+ function firstHeader(value) {
26730
+ if (Array.isArray(value)) return value[0];
26731
+ if (typeof value === "string") return value.split(",")[0];
26732
+ }
26733
+ function startMicrovmFrontDoor(opts) {
26734
+ const logger = getLogger().child("microvm");
26735
+ const bound = { port: opts.port };
26736
+ const server = createServer((req, res) => {
26737
+ handleRequest$1(req, res, opts, bound.port).catch((err) => {
26738
+ logger.debug(`microvm front door error: ${err instanceof Error ? err.message : String(err)}`);
26739
+ if (!res.headersSent) writeText(res, 502, "The MicroVM front door failed to forward the request.");
26740
+ else res.destroy();
26741
+ });
26742
+ });
26743
+ const upgraded = /* @__PURE__ */ new Set();
26744
+ const track = (sock) => {
26745
+ upgraded.add(sock);
26746
+ sock.once("close", () => upgraded.delete(sock));
26747
+ };
26748
+ server.on("upgrade", (req, socket, head) => {
26749
+ track(socket);
26750
+ handleUpgrade(req, socket, head, opts, track).catch((err) => {
26751
+ logger.debug(`microvm upgrade error: ${err instanceof Error ? err.message : String(err)}`);
26752
+ writeRawHttpError(socket, 502, "The MicroVM front door failed to forward the upgrade.");
26753
+ });
26754
+ });
26755
+ return new Promise((resolveStart, rejectStart) => {
26756
+ server.once("error", rejectStart);
26757
+ server.listen(opts.port, opts.host, () => {
26758
+ server.off("error", rejectStart);
26759
+ const address = server.address();
26760
+ const port = typeof address === "object" && address !== null ? address.port : opts.port;
26761
+ bound.port = port;
26762
+ resolveStart({
26763
+ port,
26764
+ close: () => new Promise((resolveClose) => {
26765
+ server.close(() => resolveClose());
26766
+ server.closeIdleConnections();
26767
+ for (const sock of upgraded) sock.end();
26768
+ setTimeout(() => {
26769
+ server.closeAllConnections();
26770
+ for (const sock of upgraded) sock.destroy();
26771
+ }, CLOSE_GRACE_MS).unref();
26772
+ })
26773
+ });
26774
+ });
26775
+ });
26776
+ }
26777
+ async function admit(opts) {
26778
+ const id = opts.target.microvmId;
26779
+ let state = opts.target.state();
26780
+ if ((state === "suspended" || state === "suspending" || state === "resuming") && opts.autoResume) {
26781
+ try {
26782
+ await opts.target.resume();
26783
+ } catch (err) {
26784
+ const now = opts.target.state();
26785
+ if (err instanceof MicrovmStateError && (now === "terminating" || now === "terminated")) return {
26786
+ status: 503,
26787
+ message: `MicroVM ${id} is ${now}.`
26788
+ };
26789
+ return {
26790
+ status: 502,
26791
+ message: `MicroVM ${id} could not be resumed for this request: ${err instanceof Error ? err.message : String(err)}`
26792
+ };
26793
+ }
26794
+ state = opts.target.state();
26795
+ }
26796
+ if (state === "running") return void 0;
26797
+ if (state === "terminating" || state === "terminated") return {
26798
+ status: 503,
26799
+ message: `MicroVM ${id} is ${state}.`
26800
+ };
26801
+ return {
26802
+ status: 503,
26803
+ message: `MicroVM ${id} is ${state}. Resume it with POST ${MICROVM_CONTROL_PATH}/resume (or start with --auto-resume to resume on the first request).`
26804
+ };
26805
+ }
26806
+ async function handleRequest$1(req, res, opts, boundPort) {
26807
+ const path = (req.url ?? "/").split("?")[0];
26808
+ if (path === "/_cdkl/microvm" || path.startsWith(`${"/_cdkl/microvm"}/`)) {
26809
+ const refusal = controlRequestRefusal(req.headers, opts.host, boundPort);
26810
+ if (refusal !== void 0) {
26811
+ req.resume();
26812
+ writeText(res, 403, refusal);
26813
+ return;
26814
+ }
26815
+ await handleControl(req, res, path, opts);
26816
+ return;
26817
+ }
26818
+ const selection = selectTargetPort(req.headers);
26819
+ if (!selection.ok) {
26820
+ req.resume();
26821
+ writeText(res, selection.status, selection.message);
26822
+ return;
26823
+ }
26824
+ const upstream = opts.target.upstreamFor(selection.port);
26825
+ if (!upstream) {
26826
+ req.resume();
26827
+ writeText(res, 403, notRoutableMessage(selection.port, opts.target));
26828
+ return;
26829
+ }
26830
+ const refusal = await admit(opts);
26831
+ if (refusal) {
26832
+ req.resume();
26833
+ writeText(res, refusal.status, refusal.message);
26834
+ return;
26835
+ }
26836
+ const headers = { ...req.headers };
26837
+ stripHopByHopHeaders(headers);
26838
+ stripMicrovmProxyHeaders(headers);
26839
+ await new Promise((resolveForward) => {
26840
+ const proxyReq = request({
26841
+ host: upstream.host,
26842
+ port: upstream.port,
26843
+ method: req.method,
26844
+ path: req.url,
26845
+ headers
26846
+ }, (proxyRes) => {
26847
+ const resHeaders = { ...proxyRes.headers };
26848
+ stripHopByHopHeaders(resHeaders);
26849
+ res.writeHead(proxyRes.statusCode ?? 502, resHeaders);
26850
+ proxyRes.pipe(res);
26851
+ proxyRes.on("end", resolveForward);
26852
+ proxyRes.on("error", () => {
26853
+ if (!res.writableEnded) res.destroy();
26854
+ resolveForward();
25741
26855
  });
25742
26856
  });
25743
- req.on("timeout", () => {
25744
- timedOut = true;
25745
- req.destroy();
26857
+ proxyReq.on("error", () => {
26858
+ if (!res.headersSent) writeText(res, 502, `The application in MicroVM ${opts.target.microvmId} is not responding on port ${selection.port}.`);
26859
+ else if (!res.writableEnded) res.destroy();
26860
+ resolveForward();
25746
26861
  });
25747
- req.on("error", () => resolveProbe(timedOut && connected ? "timeout" : "refused"));
25748
- req.end();
26862
+ res.on("close", () => {
26863
+ if (!res.writableEnded) proxyReq.destroy();
26864
+ resolveForward();
26865
+ });
26866
+ req.pipe(proxyReq);
25749
26867
  });
25750
26868
  }
25751
- async function containerIsRunning(containerId) {
26869
+ async function handleControl(req, res, path, opts) {
26870
+ req.resume();
26871
+ const target = opts.target;
26872
+ const action = path.slice(14).replace(/^\//, "");
26873
+ if (action === "") {
26874
+ if (req.method !== "GET" && req.method !== "HEAD") {
26875
+ writeText(res, 405, `Use GET ${MICROVM_CONTROL_PATH}.`);
26876
+ return;
26877
+ }
26878
+ writeJson(res, 200, {
26879
+ microvmId: target.microvmId,
26880
+ state: target.state(),
26881
+ ports: target.routablePorts()
26882
+ });
26883
+ return;
26884
+ }
26885
+ if (action !== "suspend" && action !== "resume" && action !== "terminate") {
26886
+ writeText(res, 404, `Unknown control action '${action.slice(0, 32)}'. Use POST ${MICROVM_CONTROL_PATH}/suspend, /resume or /terminate.`);
26887
+ return;
26888
+ }
26889
+ if (req.method !== "POST") {
26890
+ writeText(res, 405, `Use POST ${MICROVM_CONTROL_PATH}/${action}.`);
26891
+ return;
26892
+ }
26893
+ try {
26894
+ await (action === "suspend" ? target.suspend() : action === "resume" ? target.resume() : target.terminate());
26895
+ } catch (err) {
26896
+ const message = err instanceof Error ? err.message : String(err);
26897
+ writeText(res, err instanceof MicrovmStateError ? 409 : 502, message);
26898
+ return;
26899
+ }
26900
+ writeJson(res, 200, {
26901
+ microvmId: target.microvmId,
26902
+ state: target.state()
26903
+ });
26904
+ }
26905
+ const LOOPBACK_NAMES = [
26906
+ "localhost",
26907
+ "127.0.0.1",
26908
+ "[::1]"
26909
+ ];
26910
+ function isWildcard(host) {
26911
+ return host === "0.0.0.0" || host === "::" || host === "[::]";
26912
+ }
26913
+ function isIpLiteral(name) {
26914
+ return /^\d{1,3}(\.\d{1,3}){3}$/.test(name) || /^\[[0-9a-f:.]+\]$/i.test(name);
26915
+ }
26916
+ function controlRequestRefusal(headers, boundHost, boundPort) {
26917
+ const fetchSite = firstHeader(headers["sec-fetch-site"])?.trim().toLowerCase();
26918
+ if (fetchSite !== void 0 && fetchSite !== "same-origin" && fetchSite !== "none") return `Refusing a ${fetchSite} request to the MicroVM control endpoint: it accepts requests from this origin only (curl, or a page this front door served).`;
26919
+ const host = (Array.isArray(headers.host) ? headers.host[0] : headers.host)?.trim().toLowerCase();
26920
+ const m = host === void 0 ? null : /^(\[[^\]]+\]|[^:]+)(?::(\d+))?$/.exec(host);
26921
+ const name = m?.[1];
26922
+ const port = m ? m[2] !== void 0 ? Number(m[2]) : 80 : NaN;
26923
+ const bindName = boundHost.includes(":") && !boundHost.startsWith("[") ? `[${boundHost}]` : boundHost;
26924
+ const loopbackBind = isWildcard(boundHost) || LOOPBACK_NAMES.includes(bindName.toLowerCase());
26925
+ if (!(name !== void 0 && (name === bindName.toLowerCase() || loopbackBind && LOOPBACK_NAMES.includes(name) || isWildcard(boundHost) && isIpLiteral(name))) || port !== boundPort) return `Refusing a MicroVM control request whose Host header (${(host ?? "<none>").slice(0, 64)}) is not this front door's address.`;
26926
+ const origin = firstHeader(headers.origin)?.trim().toLowerCase();
26927
+ if (origin !== void 0 && origin !== `http://${host}`) return `Refusing a MicroVM control request from origin ${origin.slice(0, 64)}: it accepts requests from this origin only.`;
26928
+ }
26929
+ var MicrovmStateError = class MicrovmStateError extends Error {
26930
+ constructor(message) {
26931
+ super(message);
26932
+ this.name = "MicrovmStateError";
26933
+ Object.setPrototypeOf(this, MicrovmStateError.prototype);
26934
+ }
26935
+ };
26936
+ async function handleUpgrade(req, clientSocket, head, opts, track) {
26937
+ const selection = selectTargetPort(req.headers);
26938
+ if (!selection.ok) {
26939
+ writeRawHttpError(clientSocket, selection.status, selection.message);
26940
+ return;
26941
+ }
26942
+ const upstream = opts.target.upstreamFor(selection.port);
26943
+ if (!upstream) {
26944
+ writeRawHttpError(clientSocket, 403, notRoutableMessage(selection.port, opts.target));
26945
+ return;
26946
+ }
26947
+ const refusal = await admit(opts);
26948
+ if (refusal) {
26949
+ writeRawHttpError(clientSocket, refusal.status, refusal.message);
26950
+ return;
26951
+ }
26952
+ const headers = { ...req.headers };
26953
+ const offeredBase = stripMicrovmProxyHeaders(headers);
26954
+ const appProtocolsLeft = headers["sec-websocket-protocol"] !== void 0;
26955
+ const upstreamSocket = connect({
26956
+ host: upstream.host,
26957
+ port: upstream.port
26958
+ });
26959
+ track(upstreamSocket);
26960
+ upstreamSocket.setNoDelay(true);
26961
+ let connected = false;
26962
+ let responseHead = Buffer.alloc(0);
26963
+ upstreamSocket.on("connect", () => {
26964
+ connected = true;
26965
+ const lines = [`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/${req.httpVersion}`];
26966
+ for (const [name, value] of Object.entries(headers)) {
26967
+ if (value === void 0) continue;
26968
+ for (const v of Array.isArray(value) ? value : [value]) lines.push(`${name}: ${v}`);
26969
+ }
26970
+ upstreamSocket.write(`${lines.join("\r\n")}\r\n\r\n`);
26971
+ if (head.length > 0) upstreamSocket.write(head);
26972
+ clientSocket.pipe(upstreamSocket, { end: false });
26973
+ });
26974
+ const onHead = (chunk) => {
26975
+ responseHead = Buffer.concat([responseHead, chunk]);
26976
+ const end = responseHead.indexOf("\r\n\r\n");
26977
+ if (end === -1 && responseHead.length < 64 * 1024) return;
26978
+ upstreamSocket.pause();
26979
+ upstreamSocket.off("data", onHead);
26980
+ const out = end === -1 ? responseHead : Buffer.concat([Buffer.from(rewriteUpgradeResponseHead(responseHead.subarray(0, end).toString("latin1"), offeredBase && !appProtocolsLeft) + "\r\n\r\n", "latin1"), responseHead.subarray(end + 4)]);
26981
+ responseHead = void 0;
26982
+ clientSocket.write(out);
26983
+ upstreamSocket.pipe(clientSocket);
26984
+ };
26985
+ upstreamSocket.on("data", onHead);
26986
+ const closeBoth = () => {
26987
+ if (!clientSocket.destroyed) clientSocket.destroy();
26988
+ if (!upstreamSocket.destroyed) upstreamSocket.destroy();
26989
+ };
26990
+ let answered = false;
26991
+ const answer502 = (why) => {
26992
+ if (answered || clientSocket.destroyed) return;
26993
+ answered = true;
26994
+ writeRawHttpError(clientSocket, 502, `The application in MicroVM ${opts.target.microvmId} ${why} on port ${selection.port}.`);
26995
+ };
26996
+ upstreamSocket.on("end", () => clientSocket.unpipe(upstreamSocket));
26997
+ upstreamSocket.on("error", () => {
26998
+ if (!connected) return answer502("is not responding");
26999
+ if (responseHead === void 0) closeBoth();
27000
+ });
27001
+ upstreamSocket.on("close", (hadError) => {
27002
+ if (responseHead !== void 0) return answer502("closed the connection before answering the upgrade");
27003
+ if (hadError) closeBoth();
27004
+ else if (!clientSocket.writableEnded) clientSocket.end();
27005
+ });
27006
+ clientSocket.on("error", closeBoth);
27007
+ clientSocket.on("end", () => {
27008
+ if (!upstreamSocket.destroyed && !upstreamSocket.writableEnded) upstreamSocket.end();
27009
+ });
27010
+ clientSocket.on("close", () => {
27011
+ if (!upstreamSocket.destroyed) upstreamSocket.destroy();
27012
+ });
27013
+ }
27014
+ function rewriteUpgradeResponseHead(head, addBase) {
27015
+ if (!addBase) return head;
27016
+ const lines = head.split("\r\n");
27017
+ if (!/^HTTP\/1\.[01] 101\b/.test(lines[0] ?? "")) return head;
27018
+ if (lines.some((l) => /^sec-websocket-protocol\s*:/i.test(l))) return head;
27019
+ return [...lines, `Sec-WebSocket-Protocol: ${MICROVM_WS_BASE_PROTOCOL}`].join("\r\n");
27020
+ }
27021
+ function notRoutableMessage(port, target) {
27022
+ return `Port ${port} of MicroVM ${target.microvmId} is not reachable locally. cdk-local publishes 8080, Hooks.Port, the image's EXPOSE ports and every --proxy-port; published: ${target.routablePorts().join(", ")}.`;
27023
+ }
27024
+ function writeText(res, status, message) {
27025
+ res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
27026
+ res.end(`${message}\n`);
27027
+ }
27028
+ function writeJson(res, status, body) {
27029
+ res.writeHead(status, { "content-type": "application/json" });
27030
+ res.end(JSON.stringify(body));
27031
+ }
27032
+
27033
+ //#endregion
27034
+ //#region src/local/microvm-runner.ts
27035
+ const execFileAsync = promisify$1(execFile$1);
27036
+ const DEFAULT_TERMINATE_HOOK_TIMEOUT_MS = 1e4;
27037
+ function microvmImageTag(artifact) {
27038
+ return `${getEmbedConfig().resourceNamePrefix}-microvm-${artifact.assetHash.slice(0, 16).toLowerCase()}`;
27039
+ }
27040
+ async function buildMicrovmImage(opts) {
27041
+ const tag = microvmImageTag(opts.artifact);
27042
+ const parent = dirname$1(opts.artifact.contextDir);
27043
+ getLogger().info(`Building MicroVM image ${tag}${opts.platform ? ` (platform=${opts.platform})` : ""}...`);
27044
+ await buildDockerImage({ source: {
27045
+ directory: basename$1(opts.artifact.contextDir),
27046
+ ...opts.noCache && { cacheDisabled: true }
27047
+ } }, parent, {
27048
+ tag,
27049
+ ...opts.platform !== void 0 && { platform: opts.platform },
27050
+ assetOutdir: parent,
27051
+ progressLabel: "Building MicroVM image",
27052
+ wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for the MicroVM image's Dockerfile: ${stderr}`)
27053
+ });
27054
+ return tag;
27055
+ }
27056
+ async function readExposedPorts(image) {
25752
27057
  try {
25753
27058
  const { stdout } = await execFileAsync(getDockerCmd(), [
27059
+ "image",
25754
27060
  "inspect",
25755
27061
  "-f",
25756
- "{{.State.Running}}",
25757
- containerId
27062
+ "{{json .Config.ExposedPorts}}",
27063
+ image
25758
27064
  ]);
25759
- return stdout.trim() === "true";
27065
+ const parsed = JSON.parse(stdout.trim() || "null");
27066
+ if (!parsed) return [];
27067
+ return Object.keys(parsed).filter((k) => k.endsWith("/tcp") || !k.includes("/")).map((k) => Number(k.split("/")[0])).filter((n) => Number.isInteger(n) && n > 0 && n < 65536);
25760
27068
  } catch {
25761
- return false;
27069
+ return [];
25762
27070
  }
25763
27071
  }
25764
- function readContainerLogTail(containerId, lines, spawnFn = spawn$1) {
25765
- return new Promise((resolveLogs, rejectLogs) => {
25766
- const proc = spawnFn(getDockerCmd(), [
25767
- "logs",
25768
- "--tail",
25769
- String(lines),
25770
- containerId
25771
- ], { stdio: [
25772
- "ignore",
25773
- "pipe",
25774
- "pipe"
25775
- ] });
25776
- const chunks = [];
25777
- let size = 0;
25778
- const take = (c) => {
25779
- if (size > 4 * 1024 * 1024) return;
25780
- chunks.push(c);
25781
- size += c.length;
27072
+ function planMicrovmPorts(hooksPort, exposed, extra) {
27073
+ const out = [MICROVM_DEFAULT_INGRESS_PORT];
27074
+ for (const p of [
27075
+ ...hooksPort !== void 0 ? [hooksPort] : [],
27076
+ ...exposed,
27077
+ ...extra
27078
+ ]) if (!out.includes(p)) out.push(p);
27079
+ return out;
27080
+ }
27081
+ async function setContainerPaused(containerId, paused) {
27082
+ try {
27083
+ await execFileAsync(getDockerCmd(), [paused ? "pause" : "unpause", containerId]);
27084
+ } catch (error) {
27085
+ const err = error;
27086
+ throw new CdkLocalError(`docker ${paused ? "pause" : "unpause"} failed: ${displayToolOutput(err.stderr?.trim() || err.message || String(error))}`, "LOCAL_START_MICROVM_PAUSE_FAILED");
27087
+ }
27088
+ }
27089
+ const defaultDeps = {
27090
+ setPaused: setContainerPaused,
27091
+ remove: removeContainer
27092
+ };
27093
+ var LocalMicrovm = class {
27094
+ microvmId;
27095
+ containerId;
27096
+ current = "booting";
27097
+ resuming;
27098
+ suspending;
27099
+ paused = false;
27100
+ terminating;
27101
+ opts;
27102
+ deps;
27103
+ constructor(opts, deps = {}) {
27104
+ this.opts = opts;
27105
+ this.deps = {
27106
+ ...defaultDeps,
27107
+ ...deps
25782
27108
  };
25783
- proc.stdout?.on("data", take);
25784
- proc.stderr?.on("data", take);
25785
- proc.on("error", rejectLogs);
25786
- proc.on("close", (code) => {
25787
- if (code !== 0) {
25788
- rejectLogs(/* @__PURE__ */ new Error(`docker logs exited ${code}`));
25789
- return;
27109
+ this.microvmId = opts.microvmId;
27110
+ this.containerId = opts.containerId;
27111
+ }
27112
+ state() {
27113
+ return this.current === "booting" ? "resuming" : this.current;
27114
+ }
27115
+ upstreamFor(containerPort) {
27116
+ return this.opts.ports.get(containerPort);
27117
+ }
27118
+ routablePorts() {
27119
+ return [...this.opts.ports.keys()];
27120
+ }
27121
+ log(message) {
27122
+ getLogger().info(`MicroVM ${this.microvmId}: ${message}`);
27123
+ }
27124
+ hookEndpoint() {
27125
+ const port = this.opts.config.hooksPort;
27126
+ const endpoint = port !== void 0 ? this.opts.ports.get(port) : void 0;
27127
+ if (!endpoint) throw new CdkLocalError("The hooks port is not published.", "LOCAL_START_MICROVM_HOOK_FAILED");
27128
+ return endpoint;
27129
+ }
27130
+ hookTimeoutMs(hook) {
27131
+ const seconds = this.opts.config.hooks[hook]?.timeoutSeconds;
27132
+ if (seconds !== void 0) return seconds * 1e3;
27133
+ return hook === "terminate" ? DEFAULT_TERMINATE_HOOK_TIMEOUT_MS : this.opts.timeoutMs;
27134
+ }
27135
+ hookDeps() {
27136
+ return {
27137
+ ...this.deps.hooks ?? defaultHookDeps,
27138
+ ...this.deps.isAlive && { isAlive: this.deps.isAlive }
27139
+ };
27140
+ }
27141
+ async runtimeHook(hook, body) {
27142
+ if (!this.opts.config.hooks[hook]) return false;
27143
+ await callRuntimeHook(hook, this.hookEndpoint(), this.hookTimeoutMs(hook), body, this.hookDeps());
27144
+ this.log(`/${hook} hook answered 200`);
27145
+ return true;
27146
+ }
27147
+ async boot(runHookPayload) {
27148
+ const { hooks } = this.opts.config;
27149
+ const deps = this.hookDeps();
27150
+ if (hooks.ready) {
27151
+ const attempts = await pollBuildHook("ready", this.hookEndpoint(), this.hookTimeoutMs("ready"), deps);
27152
+ this.log(`/ready hook answered 200 after ${attempts} attempt(s)`);
27153
+ } else {
27154
+ const port = this.opts.config.hooksPort ?? 8080;
27155
+ const endpoint = this.opts.ports.get(port);
27156
+ await (this.deps.waitForAnswer ?? waitForAppAnswer)(endpoint, this.opts.timeoutMs, {
27157
+ probe: probeHttp,
27158
+ sleep: deps.sleep,
27159
+ now: deps.now,
27160
+ ...this.deps.isAlive && { isAlive: this.deps.isAlive }
27161
+ });
27162
+ this.log("application answered HTTP (no /ready hook)");
27163
+ }
27164
+ if (hooks.validate) {
27165
+ const attempts = await pollBuildHook("validate", this.hookEndpoint(), this.hookTimeoutMs("validate"), deps);
27166
+ this.log(`/validate hook answered 200 after ${attempts} attempt(s)`);
27167
+ }
27168
+ await this.runtimeHook("run", runHookBody(this.microvmId, runHookPayload));
27169
+ if (this.current === "booting") this.current = "running";
27170
+ }
27171
+ async setPaused(paused) {
27172
+ await this.deps.setPaused(this.containerId, paused);
27173
+ this.paused = paused;
27174
+ }
27175
+ suspend() {
27176
+ if (this.current !== "running") return Promise.reject(new MicrovmStateError(`MicroVM ${this.microvmId} is ${this.state()}; only a running MicroVM can be suspended.`));
27177
+ this.current = "suspending";
27178
+ this.suspending = (async () => {
27179
+ try {
27180
+ await this.runtimeHook("suspend");
27181
+ await this.setPaused(true);
27182
+ } catch (err) {
27183
+ if (this.current === "suspending") this.current = "running";
27184
+ throw err;
27185
+ } finally {
27186
+ this.suspending = void 0;
25790
27187
  }
25791
- resolveLogs(Buffer.concat(chunks).toString().split("\n").map((l) => l.replace(/\r$/, "")).filter((l) => l.length > 0).slice(-lines));
25792
- });
25793
- });
25794
- }
25795
- const defaultReadyDeps = {
25796
- probe: probeHttp,
25797
- isRunning: containerIsRunning,
25798
- fetchLogs: readContainerLogTail,
25799
- sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
25800
- now: () => Date.now()
27188
+ if (this.current === "suspending") {
27189
+ this.current = "suspended";
27190
+ this.log("suspended (container paused)");
27191
+ }
27192
+ })();
27193
+ return this.suspending;
27194
+ }
27195
+ resume() {
27196
+ if (this.current === "resuming" && this.resuming) return this.resuming;
27197
+ if (this.current === "suspending" && this.suspending) return this.suspending.then(() => this.resume(), () => void 0);
27198
+ if (this.current !== "suspended") return Promise.reject(new MicrovmStateError(`MicroVM ${this.microvmId} is ${this.state()}; only a suspended MicroVM can be resumed.`));
27199
+ this.current = "resuming";
27200
+ this.resuming = (async () => {
27201
+ try {
27202
+ await this.setPaused(false);
27203
+ try {
27204
+ await this.runtimeHook("resume");
27205
+ } catch (err) {
27206
+ if (this.current === "resuming") await this.setPaused(true).catch(() => void 0);
27207
+ throw err;
27208
+ }
27209
+ } catch (err) {
27210
+ if (this.current === "resuming") this.current = "suspended";
27211
+ throw err;
27212
+ } finally {
27213
+ this.resuming = void 0;
27214
+ }
27215
+ if (this.current === "resuming") {
27216
+ this.current = "running";
27217
+ this.log("resumed");
27218
+ }
27219
+ })();
27220
+ return this.resuming;
27221
+ }
27222
+ terminate() {
27223
+ this.terminating ??= (async () => {
27224
+ const wasBooting = this.current === "booting";
27225
+ this.current = "terminating";
27226
+ await Promise.allSettled([this.suspending, this.resuming].filter((p) => p !== void 0));
27227
+ try {
27228
+ if (this.paused) await this.setPaused(false).catch(() => void 0);
27229
+ if (!wasBooting) await this.runtimeHook("terminate");
27230
+ } catch (err) {
27231
+ getLogger().warn(`MicroVM ${this.microvmId}: ${err instanceof Error ? err.message : String(err)} Removing its container anyway.`);
27232
+ }
27233
+ await this.deps.remove(this.containerId);
27234
+ this.current = "terminated";
27235
+ this.log("terminated (container removed)");
27236
+ this.opts.onTerminated?.(this);
27237
+ })();
27238
+ return this.terminating;
27239
+ }
25801
27240
  };
25802
27241
 
25803
27242
  //#endregion
25804
- //#region src/cli/commands/local-start-web-function.ts
27243
+ //#region src/cli/commands/local-start-microvm.ts
25805
27244
  function parsePort(raw) {
25806
27245
  const n = Number(raw);
25807
27246
  if (!Number.isInteger(n) || n < 0 || n > 65535) throw new InvalidArgumentError(`--port must be an integer 0-65535, got '${raw}'.`);
25808
27247
  return n;
25809
27248
  }
27249
+ function parseCount(raw) {
27250
+ const n = Number(raw);
27251
+ if (!Number.isInteger(n) || n < 1 || n > 64) throw new InvalidArgumentError(`--count must be an integer 1-64, got '${raw}'.`);
27252
+ return n;
27253
+ }
27254
+ function parseProxyPort(raw, previous) {
27255
+ const n = Number(raw);
27256
+ if (!Number.isInteger(n) || n < 1 || n > 65535) throw new InvalidArgumentError(`--proxy-port must be an integer 1-65535, got '${raw}'.`);
27257
+ return [...previous ?? [], n];
27258
+ }
25810
27259
  function connectHost(host) {
25811
27260
  if (host === "0.0.0.0") return "127.0.0.1";
25812
27261
  if (host === "::" || host === "[::]") return "::1";
25813
27262
  return host;
25814
27263
  }
25815
- async function localStartWebFunctionCommand(target, options, extraStateProviders, hostSynthesizer) {
27264
+ function readRunHookPayload(options, readFile = (p) => readFileSync$1(p, "utf-8")) {
27265
+ if (options.runHookPayload !== void 0 && options.runHookPayloadFile !== void 0) throw new CdkLocalError("--run-hook-payload and --run-hook-payload-file are mutually exclusive.", "LOCAL_START_MICROVM_PAYLOAD_CONFLICT");
27266
+ let payload = options.runHookPayload;
27267
+ if (options.runHookPayloadFile !== void 0) try {
27268
+ payload = readFile(options.runHookPayloadFile);
27269
+ } catch (err) {
27270
+ throw new CdkLocalError(`Cannot read --run-hook-payload-file ${displayUntrustedValue(options.runHookPayloadFile)}: ${err instanceof Error ? err.message : String(err)}`, "LOCAL_START_MICROVM_PAYLOAD_UNREADABLE");
27271
+ }
27272
+ if (payload !== void 0 && Buffer.byteLength(payload) > 16384) throw new CdkLocalError(`The run hook payload is ${Buffer.byteLength(payload)} bytes; run-microvm accepts at most ${MAX_RUN_HOOK_PAYLOAD_BYTES} (16 KB).`, "LOCAL_START_MICROVM_PAYLOAD_TOO_LARGE");
27273
+ return payload;
27274
+ }
27275
+ function buildMicrovmLambdaView(resolved, config) {
27276
+ return {
27277
+ kind: "zip",
27278
+ stack: resolved.stack,
27279
+ logicalId: resolved.logicalId,
27280
+ resource: {
27281
+ Type: resolved.resource.Type,
27282
+ Properties: { Environment: { Variables: config.environment } },
27283
+ ...resolved.resource.Metadata !== void 0 && { Metadata: resolved.resource.Metadata }
27284
+ },
27285
+ memoryMb: 128,
27286
+ timeoutSec: 0,
27287
+ layers: [],
27288
+ runtime: "",
27289
+ handler: "",
27290
+ codePath: null,
27291
+ architecture: config.architecture ?? "arm64"
27292
+ };
27293
+ }
27294
+ const LAMBDA_RUNTIME_ENV_KEYS = [
27295
+ "AWS_LAMBDA_FUNCTION_NAME",
27296
+ "AWS_LAMBDA_FUNCTION_MEMORY_SIZE",
27297
+ "AWS_LAMBDA_FUNCTION_TIMEOUT",
27298
+ "AWS_LAMBDA_FUNCTION_VERSION",
27299
+ "AWS_LAMBDA_LOG_GROUP_NAME",
27300
+ "AWS_LAMBDA_LOG_STREAM_NAME"
27301
+ ];
27302
+ function finalizeMicrovmEnv(lambdaEnv, declared) {
27303
+ const env = { ...lambdaEnv };
27304
+ for (const key of LAMBDA_RUNTIME_ENV_KEYS) if (!Object.hasOwn(declared, key)) delete env[key];
27305
+ return env;
27306
+ }
27307
+ function microvmFidelityWarnings(config) {
27308
+ const image = displayIdent(config.logicalId);
27309
+ const out = [
27310
+ `MicroVM image ${image}: each MicroVM is a Docker container started fresh from the built image, not a MicroVM restored from a snapshot — state the app initializes before /ready answers 200 is NOT shared across MicroVMs (each one runs its own startup), and there is no VM isolation or memory checkpoint (suspend is docker pause).`,
27311
+ `MicroVM image ${image}: requests are accepted without X-aws-proxy-auth — the JWE token the cloud endpoint requires is not checked locally (the header is still stripped).`,
27312
+ `MicroVM image ${image}: the idle policy is not emulated — a MicroVM suspends, resumes and terminates only through ${MICROVM_CONTROL_PATH}/suspend|resume|terminate (and --auto-resume).`,
27313
+ `MicroVM image ${image}: the Dockerfile is built by the local Docker engine, not on the Lambda-managed MicroVM base OS (BaseImageArn).`
27314
+ ];
27315
+ if (config.hooks.validate) out.push(`MicroVM image ${image}: /validate is called on the same container as /ready (the cloud calls it on a fresh MicroVM started from the built snapshot).`);
27316
+ for (const what of config.unemulated) out.push(`MicroVM image ${image}: ${what} is not emulated.`);
27317
+ return out;
27318
+ }
27319
+ async function localStartMicrovmCommand(target, options, extraStateProviders, hostSynthesizer) {
25816
27320
  const logger = getLogger();
25817
27321
  if (options.verbose) logger.setLevel("debug");
25818
- let containerId;
25819
- let stopLogs;
25820
- let exitWatch;
25821
- let artifact;
27322
+ const runHookPayload = readRunHookPayload(options);
27323
+ const vms = [];
27324
+ const frontDoors = /* @__PURE__ */ new Map();
27325
+ const logStops = [];
27326
+ const exitWatches = [];
27327
+ const bareContainers = /* @__PURE__ */ new Set();
25822
27328
  let runInFlight;
27329
+ let containerName;
27330
+ let artifact;
25823
27331
  let teardownPromise;
25824
27332
  let shuttingDown = false;
25825
27333
  let waitingOnRun = false;
25826
- let containerName;
25827
27334
  const forever = () => new Promise(() => void 0);
25828
27335
  const doTeardown = async () => {
25829
- exitWatch?.stop();
25830
- if (containerId === void 0 && runInFlight !== void 0) {
27336
+ for (const w of exitWatches) w.stop();
27337
+ if (runInFlight !== void 0) {
25831
27338
  waitingOnRun = true;
25832
27339
  try {
25833
- containerId = await runInFlight;
27340
+ bareContainers.add(await runInFlight);
25834
27341
  } catch {} finally {
25835
27342
  waitingOnRun = false;
25836
27343
  }
25837
27344
  }
25838
- if (stopLogs) try {
25839
- stopLogs();
27345
+ await Promise.allSettled(vms.map((vm) => vm.terminate()));
27346
+ for (const vm of vms) bareContainers.delete(vm.containerId);
27347
+ await Promise.allSettled([...bareContainers].map((id) => removeContainer(id)));
27348
+ await Promise.allSettled([...frontDoors.values()].map((fd) => fd.close()));
27349
+ for (const stop of logStops) try {
27350
+ stop();
25840
27351
  } catch (err) {
25841
27352
  logger.debug(`streamLogs stop failed: ${err instanceof Error ? err.message : String(err)}`);
25842
27353
  }
25843
- if (containerId) await removeContainer(containerId);
25844
- disposeWebFunctionArtifact(artifact);
27354
+ disposeMicrovmCodeArtifact(artifact);
25845
27355
  };
25846
27356
  const teardown = () => teardownPromise ??= doTeardown();
25847
27357
  await applyRoleArnIfSet({
@@ -25864,107 +27374,155 @@ async function localStartWebFunctionCommand(target, options, extraStateProviders
25864
27374
  ...Object.keys(context).length > 0 && { context }
25865
27375
  };
25866
27376
  const { stacks } = await synthesizer.synthesize(synthOpts);
25867
- const resolved = resolveWebFunctionTarget(await resolveSingleTarget(target, {
25868
- entries: listTargets(stacks).webFunctions,
25869
- message: "Select a Lambda web function to serve",
25870
- noun: "Lambda web functions",
25871
- onMissing: () => new CdkLocalError(`${getEmbedConfig().cliName} start-web-function requires a <target> (a WebFunction, WebFunctionEndpoint or WebFunctionRevision display path or logical ID). Run \`${getEmbedConfig().cliName} list\` to see them, or run it in a TTY to pick interactively.`, "LOCAL_START_WEB_FUNCTION_TARGET_REQUIRED")
25872
- }), stacks, options.revision);
25873
- const code = parseWebFunctionBuildConfig(resolved.revisionLogicalId, resolved.revision.Properties?.["BuildConfig"]);
25874
- const spec = resolveWebFunctionRuntime(code.runtime);
25875
- const selectionNote = {
25876
- "target-revision": "the target revision",
25877
- "revision-flag": "--revision",
25878
- "endpoint-weight": `highest weight on endpoint ${displayIdent(resolved.endpoint?.logicalId ?? "")}`,
25879
- "only-revision": "the function's only revision"
25880
- };
25881
- logger.info(`Target: ${displayStackName(`${resolved.stack.stackName}/${resolved.revisionLogicalId}`)} (${spec.runtime}; revision chosen by ${selectionNote[resolved.selection]})`);
25882
- const serviceConfig = readWebFunctionServiceConfig(resolved.revision);
25883
- for (const line of webFunctionFidelityWarnings(resolved, serviceConfig)) logger.warn(line);
25884
- artifact = await resolveWebFunctionArtifact(resolved.stack, resolved.revisionLogicalId, code);
27377
+ const resolved = resolveMicrovmImageTarget(await resolveSingleTarget(target, {
27378
+ entries: listTargets(stacks).microvmImages,
27379
+ message: "Select a Lambda MicroVM image to run",
27380
+ noun: "Lambda MicroVM images",
27381
+ onMissing: () => new CdkLocalError(`${getEmbedConfig().cliName} start-microvm requires a <target> (a MicrovmImage display path or logical ID). Run \`${getEmbedConfig().cliName} list\` to see them, or run it in a TTY to pick interactively.`, "LOCAL_START_MICROVM_TARGET_REQUIRED")
27382
+ }), stacks);
27383
+ const config = readMicrovmImageConfig(resolved.logicalId, resolved.resource);
27384
+ const platform = options.platform ?? (config.architecture !== void 0 ? architectureToPlatform(config.architecture) : void 0);
27385
+ logger.info(`Target: ${displayStackName(`${resolved.stack.stackName}/${resolved.logicalId}`)} (MicroVM image ${displayIdent(config.name)}; hooks: ${Object.keys(config.hooks).join(", ") || "none"}${platform ? `; ${platform}` : ""})`);
27386
+ for (const line of microvmFidelityWarnings(config)) logger.warn(line);
27387
+ artifact = await resolveMicrovmCodeArtifact(resolved.stack, config);
25885
27388
  const shutdown = async (signal, exitCode) => {
25886
27389
  if (shuttingDown) {
25887
27390
  if (!waitingOnRun) {
25888
- logger.info(`Received ${signal} again; still removing the container, please wait...`);
27391
+ logger.info(`Received ${signal} again; still terminating the MicroVMs, please wait...`);
25889
27392
  return;
25890
27393
  }
25891
- disposeWebFunctionArtifact(artifact);
27394
+ disposeMicrovmCodeArtifact(artifact);
25892
27395
  logger.warn(`Received ${signal} again; exiting without waiting for docker run to return. The container may still be starting, and its environment holds the forwarded AWS credentials. Remove it by its name (this works even if it is created after this exit): docker rm -f ${containerName ?? "<name>"}`);
25893
27396
  process.exit(signal === "SIGINT" ? 130 : 143);
25894
27397
  return;
25895
27398
  }
25896
27399
  shuttingDown = true;
25897
- logger.info(`Received ${signal}, shutting down...`);
27400
+ logger.info(`Received ${signal}, terminating ${vms.length} MicroVM(s)...`);
25898
27401
  await teardown();
25899
27402
  process.exit(exitCode);
25900
27403
  };
25901
27404
  process.on("SIGINT", () => void shutdown("SIGINT", 130));
25902
27405
  process.on("SIGTERM", () => void shutdown("SIGTERM", 0));
25903
- const recent = new RecentOutput();
27406
+ const recents = /* @__PURE__ */ new Map();
27407
+ let allTerminated;
27408
+ const everyVmTerminated = new Promise((r) => allTerminated = r);
27409
+ const checkAllTerminated = () => {
27410
+ if (vms.length === options.count && vms.every((v) => v.state() === "terminated")) allTerminated();
27411
+ };
27412
+ let unexpectedExit;
27413
+ const anyUnexpectedExit = new Promise((r) => unexpectedExit = r);
27414
+ const probeHost = connectHost(options.host);
25904
27415
  try {
25905
- const assumeRole = webFunctionAssumeRole(options.assumeRole, serviceConfig);
25906
- const containerEnv = await resolveLambdaContainerEnv(buildWebFunctionLambdaView(resolved, serviceConfig, spec.runtime), {
27416
+ const containerEnv = await resolveLambdaContainerEnv(buildMicrovmLambdaView(resolved, config), {
25907
27417
  ...options,
25908
- ...assumeRole !== void 0 && { assumeRole },
25909
27418
  deployedFunctionLookups: false
25910
27419
  }, profileCredentials, extraStateProviders);
25911
- const { env, replacedHttpEndpoint } = finalizeWebFunctionEnv(containerEnv.env, {
25912
- functionName: resolved.functionName,
25913
- containerPort: WEB_FUNCTION_CONTAINER_PORT,
25914
- timeoutSeconds: serviceConfig.timeoutSeconds,
25915
- logGroup: serviceConfig.logGroup
25916
- });
25917
- if (replacedHttpEndpoint) logger.warn(`The revision's ${WEB_FUNCTION_HTTP_ENDPOINT_ENV} is replaced with 0.0.0.0:${WEB_FUNCTION_CONTAINER_PORT}: the runtime owns that variable, and the local port is published to it.`);
25918
- const plan = planWebFunctionContainer(artifact, spec, env);
25919
- const platform = options.platform ?? defaultWebFunctionPlatform(spec);
25920
- await pullImage(plan.image, options.pull === false, platform);
25921
- if (teardownPromise) return await forever();
25922
- const hostPort = options.port === 0 ? await pickFreePort() : options.port;
25923
- const name = containerName = `${getEmbedConfig().resourceNamePrefix}-webfn-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
25924
- logger.info(`Starting web function container (image=${plan.image}, start=${plan.label}, port=${hostPort} -> ${WEB_FUNCTION_CONTAINER_PORT})...`);
25925
- runInFlight = startWebFunctionContainer({
25926
- plan,
25927
- env,
25928
- sensitiveEnvKeys: new Set(containerEnv.sensitiveEnvKeys),
25929
- host: options.host,
25930
- hostPort,
27420
+ const env = finalizeMicrovmEnv(containerEnv.env, config.environment);
27421
+ const image = await buildMicrovmImage({
27422
+ artifact,
25931
27423
  platform,
25932
- name
27424
+ noCache: options.buildCache === false
25933
27425
  });
25934
- const id = await runInFlight;
25935
27426
  if (teardownPromise) return await forever();
25936
- containerId = id;
25937
- stopLogs = streamLogs(id, { onOutput: (chunk) => recent.push(chunk) });
25938
- exitWatch = watchContainerExit(id);
25939
- const probeHost = connectHost(options.host);
25940
- await waitForWebFunctionReady({
25941
- containerId: id,
25942
- host: probeHost,
25943
- port: hostPort,
25944
- timeoutMs: options.timeout,
25945
- recent,
25946
- label: plan.label
25947
- });
25948
- logger.info(`Server listening on http://${formatAuthority(probeHost, hostPort)} (${displayIdent(resolved.functionName)} (Lambda web function))`);
25949
- logger.info("Press ^C to shut down.");
27427
+ const containerPorts = planMicrovmPorts(config.hooksPort, await readExposedPorts(image), options.proxyPort ?? []);
27428
+ logger.info(`Publishing container ports ${containerPorts.join(", ")} to the front door(s).`);
27429
+ for (let i = 0; i < options.count; i++) {
27430
+ const microvmId = generateMicrovmId();
27431
+ const hostPorts = /* @__PURE__ */ new Map();
27432
+ for (const cp of containerPorts) hostPorts.set(cp, {
27433
+ host: "127.0.0.1",
27434
+ port: await pickFreePort()
27435
+ });
27436
+ const [first, ...rest] = containerPorts;
27437
+ const name = containerName = `${getEmbedConfig().resourceNamePrefix}-microvm-${process.pid}-${i}-${Math.random().toString(36).slice(2, 8)}`;
27438
+ logger.info(`Starting MicroVM ${microvmId} (container ${name})...`);
27439
+ runInFlight = runDetached({
27440
+ image,
27441
+ mounts: [],
27442
+ env,
27443
+ ...containerEnv.sensitiveEnvKeys.length > 0 && { sensitiveEnvKeys: new Set(containerEnv.sensitiveEnvKeys) },
27444
+ cmd: [],
27445
+ hostPort: hostPorts.get(first).port,
27446
+ containerPort: first,
27447
+ extraPorts: rest.map((cp) => ({
27448
+ hostPort: hostPorts.get(cp).port,
27449
+ containerPort: cp
27450
+ })),
27451
+ host: "127.0.0.1",
27452
+ ...platform !== void 0 && { platform },
27453
+ name,
27454
+ autoRemove: false
27455
+ });
27456
+ const containerId = await runInFlight;
27457
+ runInFlight = void 0;
27458
+ bareContainers.add(containerId);
27459
+ if (teardownPromise) return await forever();
27460
+ const recent = new RecentOutput();
27461
+ recents.set(containerId, recent);
27462
+ logStops.push(streamLogs(containerId, {
27463
+ onOutput: (chunk) => recent.push(chunk),
27464
+ ...options.count > 1 && { prefix: `[${microvmId.slice(0, 12)}] ` }
27465
+ }));
27466
+ const watch = watchContainerExit(containerId);
27467
+ exitWatches.push(watch);
27468
+ let exited = false;
27469
+ const vm = new LocalMicrovm({
27470
+ microvmId,
27471
+ containerId,
27472
+ ports: hostPorts,
27473
+ config,
27474
+ timeoutMs: options.timeout,
27475
+ onTerminated: checkAllTerminated
27476
+ }, { isAlive: async () => !exited });
27477
+ vms.push(vm);
27478
+ bareContainers.delete(containerId);
27479
+ watch.exited.then((code) => {
27480
+ exited = true;
27481
+ const s = vm.state();
27482
+ if (s !== "terminating" && s !== "terminated") unexpectedExit({
27483
+ vm,
27484
+ code
27485
+ });
27486
+ });
27487
+ await vm.boot(runHookPayload);
27488
+ if (teardownPromise) return await forever();
27489
+ const fd = await startMicrovmFrontDoor({
27490
+ host: options.host,
27491
+ port: options.port === 0 ? 0 : options.port + i,
27492
+ target: vm,
27493
+ autoResume: options.autoResume === true
27494
+ });
27495
+ frontDoors.set(vm, fd);
27496
+ const base = `http://${formatAuthority(probeHost, fd.port)}`;
27497
+ logger.info(`MicroVM ${microvmId} listening on ${base} (control: ${base}${MICROVM_CONTROL_PATH})`);
27498
+ }
27499
+ logger.info("Press ^C to terminate the MicroVM(s).");
27500
+ checkAllTerminated();
25950
27501
  } catch (err) {
25951
27502
  if (shuttingDown) return await forever();
27503
+ const failed = vms.at(-1);
27504
+ const wrapped = err instanceof MicrovmHookError && failed ? await containerFailure(err.message, failed.containerId, recents.get(failed.containerId) ?? new RecentOutput()).then((f) => new CdkLocalError(f.message, err.code)) : err;
25952
27505
  await teardown();
25953
- throw err;
27506
+ throw wrapped;
27507
+ }
27508
+ const outcome = await Promise.race([everyVmTerminated.then(() => void 0), anyUnexpectedExit]);
27509
+ if (shuttingDown) return await forever();
27510
+ if (outcome === void 0) {
27511
+ logger.info("Every MicroVM is terminated; shutting down.");
27512
+ await teardown();
27513
+ return;
25954
27514
  }
25955
- if (!exitWatch || !containerId) return await forever();
25956
- const exitCode = await exitWatch.exited;
25957
- if (shuttingDown || teardownPromise) return await forever();
25958
- const failure = await containerFailure(`The web function container exited (exit code ${exitCode ?? "unknown"}) while serving.`, containerId, recent, void 0, "LOCAL_START_WEB_FUNCTION_EXITED");
27515
+ if (teardownPromise) return await forever();
27516
+ const failure = await containerFailure(`MicroVM ${outcome.vm.microvmId}'s container exited (exit code ${outcome.code ?? "unknown"}) while running.`, outcome.vm.containerId, recents.get(outcome.vm.containerId) ?? new RecentOutput());
25959
27517
  await teardown();
25960
- throw failure;
27518
+ throw new CdkLocalError(failure.message, "LOCAL_START_MICROVM_EXITED");
25961
27519
  }
25962
- function createLocalStartWebFunctionCommand(opts = {}) {
27520
+ function createLocalStartMicrovmCommand(opts = {}) {
25963
27521
  setEmbedConfig(opts.embedConfig);
25964
- const cmd = new Command("start-web-function").description("Serve an AWS Lambda Web Function locally. Resolves a WebFunction, WebFunctionEndpoint or WebFunctionRevision target to one revision (an endpoint serves its highest-weight revision; --revision overrides), runs its code bundle from cdk.out in a container whose server listens on AWS_LAMBDA_HTTP_ENDPOINT, and publishes it on a local port with the revision's ServiceConfig environment. Target accepts a CDK display path or stack-qualified logical ID; single-stack apps may omit the prefix. Omit <target> in a TTY to pick from a list. Runs until ^C.").argument("[target]", "CDK display path or stack-qualified logical ID of the WebFunction / WebFunctionEndpoint / WebFunctionRevision (omit to pick interactively in a TTY)").action(withErrorHandling(async (target, options) => {
25965
- await localStartWebFunctionCommand(target, options, opts.extraStateProviders, opts.synthesizer);
27522
+ const cmd = new Command("start-microvm").description("Run AWS Lambda MicroVMs locally. Builds a MicrovmImage code artifact (a Dockerfile plus the app, from cdk.out) once, starts --count containers with its EnvironmentVariables, drives the /ready, /validate and /run lifecycle hooks in the cloud order, and serves each MicroVM on its own local URL: X-aws-proxy-port / lambda-microvms.port.N routing (default 8080), X-aws-proxy-* stripping, and a control endpoint for suspend / resume / terminate. Target accepts a CDK display path or stack-qualified logical ID; single-stack apps may omit the prefix. Omit <target> in a TTY to pick from a list. Runs until ^C, which calls /terminate.").argument("[target]", "CDK display path or stack-qualified logical ID of the MicrovmImage (omit to pick interactively in a TTY)").action(withErrorHandling(async (target, options) => {
27523
+ await localStartMicrovmCommand(target, options, opts.extraStateProviders, opts.synthesizer);
25966
27524
  }));
25967
- addStartWebFunctionSpecificOptions(cmd);
27525
+ addStartMicrovmSpecificOptions(cmd);
25968
27526
  [
25969
27527
  ...commonOptions(),
25970
27528
  ...appOptions(),
@@ -25973,8 +27531,8 @@ function createLocalStartWebFunctionCommand(opts = {}) {
25973
27531
  cmd.addOption(regionOption);
25974
27532
  return cmd;
25975
27533
  }
25976
- function addStartWebFunctionSpecificOptions(cmd) {
25977
- return cmd.addOption(new Option("--revision <logicalId>", "Serve this WebFunctionRevision (logical ID) instead of the one the endpoint weights pick.")).addOption(new Option("--port <n>", "Local port to serve on. Default 0 (OS-assigned).").default(0).argParser(parsePort)).addOption(new Option("--host <ip>", "Address to publish the port on.").default("127.0.0.1")).addOption(new Option("--platform <platform>", "docker --platform for the server container. Defaults to the runtime's cloud architecture (linux/arm64 for nodejs24.x).").choices(["linux/amd64", "linux/arm64"])).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"RevisionLogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--no-pull", "Skip docker pull of the runtime base image (use the cached image)")).addOption(new Option("--timeout <ms>", "Maximum time in milliseconds to wait for the server to answer HTTP. Default 60000.").default(6e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the revision's execution role and forward STS-issued temp credentials to the container. (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the revision's ServiceConfig.ExecutionRoleArn: a literal ARN as written, or the Fn::GetAtt / Ref CDK synthesizes resolved against --from-cfn-stack deployed state; (3) `--no-assume-role` opts out. Off by default.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. Bare form uses the resolved stack name.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
27534
+ function addStartMicrovmSpecificOptions(cmd) {
27535
+ return cmd.addOption(new Option("--count <n>", "Number of independent MicroVMs to run, each with its own URL.").default(1).argParser(parseCount)).addOption(new Option("--run-hook-payload <string>", "The runHookPayload string delivered to each MicroVM's /run hook (max 16 KB).")).addOption(new Option("--run-hook-payload-file <path>", "Read the runHookPayload from a file (mutually exclusive with --run-hook-payload).")).addOption(new Option("--port <n>", "Local port of the first MicroVM; MicroVM i listens on <n>+i. Default 0 (OS-assigned).").default(0).argParser(parsePort)).addOption(new Option("--host <ip>", "Address the MicroVM URLs listen on.").default("127.0.0.1")).addOption(new Option("--platform <platform>", "docker --platform for the build and the containers. Defaults to the image's CpuConfigurations architecture.").choices(["linux/amd64", "linux/arm64"])).addOption(new Option("--proxy-port <port>", "An extra container port X-aws-proxy-port may route to (repeatable). 8080, Hooks.Port and the image's EXPOSE ports are always routable.").argParser(parseProxyPort)).addOption(new Option("--auto-resume", "Resume a suspended MicroVM on its next request instead of answering 503 (autoResumeEnabled).")).addOption(new Option("--no-build-cache", "Build the image with docker build --no-cache.")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"MicrovmImageLogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--timeout <ms>", "Default deadline in milliseconds for each hook without its own <Hook>TimeoutInSeconds (/terminate defaults to 10000), and for the app to answer HTTP when there is no /ready hook. Default 60000.").default(6e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role <arn>", "Assume this role (the MicroVM's execution role, a run-microvm parameter rather than part of the template) and forward STS-issued temp credentials to the containers. Off by default: the shell credentials are forwarded.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. Bare form uses the resolved stack name.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
25978
27536
  }
25979
27537
 
25980
27538
  //#endregion
@@ -26003,7 +27561,7 @@ async function localListCommand(options) {
26003
27561
  process.stdout.write(`${formatTargetListing(listing, getEmbedConfig().cliName, { long: options.long })}\n`);
26004
27562
  }
26005
27563
  function formatTargetListing(listing, cliName, options = {}) {
26006
- if (countTargets(listing) === 0) return `No runnable targets (Lambda functions, APIs, ECS services / tasks, AgentCore Runtimes, load balancers, CloudFront distributions, Lambda web functions) found in this CDK app.`;
27564
+ if (countTargets(listing) === 0) return `No runnable targets (Lambda functions, APIs, ECS services / tasks, AgentCore Runtimes, load balancers, CloudFront distributions, Lambda web functions, Lambda MicroVM images) found in this CDK app.`;
26007
27565
  const long = options.long ?? false;
26008
27566
  return "\n" + [
26009
27567
  formatSection("Lambda Functions", `${cliName} invoke <target>`, listing.lambdas, long),
@@ -26013,7 +27571,8 @@ function formatTargetListing(listing, cliName, options = {}) {
26013
27571
  formatSection("AgentCore Runtimes", `${cliName} invoke-agentcore <target>`, listing.agentCoreRuntimes, long),
26014
27572
  formatSection("Application Load Balancers", `${cliName} start-alb <target...>`, listing.loadBalancers, long),
26015
27573
  formatSection("CloudFront Distributions", `${cliName} start-cloudfront <target>`, listing.cloudFrontDistributions, long),
26016
- formatSection("Lambda Web Functions", `${cliName} start-web-function <target>`, listing.webFunctions, long)
27574
+ formatSection("Lambda Web Functions", `${cliName} start-web-function <target>`, listing.webFunctions, long),
27575
+ formatSection("Lambda MicroVM Images", `${cliName} start-microvm <target>`, listing.microvmImages, long)
26017
27576
  ].filter((lines) => lines.length > 0).map((lines) => lines.join("\n")).join("\n\n");
26018
27577
  }
26019
27578
  function formatSection(title, command, entries, long) {
@@ -31289,5 +32848,5 @@ function addStudioSpecificOptions(cmd) {
31289
32848
  }
31290
32849
 
31291
32850
  //#endregion
31292
- export { WEB_FUNCTION_TYPE as $, AgentCoreResolutionError as $i, invokeAgentCore as $n, pickResponseTemplate as $r, parseLbPortOverrides as $t, containerFailure as A, resolveCfnRegion as Ai, listPinnedTargets as An, buildCognitoJwksUrl as Ar, buildEdgeRequestEvent as At, webFunctionAssumeRole as B, discoverWebSocketApis as Bi, bridgeAgentCoreWs as Bn, invokeTokenAuthorizer as Br, pickLambdaEdgeFunctionLogicalId as Bt, addListSpecificOptions as C, workloadClientConfig as Ca, substituteEnvVarsFromState as Ci, mergeForService as Cn, filterRoutesByApiIdentifiers as Cr, matchBehavior as Ct, createLocalStartWebFunctionCommand as D, buildProxyClientConfig as Da, isCfnFlagPresent as Di, describePinnedImageUri as Dn, resolveSelectionExpression as Dr, serveLambdaUrlOrigin as Dt, addStartWebFunctionSpecificOptions as E, resolveProfileCredentials as Ea, createLocalStateProvider as Ei, runImageOverrideBuilds as En, startApiServer as Er, serveFromStaticOrigin as Et, watchContainerExit as F, resolveWatchConfig as Fi, setShadowReadyTimeoutMs as Fn, verifyJwtViaDiscovery as Fr, describeS3OriginDomain as Ft, resolveWebFunctionRuntime as G, discoverRoutes as Gi, MCP_CONTAINER_PORT as Gn, isFunctionUrlOacFronted as Gr, runViewerResponse as Gt, WEB_FUNCTION_RUNTIMES as H, filterWebSocketApisByIdentifiers as Hi, A2A_CONTAINER_PORT as Hn, applyCorsResponseHeaders as Hr, resolveCloudFrontDistribution as Ht, WEB_FUNCTION_HTTP_ENDPOINT_ENV as I, resolveSingleTarget as Ii, getContainerNetworkIp as In, buildMethodArn as Ir, extractKvsAssociations as It, findFileAssetForObjectKey as J, AGENTCORE_A2A_PROTOCOL as Ji, mcpInvokeOnce as Jn, translateLambdaResponse as Jr, createLocalFileKvsDataSource as Jt, supportedWebFunctionRuntimes as K, pickRefLogicalId as Ki, MCP_PATH as Kn, matchPreflight as Kr, stripCloudFrontImport as Kt, buildWebFunctionLambdaView as L, countTargets as Li, attachContainerLogStreamer as Ln, computeRequestIdentityHash as Lr, isCloudFrontDistribution as Lt, readContainerLogTail as M, CfnLocalStateProvider as Mi, CloudMapRegistry as Mn, createJwksCache as Mr, edgeHeadersToHttp as Mt, startWebFunctionContainer as N, collectSsmParameterRefs as Ni, DEFAULT_SHADOW_READY_TIMEOUT_MS as Nn, verifyCognitoJwt as Nr, httpHeadersToEdge as Nt, RecentOutput as O, isProxyEnvConfigured as Oa, rejectExplicitCfnStackWithMultipleStacks as Oi, imageOverrideKeyAliases as On, resolveServiceIntegrationParameters as Or, applyEdgeRequestResult as Ot, waitForWebFunctionReady as P, resolveSsmParameters as Pi, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as Pn, verifyJwtAuthorizer as Pr, CLOUDFRONT_DISTRIBUTION_TYPE as Pt, WEB_FUNCTION_REVISION_TYPE as Q, AGENTCORE_RUNTIME_TYPE as Qi, AGENTCORE_SESSION_ID_HEADER as Qn, evaluateResponseParameters as Qr, createLocalStartAlbCommand as Qt, finalizeWebFunctionEnv as R, listTargets as Ri, addInvokeAgentCoreSpecificOptions as Rn, evaluateCachedLambdaPolicy as Rr, pickFunctionUrlLogicalIdFromOrigin as Rt, StudioEventBus as S, resetWorkloadIdentity as Sa, substituteAgainstStateAsync as Si, enforceImageOverrideOrphans as Sn, filterRoutesByApiIdentifier as Sr, createS3OriginReader as St, formatTargetListing as T, buildStsClientConfig as Ta, LocalStateSourceError as Ti, resolveImageOverrides as Tn, readMtlsMaterialsFromDisk as Tr, resolveErrorResponseCandidates as Tt, buildNodeEntryFileStart as U, parseSelectionExpressionPath as Ui, A2A_PATH as Un, buildCorsConfigByApiId as Ur, compileCloudFrontFunction as Ut, webFunctionFidelityWarnings as V, discoverWebSocketApisOrThrow as Vi, invokeAgentCoreWs as Vn, attachAuthorizers as Vr, pickTargetFunctionLogicalId as Vt, defaultWebFunctionPlatform as W, webSocketApiMatchesIdentifier as Wi, a2aInvokeOnce as Wn, buildCorsConfigFromCloudFrontChain as Wr, runViewerRequest as Wt, resolveWebFunctionArtifact as X, AGENTCORE_HTTP_PROTOCOL as Xi, AGENTCORE_SIGV4_SERVICE as Xn, buildHttpApiV2Event as Xr, addAlbSpecificOptions as Xt, parseWebFunctionBuildConfig as Y, AGENTCORE_AGUI_PROTOCOL as Yi, parseSseForJsonRpc as Yn, applyAuthorizerOverlay as Yr, createUnboundCloudFrontModule as Yt, WEB_FUNCTION_ENDPOINT_TYPE as Z, AGENTCORE_MCP_PROTOCOL as Zi, signAgentCoreInvocation as Zn, buildRestV1Event as Zr, albStrategy as Zt, filterStudioTargetGroups as _, SECOND_ASSUME_MESSAGE as _a, resolveRuntimeCodeMountPath as _i, resolveSharedSidecarCredentials as _n, attachStageContext as _r, resolveKvsModulesForDistribution as _t, createLocalStudioCommand as a, tryResolveImageFnJoin as aa, probeHostGatewaySupport as ai, serviceStrategy as an, computeCodeImageTag as ar, selectServeInboundAuth as at, renderStudioHtml as b, readEnvCallerCredentials as ba, EcsTaskResolutionError as bi, buildImageOverrideTag as bn, resolveEnvVars$1 as br, resolveDeployedOriginBucket as bt, startStudioProxy as c, DynamicReferenceResolver as ca, ConnectionRegistry as ci, MAX_TASKS_SUBNET_RANGE_CAP as cn, classifySourceChange as cr, startAgentCoreWsBridge as ct, createStudioDispatcher as d, firstUsableRegion as da, parseConnectionsPath as di, addImageOverrideOptions as dn, addStartApiSpecificOptions as dr, createLocalStartCloudFrontCommand as dt, pickAgentCoreCandidateStack as ea, selectIntegrationResponse as ei, resolveAlbTarget as en, waitForAgentCoreHttpReady as er, WebFunctionResolutionError as et, filterStudioCustomResources as f, keysNotFromTemplate as fa, buildConnectEvent as fi, buildEcsImageResolutionContext$1 as fn, createLocalStartApiCommand as fr, normalizeKvsFileKeys as ft, annotatePinnedEcsTargets as g, withoutKeys as ga, buildContainerImage as gi, resolveEcsAssumeRoleOption as gn, createFileWatcher as gr, idFromArn as gt, annotateEcsTaskPinnedTargets as h, resolveDynamicReferencesInEnv as ha, architectureToPlatform as hi, parseRestartPolicy as hn, createAuthorizerCache as hr, resolveCloudFrontTarget as ht, coerceStopRequest as i, substituteImagePlaceholders as ia, HOST_GATEWAY_MIN_VERSION as ii, createLocalStartServiceCommand as in, buildAgentCoreCodeImage as ir, buildAgentCoreServeAuthCheck as it, planWebFunctionContainer as j, resolveCfnStackName as ji, buildCloudMapIndex as jn, buildJwksUrlFromIssuer as jr, buildEdgeResponseEvent as jt, WEB_FUNCTION_CONTAINER_PORT as k, resolveCfnFallbackRegion as ki, isLocalCdkAssetImage as kn, defaultCredentialsLoader as kr, applyEdgeResponseResult as kt, relayServeRequest as l, containsDynamicReference as la, buildMgmtEndpointEnvUrl as li, addCommonEcsServiceOptions as ln, addInvokeSpecificOptions as lr, LocalStartCloudFrontError as lt, annotateAlbPinnedBackingServices as m, parseDynamicReference as ma, buildMessageEvent as mi, parseMaxTasks as mn, resolveApiTargetSubset as mr, parseOriginOverrides as mt, coerceRunRequest as n, derivePseudoParametersFromRegion as na, VtlEvaluationError as ni, resolveAlbFrontDoor as nn, downloadAndExtractS3Bundle as nr, addStartAgentCoreSpecificOptions as nt, resolveServeBaseUrl as o, LocalInvokeBuildError as oa, resolveHostGatewayExtraHosts as oi, addRunTaskSpecificOptions as on, renderCodeDockerfile as or, startAgentCoreHttpServer as ot, isCustomResourceLambdaTarget as p, keysOverriddenBy as pa, buildDisconnectEvent as pi, ecsClusterOption as pn, createWatchPredicates as pr, parseKvsFileOverrides as pt, disposeWebFunctionArtifact as q, resolveLambdaArnIntrinsic as qi, MCP_PROTOCOL_VERSION as qn, matchRoute as qr, createCloudFrontModule as qt, coerceServeRequest as r, formatStateRemedy as ra, HOST_DOCKER_INTERNAL_GATEWAY as ri, addStartServiceSpecificOptions as rn, SUPPORTED_CODE_RUNTIMES as rr, createLocalStartAgentCoreCommand as rt, createStudioServeManager as s, DynamicReferenceResolutionError as sa, bufferToBody as si, createLocalRunTaskCommand as sn, toCmdArgv as sr, attachAgentCoreWsBridge as st, addStudioSpecificOptions as t, resolveAgentCoreTarget as ta, tryParseStatus as ti, isApplicationLoadBalancer as tn, waitForAgentCorePing as tr, resolveWebFunctionTarget as tt, reinvoke as u, deployedFillMayBeSecret as ua, handleConnectionsRequest as ui, addEcsAssumeRoleOptions as un, createLocalInvokeCommand as ur, addStartCloudFrontSpecificOptions as ut, startStudioServer as v, assumedRoleRecorded as va, resolveRuntimeFileExtension as vi, runEcsServiceEmulator as vn, buildStageMap as vr, createDeployedKvsDataSource as vt, createLocalListCommand as w, workloadStsClientConfig as wa, substituteEnvVarsFromStateAsync as wi, parseImageOverrideFlags as wn, groupRoutesByServer as wr, startCloudFrontServer as wt, createStudioStore as x, recordAssumedRole as xa, substituteAgainstState as xi, canonicalizeImageOverrideKeys as xn, availableApiIdentifiers as xr, classifyS3Error as xt, toStudioTargetGroups as y, deployAccountStsClientConfig as ya, resolveRuntimeImage as yi, ImageOverrideError as yn, materializeLayerFromArn as yr, resolveDeployedKvsArnByName as yt, readWebFunctionServiceConfig as z, availableWebSocketApiIdentifiers as zi, createLocalInvokeAgentCoreCommand as zn, invokeRequestAuthorizer as zr, pickKvsLogicalIdFromArn as zt };
31293
- //# sourceMappingURL=local-studio-CsftJJ1r.js.map
32851
+ export { postHook as $, firstUsableRegion as $a, parseConnectionsPath as $i, addImageOverrideOptions as $n, addStartApiSpecificOptions as $r, classifyS3Error as $t, microvmFidelityWarnings as A, filterWebSocketApisByIdentifiers as Aa, applyCorsResponseHeaders as Ai, stripCloudFrontImport as An, A2A_CONTAINER_PORT as Ar, readContainerLogTail as At, MICROVM_WS_BASE_PROTOCOL as B, AGENTCORE_RUNTIME_TYPE as Ba, evaluateResponseParameters as Bi, createLocalStartAlbCommand as Bn, AGENTCORE_SESSION_ID_HEADER as Br, parseWebFunctionBuildConfig as Bt, addListSpecificOptions as C, resolveWatchConfig as Ca, verifyJwtViaDiscovery as Ci, WEB_FUNCTION_REVISION_TYPE as Cn, setShadowReadyTimeoutMs as Cr, parseKvsFileOverrides as Ct, buildMicrovmLambdaView as D, availableWebSocketApiIdentifiers as Da, invokeRequestAuthorizer as Di, compileCloudFrontFunction as Dn, createLocalInvokeAgentCoreCommand as Dr, WEB_FUNCTION_CONTAINER_PORT as Dt, addStartMicrovmSpecificOptions as E, listTargets as Ea, evaluateCachedLambdaPolicy as Ei, resolveWebFunctionTarget as En, addInvokeAgentCoreSpecificOptions as Er, RecentOutput as Et, microvmImageTag as F, resolveLambdaArnIntrinsic as Fa, matchRoute as Fi, findFileAssetForObjectKey as Fn, MCP_PROTOCOL_VERSION as Fr, buildNodeEntryFileStart as Ft, startMicrovmFrontDoor as G, formatStateRemedy as Ga, HOST_DOCKER_INTERNAL_GATEWAY as Gi, addStartServiceSpecificOptions as Gn, SUPPORTED_CODE_RUNTIMES as Gr, readWebFunctionServiceConfig as Gt, PROXY_PORT_HEADER as H, pickAgentCoreCandidateStack as Ha, selectIntegrationResponse as Hi, resolveAlbTarget as Hn, waitForAgentCoreHttpReady as Hr, WEB_FUNCTION_HTTP_ENDPOINT_ENV as Ht, planMicrovmPorts as I, AGENTCORE_A2A_PROTOCOL as Ia, translateLambdaResponse as Ii, isBucketDeploymentResource as In, mcpInvokeOnce as Ir, defaultWebFunctionPlatform as It, MICROVM_HOOK_PATH_PREFIX as J, LocalInvokeBuildError as Ja, resolveHostGatewayExtraHosts as Ji, addRunTaskSpecificOptions as Jn, renderCodeDockerfile as Jr, idFromArn as Jt, stripMicrovmProxyHeaders as K, substituteImagePlaceholders as Ka, HOST_GATEWAY_MIN_VERSION as Ki, createLocalStartServiceCommand as Kn, buildAgentCoreCodeImage as Kr, webFunctionAssumeRole as Kt, readExposedPorts as L, AGENTCORE_AGUI_PROTOCOL as La, applyAuthorizerOverlay as Li, resolveBucketDeploymentObjectKey as Ln, parseSseForJsonRpc as Lr, resolveWebFunctionRuntime as Lt, DEFAULT_TERMINATE_HOOK_TIMEOUT_MS as M, webSocketApiMatchesIdentifier as Ma, buildCorsConfigFromCloudFrontChain as Mi, createLocalFileKvsDataSource as Mn, a2aInvokeOnce as Mr, waitForWebFunctionReady as Mt, LocalMicrovm as N, discoverRoutes as Na, isFunctionUrlOacFronted as Ni, createUnboundCloudFrontModule as Nn, MCP_CONTAINER_PORT as Nr, watchContainerExit as Nt, createLocalStartMicrovmCommand as O, discoverWebSocketApis as Oa, invokeTokenAuthorizer as Oi, runViewerRequest as On, bridgeAgentCoreWs as Or, containerFailure as Ot, buildMicrovmImage as P, pickRefLogicalId as Pa, matchPreflight as Pi, bucketDeploymentSourceObjectKeys as Pn, MCP_PATH as Pr, WEB_FUNCTION_RUNTIMES as Pt, pollBuildHook as Q, deployedFillMayBeSecret as Qa, handleConnectionsRequest as Qi, addEcsAssumeRoleOptions as Qn, createLocalInvokeCommand as Qr, resolveDeployedOriginBucket as Qt, setContainerPaused as R, AGENTCORE_HTTP_PROTOCOL as Ra, buildHttpApiV2Event as Ri, addAlbSpecificOptions as Rn, AGENTCORE_SIGV4_SERVICE as Rr, supportedWebFunctionRuntimes as Rt, StudioEventBus as S, resolveSsmParameters as Sa, verifyJwtAuthorizer as Si, WEB_FUNCTION_ENDPOINT_TYPE as Sn, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as Sr, normalizeKvsFileKeys as St, formatTargetListing as T, countTargets as Ta, computeRequestIdentityHash as Ti, WebFunctionResolutionError as Tn, attachContainerLogStreamer as Tr, resolveCloudFrontTarget as Tt, rewriteUpgradeResponseHead as U, resolveAgentCoreTarget as Ua, tryParseStatus as Ui, isApplicationLoadBalancer as Un, waitForAgentCorePing as Ur, buildWebFunctionLambdaView as Ut, MicrovmStateError as V, AgentCoreResolutionError as Va, pickResponseTemplate as Vi, parseLbPortOverrides as Vn, invokeAgentCore as Vr, resolveWebFunctionArtifact as Vt, selectTargetPort as W, derivePseudoParametersFromRegion as Wa, VtlEvaluationError as Wi, resolveAlbFrontDoor as Wn, downloadAndExtractS3Bundle as Wr, finalizeWebFunctionEnv as Wt, callRuntimeHook as X, DynamicReferenceResolver as Xa, ConnectionRegistry as Xi, MAX_TASKS_SUBNET_RANGE_CAP as Xn, classifySourceChange as Xr, createDeployedKvsDataSource as Xt, MicrovmHookError as Y, DynamicReferenceResolutionError as Ya, bufferToBody as Yi, createLocalRunTaskCommand as Yn, toCmdArgv as Yr, resolveKvsModulesForDistribution as Yt, generateMicrovmId as Z, containsDynamicReference as Za, buildMgmtEndpointEnvUrl as Zi, addCommonEcsServiceOptions as Zn, addInvokeSpecificOptions as Zr, resolveDeployedKvsArnByName as Zt, filterStudioTargetGroups as _, resolveCfnFallbackRegion as _a, defaultCredentialsLoader as _i, pickKvsLogicalIdFromArn as _n, isLocalCdkAssetImage as _r, attachAgentCoreWsBridge as _t, createLocalStudioCommand as a, resolveRuntimeCodeMountPath as aa, attachStageContext as ai, serveLambdaUrlOrigin as an, SECOND_ASSUME_MESSAGE as ao, resolveSharedSidecarCredentials as ar, disposeMicrovmCodeArtifact as at, renderStudioHtml as b, CfnLocalStateProvider as ba, createJwksCache as bi, pickWebFunctionEndpointLogicalIdFromOrigin as bn, CloudMapRegistry as br, addStartCloudFrontSpecificOptions as bt, startStudioProxy as c, EcsTaskResolutionError as ca, resolveEnvVars$1 as ci, buildEdgeRequestEvent as cn, readEnvCallerCredentials as co, buildImageOverrideTag as cr, resolveMicrovmImageTarget as ct, createStudioDispatcher as d, substituteEnvVarsFromState as da, filterRoutesByApiIdentifiers as di, httpHeadersToEdge as dn, workloadClientConfig as do, mergeForService as dr, createLocalStartWebFunctionCommand as dt, buildConnectEvent as ea, createLocalStartApiCommand as ei, createS3OriginReader as en, keysNotFromTemplate as eo, buildEcsImageResolutionContext$1 as er, runHookBody as et, filterStudioCustomResources as f, substituteEnvVarsFromStateAsync as fa, groupRoutesByServer as fi, CLOUDFRONT_DISTRIBUTION_TYPE as fn, workloadStsClientConfig as fo, parseImageOverrideFlags as fr, addStartAgentCoreSpecificOptions as ft, annotatePinnedEcsTargets as g, rejectExplicitCfnStackWithMultipleStacks as ga, resolveServiceIntegrationParameters as gi, pickFunctionUrlLogicalIdFromOrigin as gn, isProxyEnvConfigured as go, imageOverrideKeyAliases as gr, startAgentCoreHttpServer as gt, annotateEcsTaskPinnedTargets as h, isCfnFlagPresent as ha, resolveSelectionExpression as hi, isCloudFrontDistribution as hn, buildProxyClientConfig as ho, describePinnedImageUri as hr, selectServeInboundAuth as ht, coerceStopRequest as i, buildContainerImage as ia, createFileWatcher as ii, serveFromStaticOrigin as in, withoutKeys as io, resolveEcsAssumeRoleOption as ir, MicrovmResolutionError as it, readRunHookPayload as j, parseSelectionExpressionPath as ja, buildCorsConfigByApiId as ji, createCloudFrontModule as jn, A2A_PATH as jr, startWebFunctionContainer as jt, finalizeMicrovmEnv as k, discoverWebSocketApisOrThrow as ka, attachAuthorizers as ki, runViewerResponse as kn, invokeAgentCoreWs as kr, planWebFunctionContainer as kt, relayServeRequest as l, substituteAgainstState as la, availableApiIdentifiers as li, buildEdgeResponseEvent as ln, recordAssumedRole as lo, canonicalizeImageOverrideKeys as lr, s3KeyOfUri as lt, annotateAlbPinnedBackingServices as m, createLocalStateProvider as ma, startApiServer as mi, extractKvsAssociations as mn, resolveProfileCredentials as mo, runImageOverrideBuilds as mr, buildAgentCoreServeAuthCheck as mt, coerceRunRequest as n, buildMessageEvent as na, resolveApiTargetSubset as ni, startCloudFrontServer as nn, parseDynamicReference as no, parseMaxTasks as nr, MICROVM_DEFAULT_INGRESS_PORT as nt, resolveServeBaseUrl as o, resolveRuntimeFileExtension as oa, buildStageMap as oi, applyEdgeRequestResult as on, assumedRoleRecorded as oo, runEcsServiceEmulator as or, readMicrovmImageConfig as ot, isCustomResourceLambdaTarget as p, LocalStateSourceError as pa, readMtlsMaterialsFromDisk as pi, describeS3OriginDomain as pn, buildStsClientConfig as po, resolveImageOverrides as pr, createLocalStartAgentCoreCommand as pt, MAX_RUN_HOOK_PAYLOAD_BYTES as q, tryResolveImageFnJoin as qa, probeHostGatewaySupport as qi, serviceStrategy as qn, computeCodeImageTag as qr, webFunctionFidelityWarnings as qt, coerceServeRequest as r, architectureToPlatform as ra, createAuthorizerCache as ri, resolveErrorResponseCandidates as rn, resolveDynamicReferencesInEnv as ro, parseRestartPolicy as rr, MICROVM_IMAGE_TYPE as rt, createStudioServeManager as s, resolveRuntimeImage as sa, materializeLayerFromArn as si, applyEdgeResponseResult as sn, deployAccountStsClientConfig as so, ImageOverrideError as sr, resolveMicrovmCodeArtifact as st, addStudioSpecificOptions as t, buildDisconnectEvent as ta, createWatchPredicates as ti, matchBehavior as tn, keysOverriddenBy as to, ecsClusterOption as tr, waitForAppAnswer as tt, reinvoke as u, substituteAgainstStateAsync as ua, filterRoutesByApiIdentifier as ui, edgeHeadersToHttp as un, resetWorkloadIdentity as uo, enforceImageOverrideOrphans as ur, addStartWebFunctionSpecificOptions as ut, startStudioServer as v, resolveCfnRegion as va, buildCognitoJwksUrl as vi, pickLambdaEdgeFunctionLogicalId as vn, listPinnedTargets as vr, startAgentCoreWsBridge as vt, createLocalListCommand as w, resolveSingleTarget as wa, buildMethodArn as wi, WEB_FUNCTION_TYPE as wn, getContainerNetworkIp as wr, parseOriginOverrides as wt, createStudioStore as x, collectSsmParameterRefs as xa, verifyCognitoJwt as xi, resolveCloudFrontDistribution as xn, DEFAULT_SHADOW_READY_TIMEOUT_MS as xr, createLocalStartCloudFrontCommand as xt, toStudioTargetGroups as y, resolveCfnStackName as ya, buildJwksUrlFromIssuer as yi, pickTargetFunctionLogicalId as yn, buildCloudMapIndex as yr, LocalStartCloudFrontError as yt, MICROVM_CONTROL_PATH as z, AGENTCORE_MCP_PROTOCOL as za, buildRestV1Event as zi, albStrategy as zn, signAgentCoreInvocation as zr, disposeWebFunctionArtifact as zt };
32852
+ //# sourceMappingURL=local-studio-DKMdamzH.js.map