cdk-local 0.118.1 → 0.120.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.
@@ -34,6 +34,9 @@ import { SignatureV4 } from "@smithy/signature-v4";
34
34
  import graphlib from "graphlib";
35
35
  import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
36
36
  import * as vm from "node:vm";
37
+ import "@aws-sdk/signature-v4a";
38
+ import { CloudFrontClient, paginateListKeyValueStores } from "@aws-sdk/client-cloudfront";
39
+ import { CloudFrontKeyValueStoreClient, GetKeyCommand, ResourceNotFoundException } from "@aws-sdk/client-cloudfront-keyvaluestore";
37
40
  import { EventEmitter } from "node:events";
38
41
 
39
42
  //#region src/cli/options.ts
@@ -23887,7 +23890,7 @@ function writeRawHttpError(socket, statusCode, message) {
23887
23890
  if (socket.destroyed) return;
23888
23891
  const body = `${message}\n`;
23889
23892
  const lines = [
23890
- `HTTP/1.1 ${statusCode} ${STATUS_TEXT[statusCode] ?? "Error"}`,
23893
+ `HTTP/1.1 ${statusCode} ${STATUS_TEXT$1[statusCode] ?? "Error"}`,
23891
23894
  "content-type: text/plain; charset=utf-8",
23892
23895
  `content-length: ${Buffer.byteLength(body)}`,
23893
23896
  "connection: close",
@@ -23940,7 +23943,7 @@ function writeRawHttpRedirect(socket, action, req, listenerPort, scheme) {
23940
23943
  function writeRawHttpFixedResponse(socket, action) {
23941
23944
  if (socket.destroyed) return;
23942
23945
  const body = action.messageBody ?? "";
23943
- const statusText = STATUS_TEXT[action.statusCode] ?? "";
23946
+ const statusText = STATUS_TEXT$1[action.statusCode] ?? "";
23944
23947
  const contentType = sanitizeRawHeaderValue(action.contentType ?? "text/plain; charset=utf-8");
23945
23948
  const lines = [
23946
23949
  `HTTP/1.1 ${action.statusCode} ${statusText}`,
@@ -23960,7 +23963,7 @@ function sanitizeRawHeaderValue(value) {
23960
23963
  return value.replace(/[\r\n]/g, " ");
23961
23964
  }
23962
23965
  /** Minimal HTTP/1.1 status text map for the codes the raw writers emit. */
23963
- const STATUS_TEXT = {
23966
+ const STATUS_TEXT$1 = {
23964
23967
  301: "Moved Permanently",
23965
23968
  302: "Found",
23966
23969
  401: "Unauthorized",
@@ -28095,6 +28098,92 @@ function addAlbSpecificOptions(cmd) {
28095
28098
  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 + `docker restart`; no rebuild, front-door pool entry unchanged since the IP/port are preserved). 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));
28096
28099
  }
28097
28100
 
28101
+ //#endregion
28102
+ //#region src/local/cloudfront-kvs.ts
28103
+ /** Build a `cf` module backed by the resolved data sources for one function. */
28104
+ function createCloudFrontModule(sources) {
28105
+ return { kvs(kvsId) {
28106
+ return makeHandle(pickSource(sources, kvsId));
28107
+ } };
28108
+ }
28109
+ /**
28110
+ * The `cf` module bound when a function calls `cf.kvs()` but no KeyValueStore
28111
+ * binding was resolved (no `--from-cfn-stack`, no covering `--kvs-file`). The
28112
+ * handle is returned successfully so a top-level `cf.kvs()` call does not throw
28113
+ * at module-eval; the actionable error surfaces when the function actually
28114
+ * reads a key.
28115
+ */
28116
+ function createUnboundCloudFrontModule(functionLogicalId) {
28117
+ const fail = () => Promise.reject(/* @__PURE__ */ new Error(`CloudFront Function '${functionLogicalId}' reads from a KeyValueStore (cf.kvs()), but no KeyValueStore binding is available locally. Pass --from-cfn-stack to read the deployed store, or --kvs-file <id>=<file.json> to back it with a local JSON map.`));
28118
+ const handle = {
28119
+ get: () => fail(),
28120
+ exists: () => fail(),
28121
+ meta: () => fail(),
28122
+ count: () => fail()
28123
+ };
28124
+ return { kvs: () => handle };
28125
+ }
28126
+ function pickSource(sources, kvsId) {
28127
+ if (sources.length === 0) throw new Error("cf.kvs(): no KeyValueStore is associated with this function.");
28128
+ if (kvsId === void 0) return sources[0];
28129
+ const byId = sources.find((s) => s.kvsId === kvsId);
28130
+ if (byId) return byId;
28131
+ if (sources.length === 1) return sources[0];
28132
+ throw new Error(`cf.kvs('${kvsId}'): no associated KeyValueStore matches this id locally (associated: ${sources.map((s) => s.kvsId ?? s.label).join(", ")}).`);
28133
+ }
28134
+ function makeHandle(source) {
28135
+ return {
28136
+ async get(key, options) {
28137
+ const value = await source.getValue(key);
28138
+ if (value === void 0) throw new Error(`cf.kvs().get('${key}'): key not found in ${source.label}.`);
28139
+ if (options?.format === "json") try {
28140
+ return JSON.parse(value);
28141
+ } catch (err) {
28142
+ throw new Error(`cf.kvs().get('${key}', { format: 'json' }): value is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
28143
+ }
28144
+ return value;
28145
+ },
28146
+ async exists(key) {
28147
+ return await source.getValue(key) !== void 0;
28148
+ },
28149
+ meta() {
28150
+ return Promise.reject(/* @__PURE__ */ new Error("cf.kvs().meta() is not reproduced locally by cdkl start-cloudfront (only get / exists are supported)."));
28151
+ },
28152
+ count() {
28153
+ return Promise.reject(/* @__PURE__ */ new Error("cf.kvs().count() is not reproduced locally by cdkl start-cloudfront (only get / exists are supported)."));
28154
+ }
28155
+ };
28156
+ }
28157
+ /**
28158
+ * Build a {@link KvsDataSource} from a local JSON file (the `--kvs-file
28159
+ * <id>=<file.json>` escape hatch). The file is a flat `{ "key": "value" }`
28160
+ * object; non-string values are JSON-stringified (KVS values are always
28161
+ * strings). Read once at construction (boot time); a `--watch` reload rebuilds
28162
+ * it.
28163
+ */
28164
+ function createLocalFileKvsDataSource(args) {
28165
+ let raw;
28166
+ try {
28167
+ raw = readFileSync(args.filePath, "utf-8");
28168
+ } catch (err) {
28169
+ throw new Error(`--kvs-file '${args.id}=${args.filePath}': could not read the file: ${err instanceof Error ? err.message : String(err)}`);
28170
+ }
28171
+ let parsed;
28172
+ try {
28173
+ parsed = JSON.parse(raw);
28174
+ } catch (err) {
28175
+ throw new Error(`--kvs-file '${args.id}=${args.filePath}': not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
28176
+ }
28177
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`--kvs-file '${args.id}=${args.filePath}': expected a JSON object of key -> value entries.`);
28178
+ const map = /* @__PURE__ */ new Map();
28179
+ for (const [key, value] of Object.entries(parsed)) map.set(key, typeof value === "string" ? value : JSON.stringify(value));
28180
+ return {
28181
+ label: `file:${args.filePath}`,
28182
+ kvsId: args.id,
28183
+ getValue: (key) => Promise.resolve(map.get(key))
28184
+ };
28185
+ }
28186
+
28098
28187
  //#endregion
28099
28188
  //#region src/local/cloudfront-function-runtime.ts
28100
28189
  /**
@@ -28109,21 +28198,49 @@ function addAlbSpecificOptions(cmd) {
28109
28198
  const FUNCTION_TIMEOUT_MS = 5e3;
28110
28199
  const EVENT_GLOBAL = "__cfEvent";
28111
28200
  /**
28201
+ * Regex matching the canonical CloudFront-Functions 2.0 KeyValueStore import
28202
+ * line (`import cf from 'cloudfront';`). The module is default-imported under a
28203
+ * single binding name; this is the only `cloudfront` import form the runtime
28204
+ * supports. Anchored per-line (`m` flag) so only the import statement is
28205
+ * matched, not a substring elsewhere.
28206
+ */
28207
+ const CLOUDFRONT_IMPORT_RE = /^[ \t]*import\s+(\w+)\s+from\s+['"]cloudfront['"]\s*;?[ \t]*$/m;
28208
+ /**
28209
+ * Strip the `import cf from 'cloudfront'` statement from a 2.0 function's code
28210
+ * and return the binding name, so the code compiles as a plain `vm.Script`
28211
+ * (an `import` statement is invalid outside an ES module) and the `cloudfront`
28212
+ * module can be injected as the binding-named sandbox global instead. Returns
28213
+ * the code unchanged with no binding name when the function does not import
28214
+ * `cloudfront`. The matched line is blanked (not deleted) so error line numbers
28215
+ * still line up with the source.
28216
+ */
28217
+ function stripCloudFrontImport(code) {
28218
+ const match = CLOUDFRONT_IMPORT_RE.exec(code);
28219
+ if (!match) return { code };
28220
+ return {
28221
+ code: code.replace(CLOUDFRONT_IMPORT_RE, ""),
28222
+ bindingName: match[1]
28223
+ };
28224
+ }
28225
+ /**
28112
28226
  * Compile a CloudFront Function's inline code once. Throws a clear error when
28113
28227
  * the code has a syntax error or declares no `handler` (surfaced at boot so a
28114
28228
  * malformed function never silently no-ops at request time).
28115
28229
  */
28116
28230
  function compileCloudFrontFunction(logicalId, code, runtime) {
28231
+ const { code: source, bindingName } = stripCloudFrontImport(code);
28117
28232
  let script;
28118
28233
  try {
28119
- script = new vm.Script(`${code}\n;handler(${EVENT_GLOBAL})`, { filename: `cloudfront-function-${logicalId}.js` });
28234
+ script = new vm.Script(`${source}\n;handler(${EVENT_GLOBAL})`, { filename: `cloudfront-function-${logicalId}.js` });
28120
28235
  } catch (err) {
28121
28236
  throw new Error(`CloudFront Function '${logicalId}' failed to compile: ${err instanceof Error ? err.message : String(err)}`);
28122
28237
  }
28123
28238
  let hasHandler;
28124
28239
  try {
28125
- const probeContext = vm.createContext({ console });
28126
- hasHandler = new vm.Script(`${code}\n;typeof handler === 'function'`).runInContext(probeContext, { timeout: FUNCTION_TIMEOUT_MS });
28240
+ const probeGlobals = { console };
28241
+ if (bindingName) probeGlobals[bindingName] = createUnboundCloudFrontModule(logicalId);
28242
+ const probeContext = vm.createContext(probeGlobals);
28243
+ hasHandler = new vm.Script(`${source}\n;typeof handler === 'function'`).runInContext(probeContext, { timeout: FUNCTION_TIMEOUT_MS });
28127
28244
  } catch (err) {
28128
28245
  throw new Error(`CloudFront Function '${logicalId}' failed to compile: ${err instanceof Error ? err.message : String(err)}`);
28129
28246
  }
@@ -28131,7 +28248,8 @@ function compileCloudFrontFunction(logicalId, code, runtime) {
28131
28248
  return {
28132
28249
  logicalId,
28133
28250
  runtime,
28134
- script
28251
+ script,
28252
+ ...bindingName !== void 0 && { cloudfrontBindingName: bindingName }
28135
28253
  };
28136
28254
  }
28137
28255
  /**
@@ -28142,10 +28260,12 @@ function compileCloudFrontFunction(logicalId, code, runtime) {
28142
28260
  * thrown by the handler is wrapped with the function's logical id.
28143
28261
  */
28144
28262
  async function invokeCloudFrontFunction(fn, event) {
28145
- const context = vm.createContext({
28263
+ const sandbox = {
28146
28264
  console,
28147
28265
  [EVENT_GLOBAL]: event
28148
- });
28266
+ };
28267
+ if (fn.cloudfrontBindingName) sandbox[fn.cloudfrontBindingName] = fn.cloudfrontModule ?? createUnboundCloudFrontModule(fn.logicalId);
28268
+ const context = vm.createContext(sandbox);
28149
28269
  let result;
28150
28270
  try {
28151
28271
  result = fn.script.runInContext(context, { timeout: FUNCTION_TIMEOUT_MS });
@@ -28282,6 +28402,15 @@ function buildViewerResponseEvent(requestEvent, response) {
28282
28402
  }
28283
28403
  };
28284
28404
  }
28405
+ /** Serialize a CloudFront request's `querystring` object back to a raw string. */
28406
+ function serializeCfQueryString(qs) {
28407
+ const parts = [];
28408
+ for (const [key, val] of Object.entries(qs)) {
28409
+ const values = val.multiValue && val.multiValue.length > 0 ? val.multiValue : [{ value: val.value }];
28410
+ for (const v of values) parts.push(v.value === "" ? encodeURIComponent(key) : `${encodeURIComponent(key)}=${encodeURIComponent(v.value)}`);
28411
+ }
28412
+ return parts.join("&");
28413
+ }
28285
28414
  function parseQueryStringToCf(raw) {
28286
28415
  const out = {};
28287
28416
  if (!raw) return out;
@@ -28340,6 +28469,7 @@ const CLOUDFRONT_FUNCTION_TYPE = "AWS::CloudFront::Function";
28340
28469
  const S3_BUCKET_TYPE = "AWS::S3::Bucket";
28341
28470
  const LAMBDA_URL_TYPE = "AWS::Lambda::Url";
28342
28471
  const LAMBDA_FUNCTION_TYPE = "AWS::Lambda::Function";
28472
+ const LAMBDA_VERSION_TYPE = "AWS::Lambda::Version";
28343
28473
  /**
28344
28474
  * Resolve a distribution logical id within a stack into its routing model.
28345
28475
  * `originOverrides` maps an origin id to a local directory (the `--origin`
@@ -28379,11 +28509,55 @@ function compileDistributionFunctions(template) {
28379
28509
  continue;
28380
28510
  }
28381
28511
  const config = props["FunctionConfig"];
28382
- const runtime = config && typeof config === "object" && typeof config["Runtime"] === "string" ? config["Runtime"] : "cloudfront-js-1.0";
28383
- out.set(logicalId, compileCloudFrontFunction(logicalId, code, runtime));
28512
+ const configObj = config && typeof config === "object" ? config : {};
28513
+ const compiled = compileCloudFrontFunction(logicalId, code, typeof configObj["Runtime"] === "string" ? configObj["Runtime"] : "cloudfront-js-1.0");
28514
+ const kvsAssociations = extractKvsAssociations(configObj);
28515
+ if (kvsAssociations.length > 0) compiled.kvsAssociations = kvsAssociations;
28516
+ out.set(logicalId, compiled);
28517
+ }
28518
+ return out;
28519
+ }
28520
+ /**
28521
+ * Extract a function's `FunctionConfig.KeyValueStoreAssociations[]` into the
28522
+ * compiled-function carrier, pre-resolving each association's KVS resource
28523
+ * logical id from the `KeyValueStoreARN` intrinsic so the command layer can
28524
+ * bind it (deployed store under `--from-cfn-stack`, or a `--kvs-file` map).
28525
+ */
28526
+ function extractKvsAssociations(functionConfig) {
28527
+ const raw = Array.isArray(functionConfig["KeyValueStoreAssociations"]) ? functionConfig["KeyValueStoreAssociations"] : [];
28528
+ const out = [];
28529
+ for (const entry of raw) {
28530
+ if (!entry || typeof entry !== "object") continue;
28531
+ const arnValue = entry["KeyValueStoreARN"];
28532
+ if (arnValue === void 0) continue;
28533
+ const kvsLogicalId = pickKvsLogicalIdFromArn(arnValue);
28534
+ out.push({
28535
+ arnValue,
28536
+ ...kvsLogicalId !== void 0 && { kvsLogicalId }
28537
+ });
28384
28538
  }
28385
28539
  return out;
28386
28540
  }
28541
+ /**
28542
+ * Unwrap a `KeyValueStoreARN` value to the `AWS::CloudFront::KeyValueStore`
28543
+ * logical id when it is an intrinsic reference to a same-template store. CDK /
28544
+ * CloudFormation synthesize it as `Fn::GetAtt[<Kvs>, 'Arn']`, a `Ref`, or
28545
+ * `Fn::Sub: '${<Kvs>.Arn}'`. Returns `undefined` for a literal ARN string (an
28546
+ * imported / out-of-stack store).
28547
+ */
28548
+ function pickKvsLogicalIdFromArn(value) {
28549
+ if (!value || typeof value !== "object") return void 0;
28550
+ const obj = value;
28551
+ const getAtt = obj["Fn::GetAtt"];
28552
+ if (Array.isArray(getAtt) && getAtt.length === 2 && typeof getAtt[0] === "string") return getAtt[0];
28553
+ if (typeof obj["Ref"] === "string") return obj["Ref"];
28554
+ const sub = obj["Fn::Sub"];
28555
+ const subTemplate = typeof sub === "string" ? sub : Array.isArray(sub) ? sub[0] : void 0;
28556
+ if (typeof subTemplate === "string") {
28557
+ const match = /^\$\{([A-Za-z0-9]+)(?:\.[A-Za-z0-9]+)?\}$/.exec(subTemplate.trim());
28558
+ if (match) return match[1];
28559
+ }
28560
+ }
28387
28561
  function resolveBehaviors(dc, functions, distLogicalId, template) {
28388
28562
  const behaviors = [];
28389
28563
  const def = dc["DefaultCacheBehavior"];
@@ -28401,10 +28575,9 @@ function resolveBehaviors(dc, functions, distLogicalId, template) {
28401
28575
  return behaviors;
28402
28576
  }
28403
28577
  function resolveBehavior(behavior, pathPattern, functions, distLogicalId, template) {
28404
- const resolved = {
28405
- targetOriginId: typeof behavior["TargetOriginId"] === "string" ? behavior["TargetOriginId"] : "",
28406
- hasLambdaEdge: Array.isArray(behavior["LambdaFunctionAssociations"]) ? behavior["LambdaFunctionAssociations"].length > 0 : false
28407
- };
28578
+ const resolved = { targetOriginId: typeof behavior["TargetOriginId"] === "string" ? behavior["TargetOriginId"] : "" };
28579
+ const lambdaEdge = resolveLambdaEdgeAssociations(behavior, template, distLogicalId);
28580
+ if (lambdaEdge) resolved.lambdaEdge = lambdaEdge;
28408
28581
  if (pathPattern !== void 0) resolved.pathPattern = pathPattern;
28409
28582
  const cors = resolveResponseHeadersPolicyCors(template, behavior["ResponseHeadersPolicyId"]);
28410
28583
  if (cors) resolved.cors = cors;
@@ -28436,6 +28609,55 @@ function pickFunctionLogicalIdFromArn(value) {
28436
28609
  const getAtt = value["Fn::GetAtt"];
28437
28610
  if (Array.isArray(getAtt) && getAtt.length === 2 && typeof getAtt[0] === "string") return getAtt[0];
28438
28611
  }
28612
+ /**
28613
+ * Resolve a behavior's `LambdaFunctionAssociations[]` into the per-event-type
28614
+ * backing-function map (issue #400). Each association names a
28615
+ * `LambdaFunctionARN` (a versioned ARN) + an `EventType` + `IncludeBody`.
28616
+ */
28617
+ function resolveLambdaEdgeAssociations(behavior, template, distLogicalId) {
28618
+ const assocs = Array.isArray(behavior["LambdaFunctionAssociations"]) ? behavior["LambdaFunctionAssociations"] : [];
28619
+ const out = {};
28620
+ for (const a of assocs) {
28621
+ if (!a || typeof a !== "object") continue;
28622
+ const assoc = a;
28623
+ const eventType = assoc["EventType"];
28624
+ const functionLogicalId = pickLambdaEdgeFunctionLogicalId(assoc["LambdaFunctionARN"], template);
28625
+ if (!functionLogicalId) {
28626
+ getLogger().warn(`Distribution '${distLogicalId}': a Lambda@Edge association references a LambdaFunctionARN cdk-local could not resolve to a local AWS::Lambda::Function; it will not run.`);
28627
+ continue;
28628
+ }
28629
+ const entry = {
28630
+ functionLogicalId,
28631
+ includeBody: assoc["IncludeBody"] === true
28632
+ };
28633
+ if (eventType === "viewer-request") out.viewerRequest = entry;
28634
+ else if (eventType === "origin-request") out.originRequest = entry;
28635
+ else if (eventType === "origin-response") out.originResponse = entry;
28636
+ else if (eventType === "viewer-response") out.viewerResponse = entry;
28637
+ }
28638
+ return Object.keys(out).length > 0 ? out : void 0;
28639
+ }
28640
+ /**
28641
+ * Resolve a `LambdaFunctionARN` (a versioned function ARN) to the backing
28642
+ * `AWS::Lambda::Function` logical id. CDK wires the association to a
28643
+ * `lambda.Version` (`{Ref: <Version>}` returns the qualified ARN), whose
28644
+ * `FunctionName` references the function. A direct (unqualified) function ref
28645
+ * is also accepted. Returns `undefined` when it cannot reach an
28646
+ * `AWS::Lambda::Function` (e.g. an imported cross-region EdgeFunction ARN).
28647
+ */
28648
+ function pickLambdaEdgeFunctionLogicalId(value, template) {
28649
+ const candidate = refLogicalId(value) ?? getAttLogicalId(value);
28650
+ if (!candidate) return void 0;
28651
+ const resources = template.Resources ?? {};
28652
+ const resource = resources[candidate];
28653
+ if (!resource) return void 0;
28654
+ if (resource.Type === LAMBDA_FUNCTION_TYPE) return candidate;
28655
+ if (resource.Type === LAMBDA_VERSION_TYPE) {
28656
+ const fnName = (resource.Properties ?? {})["FunctionName"];
28657
+ const fnId = refLogicalId(fnName) ?? getAttLogicalId(fnName);
28658
+ if (fnId && resources[fnId]?.Type === LAMBDA_FUNCTION_TYPE) return fnId;
28659
+ }
28660
+ }
28439
28661
  function resolveOrigins(dc, template, stack, overrides) {
28440
28662
  const out = /* @__PURE__ */ new Map();
28441
28663
  const origins = Array.isArray(dc["Origins"]) ? dc["Origins"] : [];
@@ -28635,6 +28857,261 @@ function isCloudFrontDistribution(resource) {
28635
28857
  return resource.Type === CLOUDFRONT_DISTRIBUTION_TYPE;
28636
28858
  }
28637
28859
 
28860
+ //#endregion
28861
+ //#region src/local/cloudfront-edge-event.ts
28862
+ /** Convert a normalized HTTP header map into the Lambda@Edge multi-map. */
28863
+ function httpHeadersToEdge(headers) {
28864
+ const out = {};
28865
+ for (const [name, values] of Object.entries(headers)) {
28866
+ const lower = name.toLowerCase();
28867
+ out[lower] = values.map((value) => ({
28868
+ key: name,
28869
+ value
28870
+ }));
28871
+ }
28872
+ return out;
28873
+ }
28874
+ /**
28875
+ * Flatten a Lambda@Edge header multi-map into a single-valued `{ name: value }`
28876
+ * map (comma-joining duplicates), EXCEPT `set-cookie`, whose values are returned
28877
+ * separately so multiple cookies survive. Pseudo / read-only headers CloudFront
28878
+ * forbids a function from setting are dropped.
28879
+ */
28880
+ function edgeHeadersToHttp(headers) {
28881
+ const out = {};
28882
+ const setCookies = [];
28883
+ for (const [name, entries] of Object.entries(headers)) {
28884
+ if (!Array.isArray(entries)) continue;
28885
+ const lower = name.toLowerCase();
28886
+ if (READ_ONLY_RESPONSE_HEADERS.has(lower)) continue;
28887
+ if (lower === "set-cookie") {
28888
+ for (const e of entries) if (e && typeof e.value === "string") setCookies.push(e.value);
28889
+ continue;
28890
+ }
28891
+ const values = entries.filter((e) => e && typeof e.value === "string").map((e) => e.value);
28892
+ if (values.length > 0) out[lower] = values.join(", ");
28893
+ }
28894
+ return {
28895
+ headers: out,
28896
+ setCookies
28897
+ };
28898
+ }
28899
+ /**
28900
+ * Headers a Lambda@Edge function is not allowed to add/modify on a response (a
28901
+ * subset CloudFront blackholes). We drop them rather than fail the response.
28902
+ */
28903
+ const READ_ONLY_RESPONSE_HEADERS = new Set([
28904
+ "connection",
28905
+ "content-length",
28906
+ "keep-alive",
28907
+ "proxy-authenticate",
28908
+ "proxy-authorization",
28909
+ "te",
28910
+ "trailer",
28911
+ "transfer-encoding",
28912
+ "upgrade"
28913
+ ]);
28914
+ /** Build the request-stage event (`viewer-request` / `origin-request`). */
28915
+ function buildEdgeRequestEvent(args) {
28916
+ return { Records: [{ cf: {
28917
+ config: {
28918
+ ...args.config,
28919
+ eventType: args.eventType
28920
+ },
28921
+ request: toEdgeRequest(args.request, args.includeBody)
28922
+ } }] };
28923
+ }
28924
+ /** Build the response-stage event (`origin-response` / `viewer-response`). */
28925
+ function buildEdgeResponseEvent(args) {
28926
+ return { Records: [{ cf: {
28927
+ config: {
28928
+ ...args.config,
28929
+ eventType: args.eventType
28930
+ },
28931
+ request: toEdgeRequest(args.request, false),
28932
+ response: {
28933
+ status: String(args.response.statusCode),
28934
+ statusDescription: statusText(args.response.statusCode),
28935
+ headers: httpHeadersToEdge(toMultiMap(args.response.headers))
28936
+ }
28937
+ } }] };
28938
+ }
28939
+ function toEdgeRequest(input, includeBody) {
28940
+ const request = {
28941
+ clientIp: input.clientIp,
28942
+ method: input.method,
28943
+ uri: input.uri,
28944
+ querystring: input.querystring,
28945
+ headers: httpHeadersToEdge(input.headers)
28946
+ };
28947
+ if (includeBody) request.body = {
28948
+ action: "read-only",
28949
+ data: (input.body ?? Buffer.alloc(0)).toString("base64"),
28950
+ encoding: "base64",
28951
+ inputTruncated: false
28952
+ };
28953
+ return request;
28954
+ }
28955
+ function interpretEdgeRequestResult(result, fallback) {
28956
+ if (!result || typeof result !== "object") return {
28957
+ kind: "continue",
28958
+ request: fallback
28959
+ };
28960
+ const obj = result;
28961
+ if ("status" in obj) return {
28962
+ kind: "response",
28963
+ response: coerceEdgeResponse(obj)
28964
+ };
28965
+ return {
28966
+ kind: "continue",
28967
+ request: coerceEdgeRequest(obj, fallback)
28968
+ };
28969
+ }
28970
+ /** Interpret a response-stage handler's return value as the (modified) response. */
28971
+ function interpretEdgeResponseResult(result, fallback) {
28972
+ if (!result || typeof result !== "object") return fallback;
28973
+ return coerceEdgeResponse(result, fallback);
28974
+ }
28975
+ /** Collapse an {@link EdgeResponse} into the server's status / headers / body. */
28976
+ function edgeResponseToResult(response, fallbackBody) {
28977
+ const statusCode = Number.parseInt(response.status, 10);
28978
+ const { headers, setCookies } = edgeHeadersToHttp(response.headers ?? {});
28979
+ let body;
28980
+ if (response.body !== void 0) body = response.bodyEncoding === "base64" ? Buffer.from(response.body, "base64") : Buffer.from(response.body);
28981
+ else body = fallbackBody ?? Buffer.alloc(0);
28982
+ return {
28983
+ statusCode: Number.isFinite(statusCode) ? statusCode : 500,
28984
+ headers,
28985
+ setCookies,
28986
+ body
28987
+ };
28988
+ }
28989
+ /**
28990
+ * Server-facing request-stage orchestration: given a handler's raw return value
28991
+ * and the current request input, produce either the (modified) request to
28992
+ * continue with, or the generated response to short-circuit.
28993
+ */
28994
+ function applyEdgeRequestResult(result, base) {
28995
+ const outcome = interpretEdgeRequestResult(result, toEdgeRequest(base, false));
28996
+ if (outcome.kind === "response") return {
28997
+ kind: "response",
28998
+ response: edgeResponseToResult(outcome.response)
28999
+ };
29000
+ return {
29001
+ kind: "continue",
29002
+ request: applyEdgeRequest(base, outcome.request)
29003
+ };
29004
+ }
29005
+ /**
29006
+ * Server-facing response-stage orchestration: apply a handler's modified
29007
+ * response over the current status / headers, keeping the origin body unless the
29008
+ * function replaced it.
29009
+ */
29010
+ function applyEdgeResponseResult(result, base, originBody) {
29011
+ return edgeResponseToResult(interpretEdgeResponseResult(result, {
29012
+ status: String(base.statusCode),
29013
+ headers: httpHeadersToEdge(toMultiMap(base.headers))
29014
+ }), originBody);
29015
+ }
29016
+ /** Apply a request-stage function's modified request back onto the server's request input. */
29017
+ function applyEdgeRequest(base, request) {
29018
+ const out = {
29019
+ clientIp: base.clientIp,
29020
+ method: typeof request.method === "string" ? request.method : base.method,
29021
+ uri: typeof request.uri === "string" ? request.uri : base.uri,
29022
+ querystring: typeof request.querystring === "string" ? request.querystring : base.querystring,
29023
+ headers: edgeHeadersToRawMap(request.headers) ?? base.headers
29024
+ };
29025
+ if (request.body && request.body.action === "replace" && typeof request.body.data === "string") out.body = request.body.encoding === "base64" ? Buffer.from(request.body.data, "base64") : Buffer.from(request.body.data);
29026
+ else if (base.body !== void 0) out.body = base.body;
29027
+ return out;
29028
+ }
29029
+ function edgeHeadersToRawMap(headers) {
29030
+ if (!headers || typeof headers !== "object") return void 0;
29031
+ const out = {};
29032
+ for (const [name, entries] of Object.entries(headers)) {
29033
+ if (!Array.isArray(entries)) continue;
29034
+ out[name.toLowerCase()] = entries.filter((e) => e && typeof e.value === "string").map((e) => e.value);
29035
+ }
29036
+ return out;
29037
+ }
29038
+ function coerceEdgeRequest(obj, fallback) {
29039
+ const out = {
29040
+ clientIp: typeof obj["clientIp"] === "string" ? obj["clientIp"] : fallback.clientIp,
29041
+ method: typeof obj["method"] === "string" ? obj["method"] : fallback.method,
29042
+ uri: typeof obj["uri"] === "string" ? obj["uri"] : fallback.uri,
29043
+ querystring: typeof obj["querystring"] === "string" ? obj["querystring"] : fallback.querystring,
29044
+ headers: coerceEdgeHeaders(obj["headers"]) ?? fallback.headers
29045
+ };
29046
+ const body = obj["body"];
29047
+ if (body && typeof body === "object") {
29048
+ const b = body;
29049
+ out.body = {
29050
+ action: b["action"] === "replace" ? "replace" : "read-only",
29051
+ data: typeof b["data"] === "string" ? b["data"] : "",
29052
+ encoding: b["encoding"] === "text" ? "text" : "base64",
29053
+ inputTruncated: b["inputTruncated"] === true
29054
+ };
29055
+ }
29056
+ return out;
29057
+ }
29058
+ function coerceEdgeResponse(obj, fallback) {
29059
+ const out = {
29060
+ status: typeof obj["status"] === "string" ? obj["status"] : typeof obj["status"] === "number" ? String(obj["status"]) : fallback?.status ?? "200",
29061
+ headers: coerceEdgeHeaders(obj["headers"]) ?? fallback?.headers ?? {}
29062
+ };
29063
+ if (typeof obj["statusDescription"] === "string") out.statusDescription = obj["statusDescription"];
29064
+ else if (fallback?.statusDescription !== void 0) out.statusDescription = fallback.statusDescription;
29065
+ if (typeof obj["body"] === "string") {
29066
+ out.body = obj["body"];
29067
+ if (obj["bodyEncoding"] === "base64" || obj["bodyEncoding"] === "text") out.bodyEncoding = obj["bodyEncoding"];
29068
+ }
29069
+ return out;
29070
+ }
29071
+ /** Coerce an arbitrary value into the EdgeHeaders shape, tolerating a bare `{name: 'value'}` map. */
29072
+ function coerceEdgeHeaders(value) {
29073
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
29074
+ const out = {};
29075
+ for (const [name, raw] of Object.entries(value)) {
29076
+ const lower = name.toLowerCase();
29077
+ if (Array.isArray(raw)) out[lower] = raw.filter((e) => !!e && typeof e === "object").map((e) => ({
29078
+ key: typeof e["key"] === "string" ? e["key"] : name,
29079
+ value: typeof e["value"] === "string" ? e["value"] : String(e["value"] ?? "")
29080
+ }));
29081
+ else if (typeof raw === "string") out[lower] = [{
29082
+ key: name,
29083
+ value: raw
29084
+ }];
29085
+ }
29086
+ return out;
29087
+ }
29088
+ function toMultiMap(headers) {
29089
+ const out = {};
29090
+ for (const [name, value] of Object.entries(headers)) out[name] = [value];
29091
+ return out;
29092
+ }
29093
+ function statusText(code) {
29094
+ return STATUS_TEXT[code] ?? "";
29095
+ }
29096
+ const STATUS_TEXT = {
29097
+ 200: "OK",
29098
+ 201: "Created",
29099
+ 204: "No Content",
29100
+ 301: "Moved Permanently",
29101
+ 302: "Found",
29102
+ 303: "See Other",
29103
+ 304: "Not Modified",
29104
+ 307: "Temporary Redirect",
29105
+ 308: "Permanent Redirect",
29106
+ 400: "Bad Request",
29107
+ 401: "Unauthorized",
29108
+ 403: "Forbidden",
29109
+ 404: "Not Found",
29110
+ 500: "Internal Server Error",
29111
+ 502: "Bad Gateway",
29112
+ 503: "Service Unavailable"
29113
+ };
29114
+
28638
29115
  //#endregion
28639
29116
  //#region src/local/cloudfront-lambda-origin.ts
28640
29117
  /**
@@ -28819,9 +29296,10 @@ function contentTypeForKey(key) {
28819
29296
  async function startCloudFrontServer(options) {
28820
29297
  const logger = getLogger().child("cloudfront");
28821
29298
  const lambdaInvokers = options.lambdaInvokers ?? /* @__PURE__ */ new Map();
29299
+ const edgeInvokers = options.edgeInvokers ?? /* @__PURE__ */ new Map();
28822
29300
  const state = { distribution: options.distribution };
28823
29301
  const handler = (req, res) => {
28824
- handleRequest$1(req, res, state, lambdaInvokers, logger).catch((err) => {
29302
+ handleRequest$1(req, res, state, lambdaInvokers, edgeInvokers, logger).catch((err) => {
28825
29303
  logger.warn(`Request handling failed: ${err instanceof Error ? err.message : String(err)}`);
28826
29304
  if (!res.headersSent) {
28827
29305
  res.statusCode = 500;
@@ -28851,7 +29329,7 @@ async function startCloudFrontServer(options) {
28851
29329
  };
28852
29330
  }
28853
29331
  /** The per-request pipeline: behavior match -> viewer-request -> origin -> viewer-response. */
28854
- async function handleRequest$1(req, res, state, lambdaInvokers, logger) {
29332
+ async function handleRequest$1(req, res, state, lambdaInvokers, edgeInvokers, logger) {
28855
29333
  const distribution = state.distribution;
28856
29334
  const rawUrl = req.url ?? "/";
28857
29335
  const queryIdx = rawUrl.indexOf("?");
@@ -28874,37 +29352,72 @@ async function handleRequest$1(req, res, state, lambdaInvokers, logger) {
28874
29352
  return;
28875
29353
  }
28876
29354
  }
29355
+ const config = {
29356
+ distributionDomainName: req.headers.host ?? "localhost",
29357
+ distributionId: distribution.logicalId,
29358
+ requestId: `cdkl-${Date.now().toString(36)}-${Math.floor(performance.now()).toString(36)}`
29359
+ };
29360
+ const clientIp = req.socket.remoteAddress ?? "127.0.0.1";
28877
29361
  let requestEvent = buildViewerRequestEvent({
28878
29362
  method: req.method ?? "GET",
28879
29363
  uri,
28880
29364
  querystring,
28881
29365
  headers: req.headers,
28882
- ip: req.socket.remoteAddress ?? "127.0.0.1",
29366
+ ip: clientIp,
28883
29367
  distributionId: distribution.logicalId,
28884
29368
  domainName: req.headers.host ?? "localhost",
28885
- requestId: `cdkl-${Date.now().toString(36)}-${Math.floor(performance.now()).toString(36)}`
29369
+ requestId: config.requestId
28886
29370
  });
28887
- let effectiveUri = uri;
28888
29371
  if (behavior.viewerRequest) {
28889
29372
  const outcome = await runViewerRequest(behavior.viewerRequest, requestEvent);
28890
29373
  if (outcome.kind === "response") {
28891
29374
  writeCfResponse(res, outcome.response, logger);
28892
29375
  return;
28893
29376
  }
28894
- effectiveUri = outcome.request.uri;
28895
29377
  requestEvent = {
28896
29378
  ...requestEvent,
28897
29379
  request: outcome.request
28898
29380
  };
28899
29381
  }
29382
+ let edgeInput = {
29383
+ clientIp,
29384
+ method: requestEvent.request.method,
29385
+ uri: requestEvent.request.uri,
29386
+ querystring: serializeCfQueryString(requestEvent.request.querystring),
29387
+ headers: cfHeadersToRawMap(requestEvent.request.headers)
29388
+ };
29389
+ for (const eventType of ["viewer-request", "origin-request"]) {
29390
+ const assoc = eventType === "viewer-request" ? behavior.lambdaEdge?.viewerRequest : behavior.lambdaEdge?.originRequest;
29391
+ if (!assoc) continue;
29392
+ const invoke = edgeInvokers.get(assoc.functionLogicalId);
29393
+ if (!invoke) {
29394
+ logger.warn(`Lambda@Edge ${eventType} function '${assoc.functionLogicalId}' was not booted (added after start-up); skipping. Restart start-cloudfront.`);
29395
+ continue;
29396
+ }
29397
+ if (assoc.includeBody && edgeInput.body === void 0) edgeInput = {
29398
+ ...edgeInput,
29399
+ body: await readRequestBody(req)
29400
+ };
29401
+ const outcome = applyEdgeRequestResult(await invoke(buildEdgeRequestEvent({
29402
+ eventType,
29403
+ config,
29404
+ request: edgeInput,
29405
+ includeBody: assoc.includeBody
29406
+ })), edgeInput);
29407
+ if (outcome.kind === "response") {
29408
+ writeEdgeResponse(res, outcome.response, behavior, req, logger);
29409
+ return;
29410
+ }
29411
+ edgeInput = outcome.request;
29412
+ }
28900
29413
  const origin = distribution.origins.get(behavior.targetOriginId);
28901
- const originResult = await serveFromOrigin(origin, behavior, {
28902
- uri: effectiveUri,
28903
- querystring,
28904
- method: req.method ?? "GET",
28905
- headers: req.headers,
28906
- readBody: () => readRequestBody(req),
28907
- sourceIp: req.socket.remoteAddress ?? "127.0.0.1",
29414
+ const originResult = await serveFromOrigin(origin, {
29415
+ uri: edgeInput.uri,
29416
+ querystring: edgeInput.querystring,
29417
+ method: edgeInput.method,
29418
+ headers: rawMapToIncomingHeaders(edgeInput.headers),
29419
+ readBody: () => edgeInput.body !== void 0 ? Promise.resolve(edgeInput.body) : readRequestBody(req),
29420
+ sourceIp: clientIp,
28908
29421
  distribution,
28909
29422
  lambdaInvokers,
28910
29423
  logger
@@ -28912,16 +29425,47 @@ async function handleRequest$1(req, res, state, lambdaInvokers, logger) {
28912
29425
  if (!originResult) return writePlain(res, 502, originUnavailableMessage(origin, behavior));
28913
29426
  let finalStatus = originResult.statusCode;
28914
29427
  let finalHeaders = originResult.headers;
29428
+ let finalBody = originResult.body;
29429
+ const setCookies = [...originResult.setCookies ?? []];
29430
+ if (behavior.lambdaEdge?.originResponse) {
29431
+ const r = await runEdgeResponseStage(behavior.lambdaEdge.originResponse, "origin-response", {
29432
+ config,
29433
+ request: edgeInput,
29434
+ statusCode: finalStatus,
29435
+ headers: finalHeaders,
29436
+ body: finalBody
29437
+ }, edgeInvokers, logger);
29438
+ if (r) {
29439
+ finalStatus = r.statusCode;
29440
+ finalHeaders = r.headers;
29441
+ finalBody = r.body;
29442
+ setCookies.push(...r.setCookies);
29443
+ }
29444
+ }
28915
29445
  if (behavior.viewerResponse) {
28916
29446
  const responseEvent = buildViewerResponseEvent(requestEvent, {
28917
- statusCode: originResult.statusCode,
28918
- headers: originResult.headers
29447
+ statusCode: finalStatus,
29448
+ headers: finalHeaders
28919
29449
  });
28920
29450
  const mutated = await runViewerResponse(behavior.viewerResponse, responseEvent);
28921
29451
  finalStatus = mutated.statusCode;
28922
- finalHeaders = cfHeadersToPlain(mutated.headers, originResult.headers);
29452
+ finalHeaders = cfHeadersToPlain(mutated.headers, finalHeaders);
29453
+ }
29454
+ if (behavior.lambdaEdge?.viewerResponse) {
29455
+ const r = await runEdgeResponseStage(behavior.lambdaEdge.viewerResponse, "viewer-response", {
29456
+ config,
29457
+ request: edgeInput,
29458
+ statusCode: finalStatus,
29459
+ headers: finalHeaders,
29460
+ body: finalBody
29461
+ }, edgeInvokers, logger);
29462
+ if (r) {
29463
+ finalStatus = r.statusCode;
29464
+ finalHeaders = r.headers;
29465
+ finalBody = r.body;
29466
+ setCookies.push(...r.setCookies);
29467
+ }
28923
29468
  }
28924
- const setCookies = [...originResult.setCookies ?? []];
28925
29469
  for (const name of Object.keys(finalHeaders)) if (name.toLowerCase() === "set-cookie") {
28926
29470
  setCookies.push(finalHeaders[name]);
28927
29471
  delete finalHeaders[name];
@@ -28937,7 +29481,54 @@ async function handleRequest$1(req, res, state, lambdaInvokers, logger) {
28937
29481
  const origin = req.headers.origin;
28938
29482
  applyCorsResponseHeadersFromConfig(res, behavior.cors, typeof origin === "string" ? origin : void 0);
28939
29483
  }
28940
- res.end(originResult.body);
29484
+ res.end(finalBody);
29485
+ }
29486
+ /** Run a Lambda@Edge response-stage function, returning the modified response (or undefined when no invoker). */
29487
+ async function runEdgeResponseStage(assoc, eventType, ctx, edgeInvokers, logger) {
29488
+ const invoke = edgeInvokers.get(assoc.functionLogicalId);
29489
+ if (!invoke) {
29490
+ logger.warn(`Lambda@Edge ${eventType} function '${assoc.functionLogicalId}' was not booted (added after start-up); skipping. Restart start-cloudfront.`);
29491
+ return;
29492
+ }
29493
+ return applyEdgeResponseResult(await invoke(buildEdgeResponseEvent({
29494
+ eventType,
29495
+ config: ctx.config,
29496
+ request: ctx.request,
29497
+ response: {
29498
+ statusCode: ctx.statusCode,
29499
+ headers: ctx.headers
29500
+ }
29501
+ })), {
29502
+ statusCode: ctx.statusCode,
29503
+ headers: ctx.headers
29504
+ }, ctx.body);
29505
+ }
29506
+ /** Write a Lambda@Edge generated response (request-stage short-circuit), incl. CORS. */
29507
+ function writeEdgeResponse(res, result, behavior, req, logger) {
29508
+ if (!req.readableEnded) req.resume();
29509
+ res.statusCode = result.statusCode;
29510
+ setHeadersSafely(res, result.headers, logger);
29511
+ if (result.setCookies.length > 0) try {
29512
+ res.setHeader("set-cookie", result.setCookies);
29513
+ } catch {}
29514
+ if (behavior.cors) {
29515
+ const origin = req.headers.origin;
29516
+ applyCorsResponseHeadersFromConfig(res, behavior.cors, typeof origin === "string" ? origin : void 0);
29517
+ }
29518
+ res.end(result.body);
29519
+ }
29520
+ /** Convert a CloudFront-Function header map (`{name: CfValue}`) into the raw `{name: string[]}` form. */
29521
+ function cfHeadersToRawMap(cf) {
29522
+ const out = {};
29523
+ for (const [name, val] of Object.entries(cf)) if (val.multiValue && val.multiValue.length > 0) out[name] = val.multiValue.map((m) => m.value);
29524
+ else out[name] = [val.value];
29525
+ return out;
29526
+ }
29527
+ /** Convert a raw `{name: string[]}` header map into Node's `IncomingHttpHeaders` shape. */
29528
+ function rawMapToIncomingHeaders(headers) {
29529
+ const out = {};
29530
+ for (const [name, values] of Object.entries(headers)) out[name] = values.length === 1 ? values[0] : values;
29531
+ return out;
28941
29532
  }
28942
29533
  /**
28943
29534
  * Convert Node's `IncomingMessage.headers` (`Record<string, string | string[]>`)
@@ -28974,9 +29565,8 @@ function setHeadersSafely(res, headers, logger) {
28974
29565
  logger.warn(`Skipping invalid response header '${name}': ${err instanceof Error ? err.message : String(err)}`);
28975
29566
  }
28976
29567
  }
28977
- async function serveFromOrigin(origin, behavior, args) {
28978
- const { distribution, logger } = args;
28979
- if (behavior.hasLambdaEdge) logger.warn(`Behavior ${behavior.pathPattern ?? "(default)"} carries a Lambda@Edge association; cdk-local does not run Lambda@Edge — serving the origin only.`);
29568
+ async function serveFromOrigin(origin, args) {
29569
+ const { distribution } = args;
28980
29570
  if (!origin) return void 0;
28981
29571
  if (origin.kind === "lambda-url") {
28982
29572
  const invoke = args.lambdaInvokers.get(origin.functionLogicalId);
@@ -29073,6 +29663,130 @@ function listen(server, host, port) {
29073
29663
  });
29074
29664
  }
29075
29665
 
29666
+ //#endregion
29667
+ //#region src/local/cloudfront-kvs-client.ts
29668
+ /**
29669
+ * Build a {@link KvsDataSource} backed by the deployed store's `GetKey` API.
29670
+ * A missing key resolves to `undefined` (the `ResourceNotFoundException` is
29671
+ * caught) so the `cf` shim can surface a clean "key not found" — any other
29672
+ * error (access denied, throttling) propagates so the user sees the real cause.
29673
+ */
29674
+ function createDeployedKvsDataSource(options) {
29675
+ const client = new CloudFrontKeyValueStoreClient({
29676
+ region: options.region ?? "us-east-1",
29677
+ ...options.credentials !== void 0 && { credentials: options.credentials }
29678
+ });
29679
+ return {
29680
+ label: `deployed:${options.kvsArn}`,
29681
+ ...options.kvsId !== void 0 && { kvsId: options.kvsId },
29682
+ async getValue(key) {
29683
+ try {
29684
+ return (await client.send(new GetKeyCommand({
29685
+ KvsARN: options.kvsArn,
29686
+ Key: key
29687
+ }))).Value;
29688
+ } catch (err) {
29689
+ if (isKeyNotFound(err)) return void 0;
29690
+ throw new Error(`cf.kvs().get('${key}') against ${options.kvsArn} failed: ${err instanceof Error ? err.message : String(err)}`);
29691
+ }
29692
+ }
29693
+ };
29694
+ }
29695
+ /**
29696
+ * Resolve a deployed KeyValueStore's ARN (+ Id) from its NAME via the
29697
+ * CloudFront control-plane `ListKeyValueStores` API. Needed because
29698
+ * `--from-cfn-stack` reads the store's physical id from `ListStackResources`,
29699
+ * and for `AWS::CloudFront::KeyValueStore` that physical id is the store NAME
29700
+ * (the `Ref` value) — NOT the ARN. The data-plane `GetKey` needs the ARN (which
29701
+ * embeds the store's UUID Id), so we look the name up against the account's
29702
+ * stores. CloudFront is global, so the client defaults to `us-east-1`. Returns
29703
+ * `undefined` on any miss (no match, access denied) so the caller falls back to
29704
+ * the unbound-KVS warning.
29705
+ */
29706
+ async function resolveDeployedKvsArnByName(name, options = {}) {
29707
+ const client = new CloudFrontClient({
29708
+ region: options.region ?? "us-east-1",
29709
+ ...options.credentials !== void 0 && { credentials: options.credentials }
29710
+ });
29711
+ try {
29712
+ for await (const page of paginateListKeyValueStores({ client }, {})) for (const item of page.KeyValueStoreList?.Items ?? []) if (item.Name === name && item.ARN) return {
29713
+ arn: item.ARN,
29714
+ ...item.Id !== void 0 && { id: item.Id }
29715
+ };
29716
+ } catch (err) {
29717
+ getLogger().debug(`ListKeyValueStores lookup for '${name}' failed: ${err instanceof Error ? err.message : String(err)}`);
29718
+ }
29719
+ }
29720
+ /** True when a GetKey error means the key (or store) was not found, not a real failure. */
29721
+ function isKeyNotFound(err) {
29722
+ if (err instanceof ResourceNotFoundException) return true;
29723
+ if (err && typeof err === "object") {
29724
+ if (err.name === "ResourceNotFoundException") return true;
29725
+ if (err.$metadata?.httpStatusCode === 404) return true;
29726
+ }
29727
+ return false;
29728
+ }
29729
+
29730
+ //#endregion
29731
+ //#region src/local/cloudfront-kvs-binding.ts
29732
+ /**
29733
+ * Build + attach the `cf` module to every KVS-reading function in the
29734
+ * distribution. Returns boot warnings for associations that resolved to no
29735
+ * binding (the runtime then injects an unbound module so the read fails with a
29736
+ * clear actionable error). Idempotent per call — overwrites
29737
+ * `cloudfrontModule` so a `--watch` reload rebinds.
29738
+ */
29739
+ async function resolveKvsModulesForDistribution(distribution, options) {
29740
+ const warnings = [];
29741
+ for (const fn of collectKvsFunctions(distribution)) {
29742
+ const sources = [];
29743
+ for (const assoc of fn.kvsAssociations ?? []) {
29744
+ const source = await buildSourceForAssociation(assoc, options);
29745
+ if (source) sources.push(source);
29746
+ else {
29747
+ const ref = assoc.kvsLogicalId ?? "<store>";
29748
+ warnings.push(`CloudFront Function '${fn.logicalId}' reads KeyValueStore '${ref}', but no binding resolved it — cf.kvs() reads will fail. Pass --from-cfn-stack to read the deployed store, or --kvs-file ${ref}=<file.json> for a local map.`);
29749
+ }
29750
+ }
29751
+ if (sources.length > 0) fn.cloudfrontModule = createCloudFrontModule(sources);
29752
+ else delete fn.cloudfrontModule;
29753
+ }
29754
+ return { warnings };
29755
+ }
29756
+ /** Collect the unique compiled functions in the distribution that read a KVS. */
29757
+ function collectKvsFunctions(distribution) {
29758
+ const byLogicalId = /* @__PURE__ */ new Map();
29759
+ for (const behavior of distribution.behaviors) for (const fn of [behavior.viewerRequest, behavior.viewerResponse]) if (fn && fn.kvsAssociations && fn.kvsAssociations.length > 0) byLogicalId.set(fn.logicalId, fn);
29760
+ return [...byLogicalId.values()];
29761
+ }
29762
+ async function buildSourceForAssociation(assoc, options) {
29763
+ if (assoc.kvsLogicalId !== void 0) {
29764
+ const filePath = options.kvsFiles?.get(assoc.kvsLogicalId);
29765
+ if (filePath !== void 0) return createLocalFileKvsDataSource({
29766
+ id: assoc.kvsLogicalId,
29767
+ filePath
29768
+ });
29769
+ }
29770
+ if (typeof assoc.arnValue === "string" && assoc.arnValue.startsWith("arn:")) return makeDeployedSource(assoc.arnValue, idFromArn(assoc.arnValue), options);
29771
+ if (assoc.kvsLogicalId !== void 0 && options.resolveDeployedKvs) {
29772
+ const ref = await options.resolveDeployedKvs(assoc.kvsLogicalId);
29773
+ if (ref) return makeDeployedSource(ref.arn, ref.id, options);
29774
+ }
29775
+ }
29776
+ function makeDeployedSource(arn, id, options) {
29777
+ return createDeployedKvsDataSource({
29778
+ kvsArn: arn,
29779
+ ...id !== void 0 && { kvsId: id },
29780
+ ...options.region !== void 0 && { region: options.region },
29781
+ ...options.credentials !== void 0 && { credentials: options.credentials }
29782
+ });
29783
+ }
29784
+ /** The KeyValueStore Id is the ARN's last `/`-segment. */
29785
+ function idFromArn(arn) {
29786
+ const slash = arn.lastIndexOf("/");
29787
+ return slash >= 0 && slash < arn.length - 1 ? arn.slice(slash + 1) : void 0;
29788
+ }
29789
+
29076
29790
  //#endregion
29077
29791
  //#region src/cli/commands/local-start-cloudfront.ts
29078
29792
  /** Error thrown by `cdkl start-cloudfront` for target / option problems. */
@@ -29097,6 +29811,55 @@ function parseOriginOverrides(values) {
29097
29811
  return out;
29098
29812
  }
29099
29813
  /**
29814
+ * Parse the repeatable `--kvs-file <kvsLogicalId>=<file.json>` overrides into a
29815
+ * map. Each entry backs a CloudFront Function's `cf.kvs()` reads with a local
29816
+ * JSON map instead of the deployed store — the AWS-free escape hatch for KVS,
29817
+ * symmetric with `--origin <id>=<dir>`. The key is the
29818
+ * `AWS::CloudFront::KeyValueStore` resource logical id (surfaced in the
29819
+ * unbound-KVS boot warning when missing).
29820
+ */
29821
+ function parseKvsFileOverrides(values) {
29822
+ const out = /* @__PURE__ */ new Map();
29823
+ for (const raw of values ?? []) {
29824
+ const eq = raw.indexOf("=");
29825
+ if (eq <= 0 || eq === raw.length - 1) throw new LocalStartCloudFrontError(`Invalid --kvs-file '${raw}'. Expected <kvsLogicalId>=<file.json> (e.g. MyKvsStore=./kvs.json).`);
29826
+ out.set(raw.slice(0, eq).trim(), raw.slice(eq + 1).trim());
29827
+ }
29828
+ return out;
29829
+ }
29830
+ /**
29831
+ * Resolve + attach the `cf` KeyValueStore module to every KVS-reading
29832
+ * CloudFront Function in the distribution (run after each synth, initial +
29833
+ * `--watch` reload). A `--kvs-file` map wins; otherwise `--from-cfn-stack`
29834
+ * resolves the deployed store's ARN from state and `cf.kvs().get()` reads it
29835
+ * via the `GetKey` API. Unresolved associations log an actionable WARN (the
29836
+ * runtime then fails the read with the same guidance).
29837
+ */
29838
+ async function attachKvsModules(distribution, stacks, options, profileCredentials, logger) {
29839
+ const kvsFiles = parseKvsFileOverrides(options.kvsFile);
29840
+ const synthRegion = stacks.find((s) => s.stackName === distribution.stackName)?.region;
29841
+ const resolveDeployedKvs = isCfnFlagPresent(options) ? async (kvsLogicalId) => {
29842
+ const provider = createLocalStateProvider(options, distribution.stackName, synthRegion);
29843
+ if (!provider) return void 0;
29844
+ const physicalId = (await provider.load(distribution.stackName, synthRegion))?.resources[kvsLogicalId]?.physicalId;
29845
+ if (!physicalId) return void 0;
29846
+ if (physicalId.startsWith("arn:")) {
29847
+ const id = idFromArn(physicalId);
29848
+ return {
29849
+ arn: physicalId,
29850
+ ...id !== void 0 && { id }
29851
+ };
29852
+ }
29853
+ return resolveDeployedKvsArnByName(physicalId, { ...profileCredentials !== void 0 && { credentials: profileCredentials } });
29854
+ } : void 0;
29855
+ const { warnings } = await resolveKvsModulesForDistribution(distribution, {
29856
+ ...kvsFiles.size > 0 && { kvsFiles },
29857
+ ...resolveDeployedKvs !== void 0 && { resolveDeployedKvs },
29858
+ ...profileCredentials !== void 0 && { credentials: profileCredentials }
29859
+ });
29860
+ for (const warning of warnings) logger.warn(warning);
29861
+ }
29862
+ /**
29100
29863
  * Resolve a CloudFront target string (`Stack/Path` display path or
29101
29864
  * `Stack:LogicalId`) to its stack + `AWS::CloudFront::Distribution` logical id.
29102
29865
  * Mirrors the ALB / ECS resolver target grammar.
@@ -29181,12 +29944,58 @@ async function bootLambdaUrlOrigins(distribution, stacks, opts) {
29181
29944
  runners
29182
29945
  };
29183
29946
  }
29947
+ /**
29948
+ * Boot one warm RIE container per unique Lambda@Edge function across the
29949
+ * distribution's behaviors (issue #400), returning the runners + an invoker map
29950
+ * keyed by backing-function logical id for {@link startCloudFrontServer}. Each
29951
+ * function gets the SAME container env as a direct `cdkl invoke` (declared env
29952
+ * vars + `--from-cfn-stack` substitution + `--assume-role` creds) via the shared
29953
+ * {@link resolveLambdaContainerEnv}. A function that cannot be booted is
29954
+ * warn-and-skipped (its edge stage will not run); booted once at start-up, NOT
29955
+ * rebuilt on a `--watch` reload.
29956
+ */
29957
+ async function bootLambdaEdgeFunctions(distribution, stacks, opts) {
29958
+ const logger = getLogger();
29959
+ const invokers = /* @__PURE__ */ new Map();
29960
+ const runners = [];
29961
+ const functionLogicalIds = /* @__PURE__ */ new Set();
29962
+ for (const behavior of distribution.behaviors) {
29963
+ const edge = behavior.lambdaEdge;
29964
+ if (!edge) continue;
29965
+ for (const assoc of [
29966
+ edge.viewerRequest,
29967
+ edge.originRequest,
29968
+ edge.originResponse,
29969
+ edge.viewerResponse
29970
+ ]) if (assoc) functionLogicalIds.add(assoc.functionLogicalId);
29971
+ }
29972
+ for (const functionLogicalId of functionLogicalIds) try {
29973
+ const lambda = resolveLambdaTarget(functionLogicalId, stacks);
29974
+ const containerEnv = await resolveLambdaContainerEnv(lambda, opts.envOptions, opts.profileCredentials);
29975
+ const runner = createFrontDoorLambdaRunner(lambda, {
29976
+ containerHost: opts.containerHost,
29977
+ skipPull: opts.skipPull,
29978
+ ...opts.envOptions.region !== void 0 && { region: opts.envOptions.region },
29979
+ containerEnv: containerEnv.env,
29980
+ ...containerEnv.sensitiveEnvKeys.length > 0 && { sensitiveEnvKeys: new Set(containerEnv.sensitiveEnvKeys) }
29981
+ });
29982
+ logger.info(`Booting Lambda@Edge container for ${functionLogicalId} (the function runs locally via RIE)...`);
29983
+ await runner.start();
29984
+ runners.push(runner);
29985
+ invokers.set(functionLogicalId, (event) => runner.invoke(event));
29986
+ } catch (err) {
29987
+ logger.warn(`Could not boot Lambda@Edge function '${functionLogicalId}'; its stage will be skipped: ${err instanceof Error ? err.message : String(err)}`);
29988
+ }
29989
+ return {
29990
+ invokers,
29991
+ runners
29992
+ };
29993
+ }
29184
29994
  /** Emit boot-time WARNs for parts of the distribution cdk-local does not serve. */
29185
29995
  function warnUnsupported(distribution) {
29186
29996
  const logger = getLogger();
29187
29997
  for (const origin of distribution.origins.values()) if (origin.kind === "custom") logger.warn(`Origin '${origin.originId}' is a custom (non-S3, non-Lambda-Function-URL) origin (${origin.domainName}); ${getEmbedConfig().cliName} start-cloudfront serves S3 origins and Lambda Function URL origins only. Requests routed to it return 502.`);
29188
29998
  else if (origin.kind === "s3-unresolved") logger.warn(`Origin '${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). Point it at a directory with --origin ${origin.originId}=<dir>. Requests routed to it return 502.`);
29189
- if (distribution.behaviors.some((b) => b.hasLambdaEdge)) logger.warn("One or more cache behaviors carry a Lambda@Edge association; cdk-local does not run Lambda@Edge functions — only CloudFront Functions + the S3 origin are served.");
29190
29999
  }
29191
30000
  async function localStartCloudFrontCommand(target, options) {
29192
30001
  const logger = getLogger();
@@ -29241,24 +30050,34 @@ async function localStartCloudFrontCommand(target, options) {
29241
30050
  ...options.profile !== void 0 && { profile: options.profile },
29242
30051
  ...options.stackRegion !== void 0 && { stackRegion: options.stackRegion }
29243
30052
  };
29244
- const { invokers: lambdaInvokers, runners: lambdaRunners } = await bootLambdaUrlOrigins(initial.distribution, initial.stacks, {
30053
+ await attachKvsModules(initial.distribution, initial.stacks, options, profileCredentials, logger);
30054
+ const bootOpts = {
29245
30055
  containerHost: options.host,
29246
30056
  skipPull: options.pull === false,
29247
30057
  envOptions,
29248
30058
  ...profileCredentials !== void 0 && { profileCredentials }
29249
- });
29250
- let tls;
29251
- if (tlsRequested) tls = await resolveFrontDoorTlsMaterials({
29252
- certPath: options.tlsCert,
29253
- keyPath: options.tlsKey
29254
- });
29255
- const server = await startCloudFrontServer({
29256
- distribution: initial.distribution,
29257
- host: options.host,
29258
- port: basePort,
29259
- ...tls && { tls },
29260
- ...lambdaInvokers.size > 0 && { lambdaInvokers }
29261
- });
30059
+ };
30060
+ const { invokers: lambdaInvokers, runners: lambdaRunners } = await bootLambdaUrlOrigins(initial.distribution, initial.stacks, bootOpts);
30061
+ const { invokers: edgeInvokers, runners: edgeRunners } = await bootLambdaEdgeFunctions(initial.distribution, initial.stacks, bootOpts);
30062
+ let server;
30063
+ try {
30064
+ let tls;
30065
+ if (tlsRequested) tls = await resolveFrontDoorTlsMaterials({
30066
+ certPath: options.tlsCert,
30067
+ keyPath: options.tlsKey
30068
+ });
30069
+ server = await startCloudFrontServer({
30070
+ distribution: initial.distribution,
30071
+ host: options.host,
30072
+ port: basePort,
30073
+ ...tls && { tls },
30074
+ ...lambdaInvokers.size > 0 && { lambdaInvokers },
30075
+ ...edgeInvokers.size > 0 && { edgeInvokers }
30076
+ });
30077
+ } catch (err) {
30078
+ await Promise.all([...lambdaRunners, ...edgeRunners].map((r) => r.stop().catch(() => void 0)));
30079
+ throw err;
30080
+ }
29262
30081
  process.stdout.write(`CloudFront distribution serving on ${server.url} (${initial.distribution.logicalId})\n`);
29263
30082
  process.stdout.write("^C to stop.\n");
29264
30083
  let watcher;
@@ -29280,6 +30099,7 @@ async function localStartCloudFrontCommand(target, options) {
29280
30099
  try {
29281
30100
  const reloaded = await synthAndResolve();
29282
30101
  warnUnsupported(reloaded.distribution);
30102
+ await attachKvsModules(reloaded.distribution, reloaded.stacks, options, profileCredentials, logger);
29283
30103
  server.update(reloaded.distribution);
29284
30104
  logger.info("Reload complete.");
29285
30105
  } catch (err) {
@@ -29301,8 +30121,8 @@ async function localStartCloudFrontCommand(target, options) {
29301
30121
  try {
29302
30122
  await server.close();
29303
30123
  } catch {}
29304
- await Promise.all(lambdaRunners.map((r) => r.stop().catch((err) => {
29305
- logger.debug(`Lambda origin runner stop failed: ${err instanceof Error ? err.message : String(err)}`);
30124
+ await Promise.all([...lambdaRunners, ...edgeRunners].map((r) => r.stop().catch((err) => {
30125
+ logger.debug(`Lambda runner stop failed: ${err instanceof Error ? err.message : String(err)}`);
29306
30126
  })));
29307
30127
  process.exit(exitCode);
29308
30128
  };
@@ -29333,7 +30153,7 @@ function createLocalStartCloudFrontCommand(opts = {}) {
29333
30153
  * `cmd`.
29334
30154
  */
29335
30155
  function addStartCloudFrontSpecificOptions(cmd) {
29336
- 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("--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 a Lambda Function URL origin's backing Lambda to a deployed CloudFormation stack so its env vars resolve to the deployed physical IDs / exports (ListStackResources). 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. No effect on a pure-S3 distribution.")).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));
30156
+ 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 <kvsLogicalId=file.json>", "Back a CloudFront Function's KeyValueStore reads (cf.kvs().get()) with a local JSON map (repeatable). The key is the AWS::CloudFront::KeyValueStore resource logical 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 a Lambda Function URL origin's backing Lambda to a deployed CloudFormation stack so its env vars resolve to the deployed physical IDs / exports (ListStackResources). 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. No effect on a pure-S3 distribution.")).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));
29337
30157
  }
29338
30158
 
29339
30159
  //#endregion
@@ -34368,5 +35188,5 @@ function addStudioSpecificOptions(cmd) {
34368
35188
  }
34369
35189
 
34370
35190
  //#endregion
34371
- export { addRunTaskSpecificOptions as $, buildConnectEvent as $n, createLocalInvokeCommand as $t, resolveCloudFrontTarget as A, attachAuthorizers as An, parseSelectionExpressionPath as Ar, createLocalInvokeAgentCoreCommand as At, compileCloudFrontFunction as B, buildRestV1Event as Bn, AgentCoreResolutionError as Br, AGENTCORE_SIGV4_SERVICE as Bt, addListSpecificOptions as C, verifyJwtAuthorizer as Cn, resolveSingleTarget as Cr, CloudMapRegistry as Ct, addStartCloudFrontSpecificOptions as D, evaluateCachedLambdaPolicy as Dn, discoverWebSocketApis as Dr, getContainerNetworkIp as Dt, LocalStartCloudFrontError as E, computeRequestIdentityHash as En, availableWebSocketApiIdentifiers as Er, setShadowReadyTimeoutMs as Et, CLOUDFRONT_DISTRIBUTION_TYPE as F, matchPreflight as Fn, AGENTCORE_A2A_PROTOCOL as Fr, MCP_CONTAINER_PORT as Ft, createLocalStartAlbCommand as G, VtlEvaluationError as Gn, substituteImagePlaceholders as Gr, downloadAndExtractS3Bundle as Gt, runViewerResponse as H, pickResponseTemplate as Hn, resolveAgentCoreTarget as Hr, AGENTCORE_SESSION_ID_HEADER as Ht, isCloudFrontDistribution as I, matchRoute as In, AGENTCORE_AGUI_PROTOCOL as Ir, MCP_PATH as It, isApplicationLoadBalancer as J, bufferToBody as Jn, buildStsClientConfig as Jr, computeCodeImageTag as Jt, parseLbPortOverrides as K, HOST_GATEWAY_MIN_VERSION as Kn, tryResolveImageFnJoin as Kr, SUPPORTED_CODE_RUNTIMES as Kt, pickFunctionUrlLogicalIdFromOrigin as L, translateLambdaResponse as Ln, AGENTCORE_HTTP_PROTOCOL as Lr, MCP_PROTOCOL_VERSION as Lt, startCloudFrontServer as M, buildCorsConfigByApiId as Mn, discoverRoutes as Mr, A2A_CONTAINER_PORT as Mt, serveFromStaticOrigin as N, buildCorsConfigFromCloudFrontChain as Nn, pickRefLogicalId as Nr, A2A_PATH as Nt, createLocalStartCloudFrontCommand as O, invokeRequestAuthorizer as On, discoverWebSocketApisOrThrow as Or, attachContainerLogStreamer as Ot, serveLambdaUrlOrigin as P, isFunctionUrlOacFronted as Pn, resolveLambdaArnIntrinsic as Pr, a2aInvokeOnce as Pt, serviceStrategy as Q, parseConnectionsPath as Qn, addInvokeSpecificOptions as Qt, pickTargetFunctionLogicalId as R, applyAuthorizerOverlay as Rn, AGENTCORE_MCP_PROTOCOL as Rr, mcpInvokeOnce as Rt, StudioEventBus as S, verifyCognitoJwt as Sn, resolveWatchConfig as Sr, buildCloudMapIndex as St, formatTargetListing as T, buildMethodArn as Tn, listTargets as Tr, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as Tt, addAlbSpecificOptions as U, selectIntegrationResponse as Un, derivePseudoParametersFromRegion as Ur, invokeAgentCore as Ut, runViewerRequest as V, evaluateResponseParameters as Vn, pickAgentCoreCandidateStack as Vr, signAgentCoreInvocation as Vt, albStrategy as W, tryParseStatus as Wn, formatStateRemedy as Wr, waitForAgentCorePing as Wt, addStartServiceSpecificOptions as X, buildMgmtEndpointEnvUrl as Xn, toCmdArgv as Xt, resolveAlbFrontDoor as Y, ConnectionRegistry as Yn, resolveProfileCredentials as Yr, renderCodeDockerfile as Yt, createLocalStartServiceCommand as Z, handleConnectionsRequest as Zn, classifySourceChange as Zt, filterStudioTargetGroups as _, resolveServiceIntegrationParameters as _n, resolveCfnRegion as _r, resolveImageOverrides as _t, createLocalStudioCommand as a, createFileWatcher as an, resolveRuntimeFileExtension as ar, buildEcsImageResolutionContext$1 as at, renderStudioHtml as b, buildJwksUrlFromIssuer as bn, collectSsmParameterRefs as br, isLocalCdkAssetImage as bt, startStudioProxy as c, materializeLayerFromArn as cn, substituteAgainstState as cr, parseRestartPolicy as ct, createStudioDispatcher as d, filterRoutesByApiIdentifier as dn, substituteEnvVarsFromStateAsync as dr, runEcsServiceEmulator as dt, addStartApiSpecificOptions as en, buildDisconnectEvent as er, createLocalRunTaskCommand as et, filterStudioCustomResources as f, filterRoutesByApiIdentifiers as fn, LocalStateSourceError as fr, ImageOverrideError as ft, annotatePinnedEcsTargets as g, resolveSelectionExpression as gn, resolveCfnFallbackRegion as gr, parseImageOverrideFlags as gt, annotateEcsTaskPinnedTargets as h, startApiServer as hn, rejectExplicitCfnStackWithMultipleStacks as hr, mergeForService as ht, coerceStopRequest as i, createAuthorizerCache as in, resolveRuntimeCodeMountPath as ir, addImageOverrideOptions as it, matchBehavior as j, applyCorsResponseHeaders as jn, webSocketApiMatchesIdentifier as jr, invokeAgentCoreWs as jt, parseOriginOverrides as k, invokeTokenAuthorizer as kn, filterWebSocketApisByIdentifiers as kr, addInvokeAgentCoreSpecificOptions as kt, relayServeRequest as l, resolveEnvVars$1 as ln, substituteAgainstStateAsync as lr, resolveEcsAssumeRoleOption as lt, annotateAlbPinnedBackingServices as m, readMtlsMaterialsFromDisk as mn, isCfnFlagPresent as mr, enforceImageOverrideOrphans as mt, coerceRunRequest as n, createWatchPredicates as nn, architectureToPlatform as nr, addCommonEcsServiceOptions as nt, resolveServeBaseUrl as o, attachStageContext as on, resolveRuntimeImage as or, ecsClusterOption as ot, isCustomResourceLambdaTarget as p, groupRoutesByServer as pn, createLocalStateProvider as pr, buildImageOverrideTag as pt, resolveAlbTarget as q, probeHostGatewaySupport as qn, LocalInvokeBuildError as qr, buildAgentCoreCodeImage as qt, coerceServeRequest as r, resolveApiTargetSubset as rn, buildContainerImage as rr, addEcsAssumeRoleOptions as rt, createStudioServeManager as s, buildStageMap as sn, EcsTaskResolutionError as sr, parseMaxTasks as st, addStudioSpecificOptions as t, createLocalStartApiCommand as tn, buildMessageEvent as tr, MAX_TASKS_SUBNET_RANGE_CAP as tt, reinvoke as u, availableApiIdentifiers as un, substituteEnvVarsFromState as ur, resolveSharedSidecarCredentials as ut, startStudioServer as v, defaultCredentialsLoader as vn, resolveCfnStackName as vr, runImageOverrideBuilds as vt, createLocalListCommand as w, verifyJwtViaDiscovery as wn, countTargets as wr, DEFAULT_SHADOW_READY_TIMEOUT_MS as wt, createStudioStore as x, createJwksCache as xn, resolveSsmParameters as xr, listPinnedTargets as xt, toStudioTargetGroups as y, buildCognitoJwksUrl as yn, CfnLocalStateProvider as yr, describePinnedImageUri as yt, resolveCloudFrontDistribution as z, buildHttpApiV2Event as zn, AGENTCORE_RUNTIME_TYPE as zr, parseSseForJsonRpc as zt };
34372
- //# sourceMappingURL=local-studio-Downi4pd.js.map
35191
+ export { resolveCloudFrontDistribution as $, matchPreflight as $n, AGENTCORE_A2A_PROTOCOL as $r, MCP_CONTAINER_PORT as $t, parseOriginOverrides as A, filterRoutesByApiIdentifiers as An, LocalStateSourceError as Ar, ImageOverrideError as At, applyEdgeRequestResult as B, verifyCognitoJwt as Bn, resolveWatchConfig as Br, buildCloudMapIndex as Bt, addListSpecificOptions as C, createFileWatcher as Cn, resolveRuntimeFileExtension as Cr, buildEcsImageResolutionContext$1 as Ct, addStartCloudFrontSpecificOptions as D, resolveEnvVars$1 as Dn, substituteAgainstStateAsync as Dr, resolveEcsAssumeRoleOption as Dt, LocalStartCloudFrontError as E, materializeLayerFromArn as En, substituteAgainstState as Er, parseRestartPolicy as Et, resolveDeployedKvsArnByName as F, resolveServiceIntegrationParameters as Fn, resolveCfnRegion as Fr, resolveImageOverrides as Ft, httpHeadersToEdge as G, evaluateCachedLambdaPolicy as Gn, discoverWebSocketApis as Gr, getContainerNetworkIp as Gt, buildEdgeRequestEvent as H, verifyJwtViaDiscovery as Hn, countTargets as Hr, DEFAULT_SHADOW_READY_TIMEOUT_MS as Ht, matchBehavior as I, defaultCredentialsLoader as In, resolveCfnStackName as Ir, runImageOverrideBuilds as It, isCloudFrontDistribution as J, attachAuthorizers as Jn, parseSelectionExpressionPath as Jr, createLocalInvokeAgentCoreCommand as Jt, CLOUDFRONT_DISTRIBUTION_TYPE as K, invokeRequestAuthorizer as Kn, discoverWebSocketApisOrThrow as Kr, attachContainerLogStreamer as Kt, startCloudFrontServer as L, buildCognitoJwksUrl as Ln, CfnLocalStateProvider as Lr, describePinnedImageUri as Lt, idFromArn as M, readMtlsMaterialsFromDisk as Mn, isCfnFlagPresent as Mr, enforceImageOverrideOrphans as Mt, resolveKvsModulesForDistribution as N, startApiServer as Nn, rejectExplicitCfnStackWithMultipleStacks as Nr, mergeForService as Nt, createLocalStartCloudFrontCommand as O, availableApiIdentifiers as On, substituteEnvVarsFromState as Or, resolveSharedSidecarCredentials as Ot, createDeployedKvsDataSource as P, resolveSelectionExpression as Pn, resolveCfnFallbackRegion as Pr, parseImageOverrideFlags as Pt, pickTargetFunctionLogicalId as Q, isFunctionUrlOacFronted as Qn, resolveLambdaArnIntrinsic as Qr, a2aInvokeOnce as Qt, serveFromStaticOrigin as R, buildJwksUrlFromIssuer as Rn, collectSsmParameterRefs as Rr, isLocalCdkAssetImage as Rt, StudioEventBus as S, createAuthorizerCache as Sn, resolveRuntimeCodeMountPath as Sr, addImageOverrideOptions as St, formatTargetListing as T, buildStageMap as Tn, EcsTaskResolutionError as Tr, parseMaxTasks as Tt, buildEdgeResponseEvent as U, buildMethodArn as Un, listTargets as Ur, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as Ut, applyEdgeResponseResult as V, verifyJwtAuthorizer as Vn, resolveSingleTarget as Vr, CloudMapRegistry as Vt, edgeHeadersToHttp as W, computeRequestIdentityHash as Wn, availableWebSocketApiIdentifiers as Wr, setShadowReadyTimeoutMs as Wt, pickKvsLogicalIdFromArn as X, buildCorsConfigByApiId as Xn, discoverRoutes as Xr, A2A_CONTAINER_PORT as Xt, pickFunctionUrlLogicalIdFromOrigin as Y, applyCorsResponseHeaders as Yn, webSocketApiMatchesIdentifier as Yr, invokeAgentCoreWs as Yt, pickLambdaEdgeFunctionLogicalId as Z, buildCorsConfigFromCloudFrontChain as Zn, pickRefLogicalId as Zr, A2A_PATH as Zt, filterStudioTargetGroups as _, createLocalInvokeCommand as _n, buildConnectEvent as _r, addRunTaskSpecificOptions as _t, createLocalStudioCommand as a, pickAgentCoreCandidateStack as ai, signAgentCoreInvocation as an, evaluateResponseParameters as ar, createLocalFileKvsDataSource as at, renderStudioHtml as b, createWatchPredicates as bn, architectureToPlatform as br, addCommonEcsServiceOptions as bt, startStudioProxy as c, formatStateRemedy as ci, waitForAgentCorePing as cn, tryParseStatus as cr, albStrategy as ct, createStudioDispatcher as d, LocalInvokeBuildError as di, buildAgentCoreCodeImage as dn, probeHostGatewaySupport as dr, resolveAlbTarget as dt, AGENTCORE_AGUI_PROTOCOL as ei, MCP_PATH as en, matchRoute as er, compileCloudFrontFunction as et, filterStudioCustomResources as f, buildStsClientConfig as fi, computeCodeImageTag as fn, bufferToBody as fr, isApplicationLoadBalancer as ft, annotatePinnedEcsTargets as g, addInvokeSpecificOptions as gn, parseConnectionsPath as gr, serviceStrategy as gt, annotateEcsTaskPinnedTargets as h, classifySourceChange as hn, handleConnectionsRequest as hr, createLocalStartServiceCommand as ht, coerceStopRequest as i, AgentCoreResolutionError as ii, AGENTCORE_SIGV4_SERVICE as in, buildRestV1Event as ir, createCloudFrontModule as it, resolveCloudFrontTarget as j, groupRoutesByServer as jn, createLocalStateProvider as jr, buildImageOverrideTag as jt, parseKvsFileOverrides as k, filterRoutesByApiIdentifier as kn, substituteEnvVarsFromStateAsync as kr, runEcsServiceEmulator as kt, relayServeRequest as l, substituteImagePlaceholders as li, downloadAndExtractS3Bundle as ln, VtlEvaluationError as lr, createLocalStartAlbCommand as lt, annotateAlbPinnedBackingServices as m, toCmdArgv as mn, buildMgmtEndpointEnvUrl as mr, addStartServiceSpecificOptions as mt, coerceRunRequest as n, AGENTCORE_MCP_PROTOCOL as ni, mcpInvokeOnce as nn, applyAuthorizerOverlay as nr, runViewerResponse as nt, resolveServeBaseUrl as o, resolveAgentCoreTarget as oi, AGENTCORE_SESSION_ID_HEADER as on, pickResponseTemplate as or, createUnboundCloudFrontModule as ot, isCustomResourceLambdaTarget as p, resolveProfileCredentials as pi, renderCodeDockerfile as pn, ConnectionRegistry as pr, resolveAlbFrontDoor as pt, extractKvsAssociations as q, invokeTokenAuthorizer as qn, filterWebSocketApisByIdentifiers as qr, addInvokeAgentCoreSpecificOptions as qt, coerceServeRequest as r, AGENTCORE_RUNTIME_TYPE as ri, parseSseForJsonRpc as rn, buildHttpApiV2Event as rr, stripCloudFrontImport as rt, createStudioServeManager as s, derivePseudoParametersFromRegion as si, invokeAgentCore as sn, selectIntegrationResponse as sr, addAlbSpecificOptions as st, addStudioSpecificOptions as t, AGENTCORE_HTTP_PROTOCOL as ti, MCP_PROTOCOL_VERSION as tn, translateLambdaResponse as tr, runViewerRequest as tt, reinvoke as u, tryResolveImageFnJoin as ui, SUPPORTED_CODE_RUNTIMES as un, HOST_GATEWAY_MIN_VERSION as ur, parseLbPortOverrides as ut, startStudioServer as v, addStartApiSpecificOptions as vn, buildDisconnectEvent as vr, createLocalRunTaskCommand as vt, createLocalListCommand as w, attachStageContext as wn, resolveRuntimeImage as wr, ecsClusterOption as wt, createStudioStore as x, resolveApiTargetSubset as xn, buildContainerImage as xr, addEcsAssumeRoleOptions as xt, toStudioTargetGroups as y, createLocalStartApiCommand as yn, buildMessageEvent as yr, MAX_TASKS_SUBNET_RANGE_CAP as yt, serveLambdaUrlOrigin as z, createJwksCache as zn, resolveSsmParameters as zr, listPinnedTargets as zt };
35192
+ //# sourceMappingURL=local-studio--ndMb9dM.js.map