cdk-local 0.104.0 → 0.105.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.
@@ -33,6 +33,7 @@ import { Sha256 } from "@aws-crypto/sha256-js";
33
33
  import { SignatureV4 } from "@smithy/signature-v4";
34
34
  import graphlib from "graphlib";
35
35
  import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
36
+ import * as vm from "node:vm";
36
37
  import { EventEmitter } from "node:events";
37
38
 
38
39
  //#region src/cli/options.ts
@@ -926,7 +927,7 @@ function parseTarget(target) {
926
927
  function resolveLambdaTarget(target, stacks) {
927
928
  if (stacks.length === 0) throw new LocalInvokeResolutionError("No stacks found in the synthesized assembly.");
928
929
  const parsed = parseTarget(target);
929
- const stack = pickStack$4(parsed, stacks);
930
+ const stack = pickStack$5(parsed, stacks);
930
931
  const template = stack.template;
931
932
  const resources = template.Resources ?? {};
932
933
  let match;
@@ -960,7 +961,7 @@ function resolveLambdaTarget(target, stacks) {
960
961
  * user may omit the stack prefix. Otherwise an explicit stack pattern is
961
962
  * required.
962
963
  */
963
- function pickStack$4(parsed, stacks) {
964
+ function pickStack$5(parsed, stacks) {
964
965
  if (parsed.stackPattern === null) {
965
966
  if (stacks.length === 1) return stacks[0];
966
967
  throw new LocalInvokeResolutionError(`Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
@@ -1369,7 +1370,7 @@ var AgentCoreResolutionError = class AgentCoreResolutionError extends Error {
1369
1370
  function resolveAgentCoreTarget(target, stacks, imageContext) {
1370
1371
  if (stacks.length === 0) throw new AgentCoreResolutionError("No stacks found in the synthesized assembly.");
1371
1372
  const parsed = parseTarget(target);
1372
- const stack = pickStack$3(parsed, stacks);
1373
+ const stack = pickStack$4(parsed, stacks);
1373
1374
  const resources = stack.template.Resources ?? {};
1374
1375
  const { logicalId, resource } = matchRuntime(parsed, target, stack, resources);
1375
1376
  if (resource.Type !== "AWS::BedrockAgentCore::Runtime") throw new AgentCoreResolutionError(`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not ${AGENTCORE_RUNTIME_TYPE}. ${getEmbedConfig().cliName} invoke-agentcore only runs Bedrock AgentCore Runtime resources.`);
@@ -1397,7 +1398,7 @@ function pickAgentCoreCandidateStack(target, stacks) {
1397
1398
  * Mirrors the Lambda / ECS resolvers' behavior via the shared
1398
1399
  * stack-matcher.
1399
1400
  */
1400
- function pickStack$3(parsed, stacks) {
1401
+ function pickStack$4(parsed, stacks) {
1401
1402
  if (parsed.stackPattern === null) {
1402
1403
  if (stacks.length === 1) return stacks[0];
1403
1404
  throw new AgentCoreResolutionError(`Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
@@ -3397,7 +3398,8 @@ function listTargets(stacks) {
3397
3398
  ecsServices: sortEntries(scanByType(stacks, "AWS::ECS::Service")),
3398
3399
  ecsTaskDefinitions: sortEntries(scanByType(stacks, "AWS::ECS::TaskDefinition")),
3399
3400
  agentCoreRuntimes: sortEntries(scanByType(stacks, AGENTCORE_RUNTIME_TYPE)),
3400
- loadBalancers: sortEntries(scanApplicationLoadBalancers(stacks))
3401
+ loadBalancers: sortEntries(scanApplicationLoadBalancers(stacks)),
3402
+ cloudFrontDistributions: sortEntries(scanByType(stacks, "AWS::CloudFront::Distribution"))
3401
3403
  };
3402
3404
  }
3403
3405
  const pathOf$1 = (e) => e.displayPath ?? e.qualifiedId;
@@ -3429,7 +3431,7 @@ function sortApiEntries(entries) {
3429
3431
  }
3430
3432
  /** Total number of targets across every category. */
3431
3433
  function countTargets(listing) {
3432
- return listing.lambdas.length + listing.apis.length + listing.ecsServices.length + listing.ecsTaskDefinitions.length + listing.agentCoreRuntimes.length + listing.loadBalancers.length;
3434
+ return listing.lambdas.length + listing.apis.length + listing.ecsServices.length + listing.ecsTaskDefinitions.length + listing.agentCoreRuntimes.length + listing.loadBalancers.length + listing.cloudFrontDistributions.length;
3433
3435
  }
3434
3436
 
3435
3437
  //#endregion
@@ -5691,7 +5693,7 @@ function parseEcsTarget(target) {
5691
5693
  function resolveEcsTaskTarget(target, stacks, context) {
5692
5694
  if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
5693
5695
  const parsed = parseEcsTarget(target);
5694
- const stack = pickStack$2(parsed, stacks);
5696
+ const stack = pickStack$3(parsed, stacks);
5695
5697
  const resources = stack.template.Resources ?? {};
5696
5698
  let logicalId;
5697
5699
  let resource;
@@ -5712,7 +5714,7 @@ function resolveEcsTaskTarget(target, stacks, context) {
5712
5714
  if (resource.Type !== "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not an AWS::ECS::TaskDefinition.`);
5713
5715
  return extractTaskDefinitionProperties(stack, logicalId, resource, context);
5714
5716
  }
5715
- function pickStack$2(parsed, stacks) {
5717
+ function pickStack$3(parsed, stacks) {
5716
5718
  if (parsed.stackPattern === null) {
5717
5719
  if (stacks.length === 1) return stacks[0];
5718
5720
  throw new EcsTaskResolutionError(`Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
@@ -13553,7 +13555,7 @@ async function startApiServer(opts) {
13553
13555
  const logger = getLogger().child("start-api");
13554
13556
  let currentState = opts.state;
13555
13557
  const requestHandler = (req, res) => {
13556
- handleRequest$1(req, res, currentState, opts).catch((err) => {
13558
+ handleRequest$2(req, res, currentState, opts).catch((err) => {
13557
13559
  logger.error(`Unhandled request error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
13558
13560
  if (!res.headersSent) writeError$1(res, 502);
13559
13561
  });
@@ -13622,7 +13624,7 @@ async function startApiServer(opts) {
13622
13624
  * route handler.
13623
13625
  * 4. Build event, acquire container, invoke RIE, translate response.
13624
13626
  */
13625
- async function handleRequest$1(req, res, state, opts) {
13627
+ async function handleRequest$2(req, res, state, opts) {
13626
13628
  const logger = getLogger().child("start-api");
13627
13629
  if (opts.preDispatch) try {
13628
13630
  if (await opts.preDispatch(req, res)) return;
@@ -19142,7 +19144,7 @@ async function resolveAgentCoreCodeImage(resolved, code, options, architecture,
19142
19144
  const loader = new AssetManifestLoader();
19143
19145
  const manifest = await loader.loadManifest(cdkOutDir, resolved.stack.stackName);
19144
19146
  const fileAssets = manifest ? loader.getFileAssets(manifest) : void 0;
19145
- const asset = fileAssets ? fileAssets.get(code.codeAssetHash) ?? findFileAssetByObjectKey(fileAssets, code.codeAssetHash) : void 0;
19147
+ const asset = fileAssets ? fileAssets.get(code.codeAssetHash) ?? findFileAssetByObjectKey$1(fileAssets, code.codeAssetHash) : void 0;
19146
19148
  if (!asset) throw new CdkLocalError(`AgentCore Runtime '${resolved.logicalId}' code bundle (asset ${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");
19147
19149
  const sourceDir = loader.getAssetSourcePath(cdkOutDir, asset);
19148
19150
  if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) throw new CdkLocalError(`AgentCore Runtime '${resolved.logicalId}' code bundle source '${sourceDir}' does not exist or is not a directory. Re-synthesize the app and retry.`, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_MISSING");
@@ -19202,7 +19204,7 @@ async function resolveAgentCoreCodeImageFromS3(resolved, code, s3Source, options
19202
19204
  * `Code.S3.Prefix`'s hash) when the source-hash-keyed lookup misses — covers a
19203
19205
  * synthesizer whose source hash differs from the destination objectKey.
19204
19206
  */
19205
- function findFileAssetByObjectKey(fileAssets, hash) {
19207
+ function findFileAssetByObjectKey$1(fileAssets, hash) {
19206
19208
  const zip = `${hash}.zip`;
19207
19209
  for (const asset of fileAssets.values()) if (Object.values(asset.destinations).some((d) => d.objectKey === zip || d.objectKey.endsWith(`/${zip}`))) return asset;
19208
19210
  }
@@ -21189,7 +21191,7 @@ function shellJoin(parts) {
21189
21191
  function resolveEcsServiceTarget(target, stacks, context, options) {
21190
21192
  if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
21191
21193
  const parsed = parseEcsTarget(target);
21192
- const stack = pickStack$1(parsed, stacks);
21194
+ const stack = pickStack$2(parsed, stacks);
21193
21195
  const resources = stack.template.Resources ?? {};
21194
21196
  let serviceLogicalId;
21195
21197
  let serviceResource;
@@ -21440,7 +21442,7 @@ function deriveServiceDisplayName(rawServiceName, serviceLogicalId, metadata) {
21440
21442
  * service-specific extensions (e.g. cross-stack service-to-task refs)
21441
21443
  * can diverge without breaking the run-task code path.
21442
21444
  */
21443
- function pickStack$1(parsed, stacks) {
21445
+ function pickStack$2(parsed, stacks) {
21444
21446
  if (parsed.stackPattern === null) {
21445
21447
  if (stacks.length === 1) return stacks[0];
21446
21448
  throw new EcsTaskResolutionError(`Target has no stack prefix, and the assembly contains ${stacks.length} stacks: ${stacks.map((s) => s.stackName).join(", ")}. Pass the target as 'Stack/Path' or 'Stack:LogicalId'.`);
@@ -23945,6 +23947,13 @@ function ruleMatches(rule, requestPath, requestQuery, requestHost, facts) {
23945
23947
  return true;
23946
23948
  }
23947
23949
  /**
23950
+ * Whether a single ALB `path-pattern` value matches a request path. The path
23951
+ * must already be query-stripped, or pass a raw URL and it is stripped here.
23952
+ */
23953
+ function albPathPatternMatches(pattern, requestPath) {
23954
+ return globToRegExp$1(pattern, false).test(pathOf(requestPath));
23955
+ }
23956
+ /**
23948
23957
  * Whether an `http-header` condition matches the request's headers. The
23949
23958
  * header name is looked up case-insensitively; each listed value glob is
23950
23959
  * matched case-insensitively against every value of that header (multi-valued
@@ -27740,7 +27749,7 @@ function parseLbPortOverrides(values) {
27740
27749
  function resolveAlbTarget(target, stacks) {
27741
27750
  if (stacks.length === 0) throw new LocalStartServiceError("No stacks found in the synthesized assembly.");
27742
27751
  const parsed = parseEcsTarget(target);
27743
- const stack = pickStack(parsed.stackPattern, stacks, target);
27752
+ const stack = pickStack$1(parsed.stackPattern, stacks, target);
27744
27753
  const resources = stack.template.Resources ?? {};
27745
27754
  if (parsed.isPath) {
27746
27755
  const index = buildCdkPathIndex(stack.template);
@@ -27748,7 +27757,7 @@ function resolveAlbTarget(target, stacks) {
27748
27757
  const r = resources[logicalId];
27749
27758
  return r !== void 0 && isApplicationLoadBalancer(r);
27750
27759
  });
27751
- if (albs.length === 0) throw notFound(target, stack, resources);
27760
+ if (albs.length === 0) throw notFound$1(target, stack, resources);
27752
27761
  if (albs.length > 1) throw new LocalStartServiceError(`Target '${target}' matches ${albs.length} load balancers in ${stack.stackName}: ${albs.map((a) => a.logicalId).join(", ")}. Refine the path or use the stack:LogicalId form.`);
27753
27762
  return {
27754
27763
  stack,
@@ -27756,13 +27765,13 @@ function resolveAlbTarget(target, stacks) {
27756
27765
  };
27757
27766
  }
27758
27767
  const res = resources[parsed.pathOrId];
27759
- if (!res || !isApplicationLoadBalancer(res)) throw notFound(target, stack, resources);
27768
+ if (!res || !isApplicationLoadBalancer(res)) throw notFound$1(target, stack, resources);
27760
27769
  return {
27761
27770
  stack,
27762
27771
  albLogicalId: parsed.pathOrId
27763
27772
  };
27764
27773
  }
27765
- function pickStack(stackPattern, stacks, target) {
27774
+ function pickStack$1(stackPattern, stacks, target) {
27766
27775
  if (stackPattern === null) {
27767
27776
  if (stacks.length === 1) return stacks[0];
27768
27777
  throw new LocalStartServiceError(`Target '${target}' has no stack prefix, and the assembly contains ${stacks.length} stacks: ${stacks.map((s) => s.stackName).join(", ")}. Pass it as 'Stack/Path' or 'Stack:LogicalId'.`);
@@ -27772,7 +27781,7 @@ function pickStack(stackPattern, stacks, target) {
27772
27781
  if (matched.length > 1) throw new LocalStartServiceError(`Multiple stacks match '${stackPattern}': ${matched.map((s) => s.stackName).join(", ")}. Refine the pattern.`);
27773
27782
  return matched[0];
27774
27783
  }
27775
- function notFound(target, stack, resources) {
27784
+ function notFound$1(target, stack, resources) {
27776
27785
  const albs = Object.entries(resources).filter(([, r]) => r.Type === "AWS::ElasticLoadBalancingV2::LoadBalancer").map(([logicalId]) => logicalId);
27777
27786
  const available = albs.length > 0 ? ` Available load balancers in ${stack.stackName}: ${albs.join(", ")}.` : ` ${stack.stackName} declares no AWS::ElasticLoadBalancingV2::LoadBalancer resources.`;
27778
27787
  return new LocalStartServiceError(`Target '${target}' did not match an application Load Balancer in ${stack.stackName}.${available}`);
@@ -27917,6 +27926,988 @@ function addAlbSpecificOptions(cmd) {
27917
27926
  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));
27918
27927
  }
27919
27928
 
27929
+ //#endregion
27930
+ //#region src/local/cloudfront-function-runtime.ts
27931
+ /**
27932
+ * Hard cap on a single CloudFront Function's synchronous run. The deployed
27933
+ * runtime caps CPU at ~1ms; locally we are generous but still bound it so a
27934
+ * runaway `while (true) {}` in a function fails that one request with a clear
27935
+ * error instead of wedging the single-threaded local server for every
27936
+ * subsequent request. `vm`'s timeout only interrupts synchronous code — an
27937
+ * `await`-based hang in a 2.0 async handler is not caught (it has no local
27938
+ * analogue), which is acceptable for a dev tool.
27939
+ */
27940
+ const FUNCTION_TIMEOUT_MS = 5e3;
27941
+ const EVENT_GLOBAL = "__cfEvent";
27942
+ /**
27943
+ * Compile a CloudFront Function's inline code once. Throws a clear error when
27944
+ * the code has a syntax error or declares no `handler` (surfaced at boot so a
27945
+ * malformed function never silently no-ops at request time).
27946
+ */
27947
+ function compileCloudFrontFunction(logicalId, code, runtime) {
27948
+ let script;
27949
+ try {
27950
+ script = new vm.Script(`${code}\n;handler(${EVENT_GLOBAL})`, { filename: `cloudfront-function-${logicalId}.js` });
27951
+ } catch (err) {
27952
+ throw new Error(`CloudFront Function '${logicalId}' failed to compile: ${err instanceof Error ? err.message : String(err)}`);
27953
+ }
27954
+ let hasHandler;
27955
+ try {
27956
+ const probeContext = vm.createContext({ console });
27957
+ hasHandler = new vm.Script(`${code}\n;typeof handler === 'function'`).runInContext(probeContext, { timeout: FUNCTION_TIMEOUT_MS });
27958
+ } catch (err) {
27959
+ throw new Error(`CloudFront Function '${logicalId}' failed to compile: ${err instanceof Error ? err.message : String(err)}`);
27960
+ }
27961
+ if (hasHandler !== true) throw new Error(`CloudFront Function '${logicalId}' does not declare a 'handler' function. A CloudFront Function must export \`function handler(event) { ... }\`.`);
27962
+ return {
27963
+ logicalId,
27964
+ runtime,
27965
+ script
27966
+ };
27967
+ }
27968
+ /**
27969
+ * Invoke a compiled function's `handler(event)` in a fresh sandbox and return
27970
+ * its result. The synchronous portion is bounded by {@link FUNCTION_TIMEOUT_MS}
27971
+ * (a runaway `while (true) {}` fails this one request instead of wedging the
27972
+ * server); a `cloudfront-js-2.0` async handler's promise is awaited. Any error
27973
+ * thrown by the handler is wrapped with the function's logical id.
27974
+ */
27975
+ async function invokeCloudFrontFunction(fn, event) {
27976
+ const context = vm.createContext({
27977
+ console,
27978
+ [EVENT_GLOBAL]: event
27979
+ });
27980
+ let result;
27981
+ try {
27982
+ result = fn.script.runInContext(context, { timeout: FUNCTION_TIMEOUT_MS });
27983
+ } catch (err) {
27984
+ throw new Error(`CloudFront Function '${fn.logicalId}' threw at request time: ${err instanceof Error ? err.message : String(err)}`);
27985
+ }
27986
+ try {
27987
+ return result instanceof Promise ? await result : result;
27988
+ } catch (err) {
27989
+ throw new Error(`CloudFront Function '${fn.logicalId}' threw at request time: ${err instanceof Error ? err.message : String(err)}`);
27990
+ }
27991
+ }
27992
+ /**
27993
+ * Run a `viewer-request` function and classify its return value. A non-object
27994
+ * return (or `undefined`) is treated as "continue unchanged" — a defensive
27995
+ * default matching CloudFront's tolerance of a function that just inspects the
27996
+ * request.
27997
+ */
27998
+ async function runViewerRequest(fn, event) {
27999
+ const result = await invokeCloudFrontFunction(fn, event);
28000
+ if (result && typeof result === "object") {
28001
+ const obj = result;
28002
+ if ("statusCode" in obj) return {
28003
+ kind: "response",
28004
+ response: coerceResponse(obj)
28005
+ };
28006
+ return {
28007
+ kind: "continue",
28008
+ request: coerceRequest(obj, event.request)
28009
+ };
28010
+ }
28011
+ return {
28012
+ kind: "continue",
28013
+ request: event.request
28014
+ };
28015
+ }
28016
+ /**
28017
+ * Run a `viewer-response` function and return the (possibly mutated) response.
28018
+ * A non-object return falls back to the unmodified origin response.
28019
+ */
28020
+ async function runViewerResponse(fn, event) {
28021
+ const result = await invokeCloudFrontFunction(fn, event);
28022
+ if (result && typeof result === "object") return coerceResponse(result, event.response);
28023
+ return event.response;
28024
+ }
28025
+ /** Normalize a function's returned request object back into a {@link CfRequest}. */
28026
+ function coerceRequest(obj, fallback) {
28027
+ return {
28028
+ method: typeof obj["method"] === "string" ? obj["method"] : fallback.method,
28029
+ uri: typeof obj["uri"] === "string" ? obj["uri"] : fallback.uri,
28030
+ querystring: coerceValueMap(obj["querystring"]) ?? fallback.querystring,
28031
+ headers: coerceValueMap(obj["headers"]) ?? fallback.headers,
28032
+ cookies: coerceValueMap(obj["cookies"]) ?? fallback.cookies
28033
+ };
28034
+ }
28035
+ /** Normalize a function's returned response object into a {@link CfResponse}. */
28036
+ function coerceResponse(obj, fallback) {
28037
+ const res = {
28038
+ statusCode: typeof obj["statusCode"] === "number" ? obj["statusCode"] : fallback?.statusCode ?? 200,
28039
+ headers: coerceValueMap(obj["headers"]) ?? fallback?.headers ?? {}
28040
+ };
28041
+ const desc = obj["statusDescription"] ?? fallback?.statusDescription;
28042
+ if (typeof desc === "string") res.statusDescription = desc;
28043
+ const cookies = coerceValueMap(obj["cookies"]);
28044
+ if (cookies) res.cookies = cookies;
28045
+ const body = obj["body"];
28046
+ if (typeof body === "string" || body && typeof body === "object") res.body = body;
28047
+ else if (fallback?.body !== void 0) res.body = fallback.body;
28048
+ return res;
28049
+ }
28050
+ /**
28051
+ * Coerce a header / cookie / querystring map back into the `{ value }` shape,
28052
+ * tolerating a function that wrote a bare string value (`headers.location =
28053
+ * 'https://...'` instead of `{ value: '...' }`). Returns `undefined` when the
28054
+ * field is absent so the caller keeps the prior value.
28055
+ */
28056
+ function coerceValueMap(value) {
28057
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
28058
+ const out = {};
28059
+ for (const [k, v] of Object.entries(value)) if (v && typeof v === "object" && "value" in v) {
28060
+ const cv = v;
28061
+ const entry = { value: scalarToString(cv.value) };
28062
+ if (Array.isArray(cv.multiValue)) entry.multiValue = cv.multiValue.filter((m) => Boolean(m) && typeof m === "object").map((m) => ({ value: scalarToString(m.value) }));
28063
+ out[k] = entry;
28064
+ } else if (typeof v === "string") out[k] = { value: v };
28065
+ return out;
28066
+ }
28067
+ /**
28068
+ * Coerce a CloudFront function's header / cookie / query value to a string. CF
28069
+ * values are strings, but a function may hand back a number / boolean; anything
28070
+ * non-scalar (or absent) collapses to the empty string.
28071
+ */
28072
+ function scalarToString(value) {
28073
+ if (typeof value === "string") return value;
28074
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
28075
+ return "";
28076
+ }
28077
+ /**
28078
+ * Build a `viewer-request` event from an incoming HTTP request. Header keys
28079
+ * are lower-cased (CloudFront's contract); a multi-valued header is carried in
28080
+ * `multiValue` with `value` set to the first entry.
28081
+ */
28082
+ function buildViewerRequestEvent(input) {
28083
+ return {
28084
+ version: "1.0",
28085
+ context: {
28086
+ distributionDomainName: input.domainName,
28087
+ distributionId: input.distributionId,
28088
+ eventType: "viewer-request",
28089
+ requestId: input.requestId
28090
+ },
28091
+ viewer: { ip: input.ip },
28092
+ request: {
28093
+ method: input.method.toUpperCase(),
28094
+ uri: input.uri,
28095
+ querystring: parseQueryStringToCf(input.querystring),
28096
+ headers: headersToCf(input.headers),
28097
+ cookies: cookiesFromHeaders(input.headers)
28098
+ }
28099
+ };
28100
+ }
28101
+ /** Extend a request event into a `viewer-response` event with the origin response. */
28102
+ function buildViewerResponseEvent(requestEvent, response) {
28103
+ return {
28104
+ ...requestEvent,
28105
+ context: {
28106
+ ...requestEvent.context,
28107
+ eventType: "viewer-response"
28108
+ },
28109
+ response: {
28110
+ statusCode: response.statusCode,
28111
+ ...response.statusDescription !== void 0 && { statusDescription: response.statusDescription },
28112
+ headers: headersToCf(response.headers)
28113
+ }
28114
+ };
28115
+ }
28116
+ function parseQueryStringToCf(raw) {
28117
+ const out = {};
28118
+ if (!raw) return out;
28119
+ for (const pair of raw.split("&")) {
28120
+ if (!pair) continue;
28121
+ const eq = pair.indexOf("=");
28122
+ addCfValue(out, decodePart(eq === -1 ? pair : pair.slice(0, eq)), eq === -1 ? "" : decodePart(pair.slice(eq + 1)));
28123
+ }
28124
+ return out;
28125
+ }
28126
+ function headersToCf(headers) {
28127
+ const out = {};
28128
+ for (const [name, raw] of Object.entries(headers)) {
28129
+ if (raw === void 0) continue;
28130
+ const lower = name.toLowerCase();
28131
+ if (Array.isArray(raw)) for (const v of raw) addCfValue(out, lower, v);
28132
+ else addCfValue(out, lower, raw);
28133
+ }
28134
+ return out;
28135
+ }
28136
+ function cookiesFromHeaders(headers) {
28137
+ const out = {};
28138
+ const raw = headers["cookie"] ?? headers["Cookie"];
28139
+ const list = raw === void 0 ? [] : Array.isArray(raw) ? raw : [raw];
28140
+ for (const header of list) for (const pair of header.split(";")) {
28141
+ const trimmed = pair.trim();
28142
+ if (!trimmed) continue;
28143
+ const eq = trimmed.indexOf("=");
28144
+ if (eq === -1) continue;
28145
+ addCfValue(out, trimmed.slice(0, eq).trim(), trimmed.slice(eq + 1).trim());
28146
+ }
28147
+ return out;
28148
+ }
28149
+ /** Append a value under `key`, promoting to `multiValue` on the second hit. */
28150
+ function addCfValue(map, key, value) {
28151
+ const existing = map[key];
28152
+ if (existing === void 0) {
28153
+ map[key] = { value };
28154
+ return;
28155
+ }
28156
+ if (!existing.multiValue) existing.multiValue = [{ value: existing.value }];
28157
+ existing.multiValue.push({ value });
28158
+ }
28159
+ function decodePart(s) {
28160
+ try {
28161
+ return decodeURIComponent(s.replace(/\+/g, " "));
28162
+ } catch {
28163
+ return s;
28164
+ }
28165
+ }
28166
+
28167
+ //#endregion
28168
+ //#region src/local/cloudfront-resolver.ts
28169
+ const CLOUDFRONT_DISTRIBUTION_TYPE = "AWS::CloudFront::Distribution";
28170
+ const CLOUDFRONT_FUNCTION_TYPE = "AWS::CloudFront::Function";
28171
+ const S3_BUCKET_TYPE = "AWS::S3::Bucket";
28172
+ /**
28173
+ * Resolve a distribution logical id within a stack into its routing model.
28174
+ * `originOverrides` maps an origin id to a local directory (the `--origin`
28175
+ * escape hatch); a covered origin is served from that directory regardless of
28176
+ * BucketDeployment resolution.
28177
+ */
28178
+ function resolveCloudFrontDistribution(args) {
28179
+ const { stack, logicalId } = args;
28180
+ const template = stack.template;
28181
+ const resource = (template.Resources ?? {})[logicalId];
28182
+ if (!resource || resource.Type !== "AWS::CloudFront::Distribution") throw new Error(`Resource '${logicalId}' in stack ${stack.stackName} is not an ${CLOUDFRONT_DISTRIBUTION_TYPE}.`);
28183
+ const distConfig = (resource.Properties ?? {})["DistributionConfig"];
28184
+ if (!distConfig || typeof distConfig !== "object") throw new Error(`Distribution '${logicalId}' has no DistributionConfig.`);
28185
+ const dc = distConfig;
28186
+ const behaviors = resolveBehaviors(dc, compileDistributionFunctions(template), logicalId);
28187
+ const origins = resolveOrigins(dc, template, stack, args.originOverrides ?? /* @__PURE__ */ new Map());
28188
+ const customErrorResponses = resolveCustomErrorResponses(dc);
28189
+ const result = {
28190
+ logicalId,
28191
+ stackName: stack.stackName,
28192
+ behaviors,
28193
+ origins,
28194
+ customErrorResponses
28195
+ };
28196
+ if (typeof dc["DefaultRootObject"] === "string" && dc["DefaultRootObject"] !== "") result.defaultRootObject = dc["DefaultRootObject"];
28197
+ return result;
28198
+ }
28199
+ /** Compile every `AWS::CloudFront::Function` in the template, keyed by logical id. */
28200
+ function compileDistributionFunctions(template) {
28201
+ const out = /* @__PURE__ */ new Map();
28202
+ for (const [logicalId, resource] of Object.entries(template.Resources ?? {})) {
28203
+ if (resource.Type !== CLOUDFRONT_FUNCTION_TYPE) continue;
28204
+ const props = resource.Properties ?? {};
28205
+ const code = props["FunctionCode"];
28206
+ if (typeof code !== "string") {
28207
+ getLogger().warn(`CloudFront Function '${logicalId}' has a non-inline FunctionCode; cdk-local can only run inline function code. Skipping.`);
28208
+ continue;
28209
+ }
28210
+ const config = props["FunctionConfig"];
28211
+ const runtime = config && typeof config === "object" && typeof config["Runtime"] === "string" ? config["Runtime"] : "cloudfront-js-1.0";
28212
+ out.set(logicalId, compileCloudFrontFunction(logicalId, code, runtime));
28213
+ }
28214
+ return out;
28215
+ }
28216
+ function resolveBehaviors(dc, functions, distLogicalId) {
28217
+ const behaviors = [];
28218
+ const def = dc["DefaultCacheBehavior"];
28219
+ if (def && typeof def === "object") behaviors.push(resolveBehavior(def, void 0, functions, distLogicalId));
28220
+ const extra = Array.isArray(dc["CacheBehaviors"]) ? dc["CacheBehaviors"] : [];
28221
+ for (const b of extra) {
28222
+ if (!b || typeof b !== "object") continue;
28223
+ const behavior = b;
28224
+ if (typeof behavior["PathPattern"] !== "string") {
28225
+ getLogger().warn(`Distribution '${distLogicalId}': a cache behavior has no literal PathPattern; cdk-local cannot route it and is skipping it.`);
28226
+ continue;
28227
+ }
28228
+ behaviors.push(resolveBehavior(behavior, behavior["PathPattern"], functions, distLogicalId));
28229
+ }
28230
+ return behaviors;
28231
+ }
28232
+ function resolveBehavior(behavior, pathPattern, functions, distLogicalId) {
28233
+ const resolved = {
28234
+ targetOriginId: typeof behavior["TargetOriginId"] === "string" ? behavior["TargetOriginId"] : "",
28235
+ hasLambdaEdge: Array.isArray(behavior["LambdaFunctionAssociations"]) ? behavior["LambdaFunctionAssociations"].length > 0 : false
28236
+ };
28237
+ if (pathPattern !== void 0) resolved.pathPattern = pathPattern;
28238
+ if (typeof behavior["ViewerProtocolPolicy"] === "string") resolved.viewerProtocolPolicy = behavior["ViewerProtocolPolicy"];
28239
+ const assocs = Array.isArray(behavior["FunctionAssociations"]) ? behavior["FunctionAssociations"] : [];
28240
+ for (const a of assocs) {
28241
+ if (!a || typeof a !== "object") continue;
28242
+ const assoc = a;
28243
+ const eventType = assoc["EventType"];
28244
+ const fnLogicalId = pickFunctionLogicalIdFromArn(assoc["FunctionARN"] ?? assoc["FunctionArn"]);
28245
+ if (!fnLogicalId) {
28246
+ getLogger().warn(`Distribution '${distLogicalId}': a FunctionAssociation references a function ARN cdk-local could not resolve to a local AWS::CloudFront::Function; it will not run.`);
28247
+ continue;
28248
+ }
28249
+ const fn = functions.get(fnLogicalId);
28250
+ if (!fn) continue;
28251
+ if (eventType === "viewer-request") resolved.viewerRequest = fn;
28252
+ else if (eventType === "viewer-response") resolved.viewerResponse = fn;
28253
+ }
28254
+ return resolved;
28255
+ }
28256
+ /**
28257
+ * Unwrap a `FunctionAssociations[].FunctionARN` intrinsic to the
28258
+ * `AWS::CloudFront::Function` logical id. CDK synthesizes it as
28259
+ * `{Fn::GetAtt: [<logicalId>, "FunctionARN"]}`.
28260
+ */
28261
+ function pickFunctionLogicalIdFromArn(value) {
28262
+ if (!value || typeof value !== "object") return void 0;
28263
+ const getAtt = value["Fn::GetAtt"];
28264
+ if (Array.isArray(getAtt) && getAtt.length === 2 && typeof getAtt[0] === "string") return getAtt[0];
28265
+ }
28266
+ function resolveOrigins(dc, template, stack, overrides) {
28267
+ const out = /* @__PURE__ */ new Map();
28268
+ const origins = Array.isArray(dc["Origins"]) ? dc["Origins"] : [];
28269
+ const manifest = loadStackManifest(stack);
28270
+ for (const o of origins) {
28271
+ if (!o || typeof o !== "object") continue;
28272
+ const origin = o;
28273
+ const originId = typeof origin["Id"] === "string" ? origin["Id"] : void 0;
28274
+ if (!originId) continue;
28275
+ const overrideDir = overrides.get(originId);
28276
+ if (overrideDir) {
28277
+ out.set(originId, {
28278
+ kind: "s3",
28279
+ originId,
28280
+ localDirs: [resolveDir(overrideDir)]
28281
+ });
28282
+ continue;
28283
+ }
28284
+ const bucketLogicalId = pickBucketLogicalIdFromOrigin(origin, template);
28285
+ if (!(origin["S3OriginConfig"] !== void 0 || bucketLogicalId !== void 0)) {
28286
+ out.set(originId, {
28287
+ kind: "custom",
28288
+ originId,
28289
+ domainName: describeDomainName(origin["DomainName"])
28290
+ });
28291
+ continue;
28292
+ }
28293
+ const localDirs = bucketLogicalId && manifest ? resolveBucketDeploymentDirs(template, manifest, stack, bucketLogicalId) : [];
28294
+ if (localDirs.length > 0) out.set(originId, {
28295
+ kind: "s3",
28296
+ originId,
28297
+ localDirs
28298
+ });
28299
+ else out.set(originId, {
28300
+ kind: "s3-unresolved",
28301
+ originId,
28302
+ ...bucketLogicalId !== void 0 && { bucketLogicalId }
28303
+ });
28304
+ }
28305
+ return out;
28306
+ }
28307
+ /**
28308
+ * Read a stack's asset manifest, or `undefined` when it ships no assets / is
28309
+ * unreadable. We already hold the absolute manifest path on {@link StackInfo},
28310
+ * so we read + parse it directly rather than going through the by-stack-name
28311
+ * loader.
28312
+ */
28313
+ function loadStackManifest(stack) {
28314
+ if (!stack.assetManifestPath) return void 0;
28315
+ try {
28316
+ return JSON.parse(readFileSync(stack.assetManifestPath, "utf-8"));
28317
+ } catch (err) {
28318
+ getLogger().warn(`Could not read asset manifest at ${stack.assetManifestPath}: ${err instanceof Error ? err.message : String(err)}`);
28319
+ return;
28320
+ }
28321
+ }
28322
+ /**
28323
+ * Walk a bucket's `Custom::CDKBucketDeployment*` custom resources back to the
28324
+ * local source-asset directories that were staged for it. Returns the resolved
28325
+ * local directories (one per matching BucketDeployment source) — empty when the
28326
+ * bucket has no BucketDeployment (content uploaded out of band) or the source
28327
+ * object key cannot be matched to a manifest file asset.
28328
+ */
28329
+ function resolveBucketDeploymentDirs(template, manifest, stack, bucketLogicalId) {
28330
+ const manifestDir = stack.assetManifestPath ? dirname(stack.assetManifestPath) : void 0;
28331
+ if (!manifestDir) return [];
28332
+ const dirs = [];
28333
+ for (const resource of Object.values(template.Resources ?? {})) {
28334
+ if (!String(resource.Type).startsWith("Custom::CDKBucketDeployment")) continue;
28335
+ const props = resource.Properties ?? {};
28336
+ if (refLogicalId(props["DestinationBucketName"]) !== bucketLogicalId) continue;
28337
+ const keys = Array.isArray(props["SourceObjectKeys"]) ? props["SourceObjectKeys"] : [];
28338
+ for (const key of keys) {
28339
+ if (typeof key !== "string") continue;
28340
+ const asset = findFileAssetByObjectKey(manifest, key);
28341
+ if (!asset) continue;
28342
+ dirs.push(resolve(manifestDir, asset.source.path));
28343
+ }
28344
+ }
28345
+ return dirs;
28346
+ }
28347
+ /**
28348
+ * Find the file asset whose published object key equals `objectKey`. The
28349
+ * BucketDeployment `SourceObjectKeys` carries the published key
28350
+ * (`<hash>.zip`); matching on the destination object key is more robust than
28351
+ * guessing how the hash maps to the manifest key.
28352
+ */
28353
+ function findFileAssetByObjectKey(manifest, objectKey) {
28354
+ for (const asset of Object.values(manifest.files ?? {})) for (const dest of Object.values(asset.destinations ?? {})) if (dest.objectKey === objectKey) return asset;
28355
+ const hash = objectKey.replace(/\.zip$/, "");
28356
+ return manifest.files?.[hash];
28357
+ }
28358
+ /**
28359
+ * Extract the S3 bucket logical id an origin's `DomainName` points at. CDK
28360
+ * synthesizes an S3 origin's `DomainName` as
28361
+ * `{Fn::GetAtt: [<bucket>, "RegionalDomainName"]}` (or `DomainName` /
28362
+ * `WebsiteURL`). Returns the logical id only when it resolves to an
28363
+ * `AWS::S3::Bucket` in the template.
28364
+ */
28365
+ function pickBucketLogicalIdFromOrigin(origin, template) {
28366
+ const candidate = getAttLogicalId(origin["DomainName"]);
28367
+ if (!candidate) return void 0;
28368
+ const resource = (template.Resources ?? {})[candidate];
28369
+ if (resource && resource.Type === S3_BUCKET_TYPE) return candidate;
28370
+ }
28371
+ function getAttLogicalId(value) {
28372
+ if (!value || typeof value !== "object") return void 0;
28373
+ const getAtt = value["Fn::GetAtt"];
28374
+ if (Array.isArray(getAtt) && getAtt.length === 2 && typeof getAtt[0] === "string") return getAtt[0];
28375
+ }
28376
+ function refLogicalId(value) {
28377
+ if (!value || typeof value !== "object") return void 0;
28378
+ const ref = value["Ref"];
28379
+ return typeof ref === "string" ? ref : void 0;
28380
+ }
28381
+ function resolveCustomErrorResponses(dc) {
28382
+ const raw = Array.isArray(dc["CustomErrorResponses"]) ? dc["CustomErrorResponses"] : [];
28383
+ const out = [];
28384
+ for (const e of raw) {
28385
+ if (!e || typeof e !== "object") continue;
28386
+ const entry = e;
28387
+ if (typeof entry["ErrorCode"] !== "number") continue;
28388
+ const resolved = { errorCode: entry["ErrorCode"] };
28389
+ if (typeof entry["ResponsePagePath"] === "string") resolved.responsePagePath = entry["ResponsePagePath"];
28390
+ if (typeof entry["ResponseCode"] === "number") resolved.responseCode = entry["ResponseCode"];
28391
+ out.push(resolved);
28392
+ }
28393
+ return out;
28394
+ }
28395
+ function describeDomainName(value) {
28396
+ if (typeof value === "string") return value;
28397
+ if (value && typeof value === "object") return JSON.stringify(value);
28398
+ return "<unknown>";
28399
+ }
28400
+ function resolveDir(dir) {
28401
+ return isAbsolute(dir) ? dir : resolve(process.cwd(), dir);
28402
+ }
28403
+ /** True when a template resource is an `AWS::CloudFront::Distribution`. */
28404
+ function isCloudFrontDistribution(resource) {
28405
+ return resource.Type === CLOUDFRONT_DISTRIBUTION_TYPE;
28406
+ }
28407
+
28408
+ //#endregion
28409
+ //#region src/local/cloudfront-static-origin.ts
28410
+ /**
28411
+ * Resolve a URI against one or more local origin directories, honoring the
28412
+ * default root object and the distribution's custom error responses. The
28413
+ * directories are searched in order (a BucketDeployment can layer multiple
28414
+ * sources onto one bucket; later sources overlay earlier ones in the cloud, so
28415
+ * the first directory that has the key wins here).
28416
+ */
28417
+ function serveFromStaticOrigin(input) {
28418
+ const key = uriToKey(input.uri, input.defaultRootObject);
28419
+ const direct = readKey(input.localDirs, key);
28420
+ if (direct) return {
28421
+ statusCode: 200,
28422
+ headers: { "content-type": contentTypeForKey(key) },
28423
+ body: direct
28424
+ };
28425
+ const errorResponses = input.customErrorResponses ?? [];
28426
+ for (const code of [403, 404]) {
28427
+ const match = errorResponses.find((e) => e.errorCode === code);
28428
+ if (!match || !match.responsePagePath) continue;
28429
+ const errorKey = stripLeadingSlash(match.responsePagePath);
28430
+ const body = readKey(input.localDirs, errorKey);
28431
+ if (body) return {
28432
+ statusCode: match.responseCode ?? code,
28433
+ headers: { "content-type": contentTypeForKey(errorKey) },
28434
+ body
28435
+ };
28436
+ }
28437
+ return {
28438
+ statusCode: 404,
28439
+ headers: { "content-type": "text/plain; charset=utf-8" },
28440
+ body: Buffer.from(`Not found: ${input.uri}\n`)
28441
+ };
28442
+ }
28443
+ /**
28444
+ * Map a request URI to an S3 object key. The query string / fragment is
28445
+ * dropped, the leading slash is removed, and the root path (`/` or empty)
28446
+ * resolves to the default root object. A URI ending in `/` is NOT auto-indexed
28447
+ * (CloudFront does not), so it falls through to a missing key unless a function
28448
+ * rewrote it.
28449
+ */
28450
+ function uriToKey(uri, defaultRootObject) {
28451
+ let path = uri;
28452
+ const q = path.indexOf("?");
28453
+ if (q !== -1) path = path.slice(0, q);
28454
+ const h = path.indexOf("#");
28455
+ if (h !== -1) path = path.slice(0, h);
28456
+ path = decodeURIComponentSafe(path);
28457
+ const stripped = stripLeadingSlash(path);
28458
+ if (stripped === "") return defaultRootObject ? stripLeadingSlash(defaultRootObject) : "";
28459
+ return stripped;
28460
+ }
28461
+ /**
28462
+ * Read a key from the first directory that contains it as a regular file.
28463
+ * Path-traversal safe: the resolved absolute path must stay within the origin
28464
+ * directory (a `../` in the key, or a symlink escaping the root, yields no
28465
+ * read). Returns `undefined` when no directory has the key.
28466
+ */
28467
+ function readKey(localDirs, key) {
28468
+ if (key === "") return void 0;
28469
+ for (const dir of localDirs) {
28470
+ const resolved = safeJoin(dir, key);
28471
+ if (!resolved) continue;
28472
+ try {
28473
+ if (statSync(resolved).isFile()) return readFileSync(resolved);
28474
+ } catch {}
28475
+ }
28476
+ }
28477
+ /**
28478
+ * Join `key` onto `dir`, rejecting any result that escapes `dir` (Zip-Slip /
28479
+ * path-traversal guard). Returns `undefined` when the key would escape.
28480
+ */
28481
+ function safeJoin(dir, key) {
28482
+ const candidate = normalize(join(dir, key));
28483
+ const root = normalize(dir);
28484
+ const rootWithSep = root.endsWith(sep) ? root : root + sep;
28485
+ if (candidate !== root && !candidate.startsWith(rootWithSep)) return void 0;
28486
+ return candidate;
28487
+ }
28488
+ function stripLeadingSlash(s) {
28489
+ return s.startsWith("/") ? s.replace(/^\/+/, "") : s;
28490
+ }
28491
+ function decodeURIComponentSafe(s) {
28492
+ try {
28493
+ return decodeURIComponent(s);
28494
+ } catch {
28495
+ return s;
28496
+ }
28497
+ }
28498
+ /** Minimal extension -> MIME map for the common static-site asset types. */
28499
+ const MIME_BY_EXT = {
28500
+ html: "text/html; charset=utf-8",
28501
+ htm: "text/html; charset=utf-8",
28502
+ css: "text/css; charset=utf-8",
28503
+ js: "text/javascript; charset=utf-8",
28504
+ mjs: "text/javascript; charset=utf-8",
28505
+ json: "application/json; charset=utf-8",
28506
+ map: "application/json; charset=utf-8",
28507
+ xml: "application/xml; charset=utf-8",
28508
+ txt: "text/plain; charset=utf-8",
28509
+ svg: "image/svg+xml",
28510
+ png: "image/png",
28511
+ jpg: "image/jpeg",
28512
+ jpeg: "image/jpeg",
28513
+ gif: "image/gif",
28514
+ webp: "image/webp",
28515
+ avif: "image/avif",
28516
+ ico: "image/x-icon",
28517
+ woff: "font/woff",
28518
+ woff2: "font/woff2",
28519
+ ttf: "font/ttf",
28520
+ otf: "font/otf",
28521
+ eot: "application/vnd.ms-fontobject",
28522
+ pdf: "application/pdf",
28523
+ wasm: "application/wasm",
28524
+ webmanifest: "application/manifest+json"
28525
+ };
28526
+ /** Resolve a Content-Type for an object key by extension. */
28527
+ function contentTypeForKey(key) {
28528
+ const dot = key.lastIndexOf(".");
28529
+ if (dot === -1 || dot === key.length - 1) return "application/octet-stream";
28530
+ return MIME_BY_EXT[key.slice(dot + 1).toLowerCase()] ?? "application/octet-stream";
28531
+ }
28532
+
28533
+ //#endregion
28534
+ //#region src/local/cloudfront-server.ts
28535
+ /** Start the local CloudFront server and resolve once it is listening. */
28536
+ async function startCloudFrontServer(options) {
28537
+ const logger = getLogger().child("cloudfront");
28538
+ const state = { distribution: options.distribution };
28539
+ const handler = (req, res) => {
28540
+ handleRequest$1(req, res, state, logger).catch((err) => {
28541
+ logger.warn(`Request handling failed: ${err instanceof Error ? err.message : String(err)}`);
28542
+ if (!res.headersSent) {
28543
+ res.statusCode = 500;
28544
+ res.setHeader("content-type", "text/plain; charset=utf-8");
28545
+ }
28546
+ if (!res.writableEnded) res.end("Internal error in cdkl start-cloudfront.\n");
28547
+ });
28548
+ };
28549
+ const scheme = options.tls ? "https" : "http";
28550
+ const server = options.tls ? createServer$2({
28551
+ cert: options.tls.certPem,
28552
+ key: options.tls.keyPem
28553
+ }, handler) : createServer$1(handler);
28554
+ const port = await listen(server, options.host, options.port);
28555
+ return {
28556
+ url: `${scheme}://${options.host}:${port}`,
28557
+ port,
28558
+ scheme,
28559
+ update(distribution) {
28560
+ state.distribution = distribution;
28561
+ },
28562
+ close() {
28563
+ return new Promise((resolveClose) => {
28564
+ server.close(() => resolveClose());
28565
+ });
28566
+ }
28567
+ };
28568
+ }
28569
+ /** The per-request pipeline: behavior match -> viewer-request -> origin -> viewer-response. */
28570
+ async function handleRequest$1(req, res, state, logger) {
28571
+ const distribution = state.distribution;
28572
+ const rawUrl = req.url ?? "/";
28573
+ const queryIdx = rawUrl.indexOf("?");
28574
+ const uri = queryIdx === -1 ? rawUrl : rawUrl.slice(0, queryIdx);
28575
+ const querystring = queryIdx === -1 ? "" : rawUrl.slice(queryIdx + 1);
28576
+ const behavior = matchBehavior(distribution.behaviors, uri);
28577
+ if (!behavior) {
28578
+ writePlain(res, 404, "No cache behavior matched.\n");
28579
+ return;
28580
+ }
28581
+ let requestEvent = buildViewerRequestEvent({
28582
+ method: req.method ?? "GET",
28583
+ uri,
28584
+ querystring,
28585
+ headers: req.headers,
28586
+ ip: req.socket.remoteAddress ?? "127.0.0.1",
28587
+ distributionId: distribution.logicalId,
28588
+ domainName: req.headers.host ?? "localhost",
28589
+ requestId: `cdkl-${Date.now().toString(36)}-${Math.floor(performance.now()).toString(36)}`
28590
+ });
28591
+ let effectiveUri = uri;
28592
+ if (behavior.viewerRequest) {
28593
+ const outcome = await runViewerRequest(behavior.viewerRequest, requestEvent);
28594
+ if (outcome.kind === "response") {
28595
+ writeCfResponse(res, outcome.response, logger);
28596
+ return;
28597
+ }
28598
+ effectiveUri = outcome.request.uri;
28599
+ requestEvent = {
28600
+ ...requestEvent,
28601
+ request: outcome.request
28602
+ };
28603
+ }
28604
+ const origin = distribution.origins.get(behavior.targetOriginId);
28605
+ const originResult = serveFromOrigin(origin, behavior, effectiveUri, distribution, logger);
28606
+ if (!originResult) return writePlain(res, 502, originUnavailableMessage(origin, behavior));
28607
+ let finalStatus = originResult.statusCode;
28608
+ let finalHeaders = originResult.headers;
28609
+ if (behavior.viewerResponse) {
28610
+ const responseEvent = buildViewerResponseEvent(requestEvent, {
28611
+ statusCode: originResult.statusCode,
28612
+ headers: originResult.headers
28613
+ });
28614
+ const mutated = await runViewerResponse(behavior.viewerResponse, responseEvent);
28615
+ finalStatus = mutated.statusCode;
28616
+ finalHeaders = cfHeadersToPlain(mutated.headers, originResult.headers);
28617
+ }
28618
+ res.statusCode = finalStatus;
28619
+ setHeadersSafely(res, finalHeaders, logger);
28620
+ res.end(originResult.body);
28621
+ }
28622
+ /**
28623
+ * Set response headers, skipping any whose name / value Node rejects (invalid
28624
+ * HTTP token, CR/LF injection) rather than throwing an opaque 500. A CloudFront
28625
+ * Function can return an arbitrary header map; a malformed entry should fail
28626
+ * loudly on that one header, not the whole response. CR/LF is stripped from
28627
+ * values defensively before the set.
28628
+ */
28629
+ function setHeadersSafely(res, headers, logger) {
28630
+ for (const [name, value] of Object.entries(headers)) try {
28631
+ res.setHeader(name, value.replace(/[\r\n]/g, ""));
28632
+ } catch (err) {
28633
+ logger.warn(`Skipping invalid response header '${name}': ${err instanceof Error ? err.message : String(err)}`);
28634
+ }
28635
+ }
28636
+ function serveFromOrigin(origin, behavior, uri, distribution, logger) {
28637
+ if (behavior.hasLambdaEdge) logger.warn(`Behavior ${behavior.pathPattern ?? "(default)"} carries a Lambda@Edge association; cdk-local does not run Lambda@Edge — serving the S3 origin only.`);
28638
+ if (!origin || origin.kind !== "s3") return void 0;
28639
+ const result = serveFromStaticOrigin({
28640
+ localDirs: origin.localDirs,
28641
+ uri,
28642
+ ...distribution.defaultRootObject !== void 0 && { defaultRootObject: distribution.defaultRootObject },
28643
+ customErrorResponses: distribution.customErrorResponses
28644
+ });
28645
+ return {
28646
+ statusCode: result.statusCode,
28647
+ headers: result.headers,
28648
+ body: result.body
28649
+ };
28650
+ }
28651
+ function originUnavailableMessage(origin, behavior) {
28652
+ if (!origin) return `Behavior ${behavior.pathPattern ?? "(default)"} targets unknown origin '${behavior.targetOriginId}'.\n`;
28653
+ if (origin.kind === "custom") return `Origin '${origin.originId}' is a custom (non-S3) origin (${origin.domainName}). cdkl start-cloudfront serves S3 origins only.
28654
+ `;
28655
+ return `Origin '${origin.originId}' is an S3 origin with no resolvable local source. Point it at a directory with --origin ${origin.originId}=<dir>.\n`;
28656
+ }
28657
+ /**
28658
+ * Pick the cache behavior for a URI: the first `CacheBehaviors[]` entry (in
28659
+ * declared order) whose path pattern matches, else the default behavior. The
28660
+ * default behavior is the entry with no `pathPattern`.
28661
+ */
28662
+ function matchBehavior(behaviors, uri) {
28663
+ let fallback;
28664
+ for (const behavior of behaviors) {
28665
+ if (behavior.pathPattern === void 0) {
28666
+ fallback = behavior;
28667
+ continue;
28668
+ }
28669
+ if (albPathPatternMatches(behavior.pathPattern, uri)) return behavior;
28670
+ }
28671
+ return fallback;
28672
+ }
28673
+ function writeCfResponse(res, response, logger) {
28674
+ res.statusCode = response.statusCode;
28675
+ setHeadersSafely(res, cfHeadersToPlain(response.headers, {}), logger);
28676
+ const body = cfResponseBody(response);
28677
+ res.end(body);
28678
+ }
28679
+ function cfResponseBody(response) {
28680
+ if (response.body === void 0) return Buffer.alloc(0);
28681
+ if (typeof response.body === "string") return Buffer.from(response.body);
28682
+ const data = response.body.data ?? "";
28683
+ return response.body.encoding === "base64" ? Buffer.from(data, "base64") : Buffer.from(data);
28684
+ }
28685
+ /** Flatten a CloudFront header map (`{ name: { value } }`) to `{ name: value }`. */
28686
+ function cfHeadersToPlain(cf, base) {
28687
+ const out = { ...base };
28688
+ for (const [name, val] of Object.entries(cf)) if (val.multiValue && val.multiValue.length > 0) out[name] = val.multiValue.map((m) => m.value).join(", ");
28689
+ else out[name] = val.value;
28690
+ return out;
28691
+ }
28692
+ function writePlain(res, status, body) {
28693
+ res.statusCode = status;
28694
+ res.setHeader("content-type", "text/plain; charset=utf-8");
28695
+ res.end(body);
28696
+ }
28697
+ function listen(server, host, port) {
28698
+ return new Promise((resolvePort, rejectPort) => {
28699
+ server.once("error", rejectPort);
28700
+ server.listen(port, host, () => {
28701
+ const addr = server.address();
28702
+ const bound = typeof addr === "object" && addr ? addr.port : port;
28703
+ server.removeListener("error", rejectPort);
28704
+ resolvePort(bound);
28705
+ });
28706
+ });
28707
+ }
28708
+
28709
+ //#endregion
28710
+ //#region src/cli/commands/local-start-cloudfront.ts
28711
+ /** Error thrown by `cdkl start-cloudfront` for target / option problems. */
28712
+ var LocalStartCloudFrontError = class extends CdkLocalError {
28713
+ constructor(message, cause) {
28714
+ super(message, "LOCAL_START_CLOUDFRONT_ERROR", cause);
28715
+ }
28716
+ };
28717
+ /**
28718
+ * Parse the repeatable `--origin <originId>=<dir>` overrides into a map. Each
28719
+ * value points one of the distribution's origins at a local directory — the
28720
+ * escape hatch for when cdk-local cannot resolve the BucketDeployment source
28721
+ * automatically (content uploaded out of band, or a non-CDK bucket).
28722
+ */
28723
+ function parseOriginOverrides(values) {
28724
+ const out = /* @__PURE__ */ new Map();
28725
+ for (const raw of values ?? []) {
28726
+ const eq = raw.indexOf("=");
28727
+ if (eq <= 0 || eq === raw.length - 1) throw new LocalStartCloudFrontError(`Invalid --origin '${raw}'. Expected <originId>=<dir> (e.g. MyOrigin=./site).`);
28728
+ out.set(raw.slice(0, eq).trim(), raw.slice(eq + 1).trim());
28729
+ }
28730
+ return out;
28731
+ }
28732
+ /**
28733
+ * Resolve a CloudFront target string (`Stack/Path` display path or
28734
+ * `Stack:LogicalId`) to its stack + `AWS::CloudFront::Distribution` logical id.
28735
+ * Mirrors the ALB / ECS resolver target grammar.
28736
+ */
28737
+ function resolveCloudFrontTarget(target, stacks) {
28738
+ if (stacks.length === 0) throw new LocalStartCloudFrontError("No stacks found in the synthesized assembly.");
28739
+ const parsed = parseEcsTarget(target);
28740
+ const stack = pickStack(parsed.stackPattern, stacks, target);
28741
+ const resources = stack.template.Resources ?? {};
28742
+ if (parsed.isPath) {
28743
+ const index = buildCdkPathIndex(stack.template);
28744
+ const dists = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId }) => {
28745
+ const r = resources[logicalId];
28746
+ return r !== void 0 && r.Type === "AWS::CloudFront::Distribution";
28747
+ });
28748
+ if (dists.length === 0) throw notFound(target, stack, resources);
28749
+ 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.`);
28750
+ return {
28751
+ stack,
28752
+ logicalId: dists[0].logicalId
28753
+ };
28754
+ }
28755
+ const res = resources[parsed.pathOrId];
28756
+ if (!res || res.Type !== "AWS::CloudFront::Distribution") throw notFound(target, stack, resources);
28757
+ return {
28758
+ stack,
28759
+ logicalId: parsed.pathOrId
28760
+ };
28761
+ }
28762
+ function pickStack(stackPattern, stacks, target) {
28763
+ if (stackPattern === null) {
28764
+ if (stacks.length === 1) return stacks[0];
28765
+ throw new LocalStartCloudFrontError(`Target '${target}' has no stack prefix, and the assembly contains ${stacks.length} stacks: ${stacks.map((s) => s.stackName).join(", ")}. Pass it as 'Stack/Path' or 'Stack:LogicalId'.`);
28766
+ }
28767
+ const matched = matchStacks(stacks, [stackPattern]);
28768
+ if (matched.length === 0) throw new LocalStartCloudFrontError(`No stack matches '${stackPattern}'. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
28769
+ if (matched.length > 1) throw new LocalStartCloudFrontError(`Multiple stacks match '${stackPattern}': ${matched.map((s) => s.stackName).join(", ")}. Refine the pattern.`);
28770
+ return matched[0];
28771
+ }
28772
+ function notFound(target, stack, resources) {
28773
+ const dists = Object.entries(resources).filter(([, r]) => r.Type === CLOUDFRONT_DISTRIBUTION_TYPE).map(([logicalId]) => logicalId);
28774
+ const available = dists.length > 0 ? ` Available distributions in ${stack.stackName}: ${dists.join(", ")}.` : ` ${stack.stackName} declares no ${CLOUDFRONT_DISTRIBUTION_TYPE} resources.`;
28775
+ return new LocalStartCloudFrontError(`Target '${target}' did not match a CloudFront distribution in ${stack.stackName}.${available}`);
28776
+ }
28777
+ /** Emit boot-time WARNs for parts of the distribution cdk-local does not serve. */
28778
+ function warnUnsupported(distribution) {
28779
+ const logger = getLogger();
28780
+ for (const origin of distribution.origins.values()) if (origin.kind === "custom") logger.warn(`Origin '${origin.originId}' is a custom (non-S3) origin (${origin.domainName}); cdkl start-cloudfront serves S3 origins only. Requests routed to it return 502.`);
28781
+ 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.`);
28782
+ 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.");
28783
+ }
28784
+ async function localStartCloudFrontCommand(target, options) {
28785
+ const logger = getLogger();
28786
+ if (options.verbose) logger.setLevel("debug");
28787
+ const originOverrides = parseOriginOverrides(options.origin);
28788
+ const tlsRequested = options.tls === true || options.tlsCert !== void 0 || options.tlsKey !== void 0;
28789
+ await applyRoleArnIfSet({
28790
+ roleArn: options.roleArn,
28791
+ region: options.region,
28792
+ profile: options.profile
28793
+ });
28794
+ const appCmd = resolveApp(options.app);
28795
+ if (!appCmd) throw new LocalStartCloudFrontError(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
28796
+ const basePort = parseInt(options.port, 10);
28797
+ if (!Number.isFinite(basePort) || basePort < 0 || basePort > 65535) throw new LocalStartCloudFrontError(`--port must be 0..65535 (got ${options.port}).`);
28798
+ const synthAndResolve = async () => {
28799
+ logger.info("Synthesizing CDK app...");
28800
+ const synthesizer = new Synthesizer();
28801
+ const context = parseContextOptions(options.context);
28802
+ const synthOpts = {
28803
+ app: appCmd,
28804
+ output: options.output,
28805
+ ...options.region && { region: options.region },
28806
+ ...options.profile && { profile: options.profile },
28807
+ ...Object.keys(context).length > 0 && { context }
28808
+ };
28809
+ const { stacks } = await synthesizer.synthesize(synthOpts);
28810
+ const chosen = await resolveSingleTarget(target, {
28811
+ entries: listTargets(stacks).cloudFrontDistributions,
28812
+ message: "Select a CloudFront distribution to serve",
28813
+ noun: "CloudFront distributions",
28814
+ onMissing: () => new LocalStartCloudFrontError(`${getEmbedConfig().cliName} start-cloudfront requires a <target>. Pass a distribution path like 'Stack/MyDist', or run it in a TTY to pick interactively.`)
28815
+ });
28816
+ target = chosen;
28817
+ const { stack, logicalId } = resolveCloudFrontTarget(chosen, stacks);
28818
+ return resolveCloudFrontDistribution({
28819
+ stack,
28820
+ logicalId,
28821
+ originOverrides
28822
+ });
28823
+ };
28824
+ const initial = await synthAndResolve();
28825
+ warnUnsupported(initial);
28826
+ let tls;
28827
+ if (tlsRequested) tls = await resolveFrontDoorTlsMaterials({
28828
+ certPath: options.tlsCert,
28829
+ keyPath: options.tlsKey
28830
+ });
28831
+ const server = await startCloudFrontServer({
28832
+ distribution: initial,
28833
+ host: options.host,
28834
+ port: basePort,
28835
+ ...tls && { tls }
28836
+ });
28837
+ process.stdout.write(`CloudFront distribution serving on ${server.url} (${initial.logicalId})\n`);
28838
+ process.stdout.write("^C to stop.\n");
28839
+ let watcher;
28840
+ let reloadChain = Promise.resolve();
28841
+ if (options.watch) {
28842
+ const watchRoot = process.cwd();
28843
+ const { ignored, shouldTrigger, excludePatterns } = createWatchPredicates({
28844
+ watchRoot,
28845
+ output: options.output,
28846
+ watchConfig: resolveWatchConfig()
28847
+ });
28848
+ watcher = createFileWatcher({
28849
+ paths: [watchRoot],
28850
+ ignored,
28851
+ shouldTrigger,
28852
+ onChange: () => {
28853
+ logger.info("Detected source change; reloading...");
28854
+ reloadChain = reloadChain.then(async () => {
28855
+ try {
28856
+ const reloaded = await synthAndResolve();
28857
+ warnUnsupported(reloaded);
28858
+ server.update(reloaded);
28859
+ logger.info("Reload complete.");
28860
+ } catch (err) {
28861
+ logger.warn(`Reload failed; keeping the previous version serving: ${err instanceof Error ? err.message : String(err)}`);
28862
+ }
28863
+ }).catch(() => void 0);
28864
+ }
28865
+ });
28866
+ logger.info(`Watching ${watchRoot} for source changes (excluding ${excludePatterns.join(", ")}).`);
28867
+ }
28868
+ let shuttingDown = false;
28869
+ const shutdown = async (signal, exitCode) => {
28870
+ if (shuttingDown) return;
28871
+ shuttingDown = true;
28872
+ logger.info(`Received ${signal}, shutting down...`);
28873
+ if (watcher) try {
28874
+ await watcher.close();
28875
+ } catch {}
28876
+ try {
28877
+ await server.close();
28878
+ } catch {}
28879
+ process.exit(exitCode);
28880
+ };
28881
+ process.on("SIGINT", () => void shutdown("SIGINT", 130));
28882
+ process.on("SIGTERM", () => void shutdown("SIGTERM", 0));
28883
+ await new Promise(() => void 0);
28884
+ }
28885
+ function createLocalStartCloudFrontCommand(opts = {}) {
28886
+ setEmbedConfig(opts.embedConfig);
28887
+ 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) plus its viewer-request / viewer-response CloudFront Functions, reproducing the distribution routing locally so a rewrite / routing change is verifiable in seconds. Serves S3 origins only; custom origins and Lambda@Edge are not run (warn-and-skip). 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) => {
28888
+ await localStartCloudFrontCommand(target, options);
28889
+ }));
28890
+ addStartCloudFrontSpecificOptions(cmd);
28891
+ [
28892
+ ...commonOptions(),
28893
+ ...appOptions(),
28894
+ ...contextOptions
28895
+ ].forEach((opt) => cmd.addOption(opt));
28896
+ cmd.addOption(regionOption);
28897
+ return cmd;
28898
+ }
28899
+ /**
28900
+ * Register the `cdkl start-cloudfront`-only option block on top of the shared
28901
+ * common / app / context helpers. Shared between `cdkl start-cloudfront` and
28902
+ * any host CLI (e.g. cdkd) that wraps the distribution-serving command, so
28903
+ * adding or renaming a `start-cloudfront` flag here propagates to every
28904
+ * embedder without duplicate `.addOption(...)` blocks. Chainable: returns
28905
+ * `cmd`.
28906
+ */
28907
+ function addStartCloudFrontSpecificOptions(cmd) {
28908
+ 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("--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));
28909
+ }
28910
+
27920
28911
  //#endregion
27921
28912
  //#region src/cli/commands/local-list.ts
27922
28913
  async function localListCommand(options) {
@@ -27952,7 +28943,7 @@ async function localListCommand(options) {
27952
28943
  * without running synthesis.
27953
28944
  */
27954
28945
  function formatTargetListing(listing, cliName, options = {}) {
27955
- if (countTargets(listing) === 0) return `No runnable targets (Lambda functions, APIs, ECS services / tasks, AgentCore Runtimes, load balancers) found in this CDK app.`;
28946
+ if (countTargets(listing) === 0) return `No runnable targets (Lambda functions, APIs, ECS services / tasks, AgentCore Runtimes, load balancers, CloudFront distributions) found in this CDK app.`;
27956
28947
  const long = options.long ?? false;
27957
28948
  return "\n" + [
27958
28949
  formatSection("Lambda Functions", `${cliName} invoke <target>`, listing.lambdas, long),
@@ -27960,7 +28951,8 @@ function formatTargetListing(listing, cliName, options = {}) {
27960
28951
  formatSection("ECS Services", `${cliName} start-service <target...>`, listing.ecsServices, long),
27961
28952
  formatSection("ECS Task Definitions", `${cliName} run-task <target>`, listing.ecsTaskDefinitions, long),
27962
28953
  formatSection("AgentCore Runtimes", `${cliName} invoke-agentcore <target>`, listing.agentCoreRuntimes, long),
27963
- formatSection("Application Load Balancers", `${cliName} start-alb <target...>`, listing.loadBalancers, long)
28954
+ formatSection("Application Load Balancers", `${cliName} start-alb <target...>`, listing.loadBalancers, long),
28955
+ formatSection("CloudFront Distributions", `${cliName} start-cloudfront <target>`, listing.cloudFrontDistributions, long)
27964
28956
  ].filter((lines) => lines.length > 0).map((lines) => lines.join("\n")).join("\n\n");
27965
28957
  }
27966
28958
  function formatSection(title, command, entries, long) {
@@ -27975,7 +28967,7 @@ function formatSection(title, command, entries, long) {
27975
28967
  }
27976
28968
  function createLocalListCommand(opts = {}) {
27977
28969
  setEmbedConfig(opts.embedConfig);
27978
- const cmd = new Command("list").alias("ls").description("List the runnable targets in the synthesized CDK app, grouped by the command that runs them: Lambda functions (invoke), API Gateway REST v1 / HTTP v2 / Function URL / WebSocket surfaces (start-api), ECS services (start-service), ECS task definitions (run-task), AgentCore Runtimes (invoke-agentcore), and Application Load Balancers (start-alb). Each target is shown by its CDK display path; pass -l to also print the stack-qualified logical ID. Tip: you usually do not need to copy these — just run the command (e.g. `invoke`) with no target in a terminal and pick from the list.").action(withErrorHandling(async (options) => {
28970
+ const cmd = new Command("list").alias("ls").description("List the runnable targets in the synthesized CDK app, grouped by the command that runs them: Lambda functions (invoke), API Gateway REST v1 / HTTP v2 / Function URL / WebSocket surfaces (start-api), ECS services (start-service), ECS task definitions (run-task), AgentCore Runtimes (invoke-agentcore), Application Load Balancers (start-alb), and CloudFront distributions (start-cloudfront). Each target is shown by its CDK display path; pass -l to also print the stack-qualified logical ID. Tip: you usually do not need to copy these — just run the command (e.g. `invoke`) with no target in a terminal and pick from the list.").action(withErrorHandling(async (options) => {
27979
28971
  await localListCommand(options);
27980
28972
  }));
27981
28973
  addListSpecificOptions(cmd);
@@ -32051,6 +33043,111 @@ function resolveBootAssemblyDir(appCmd, output) {
32051
33043
  const outPath = resolve(output);
32052
33044
  if (isDir(outPath)) return outPath;
32053
33045
  }
33046
+ /**
33047
+ * Resolve the {@link StackInfo} an ECS-service target id belongs to.
33048
+ *
33049
+ * A studio ECS target id is a CDK display path (`Stack/Construct/...`) or a
33050
+ * `Stack:LogicalId` form (the same shapes {@link resolveEcsServiceTarget}
33051
+ * accepts). We only need the owning stack so we can build that stack's
33052
+ * {@link EcsImageResolutionContext}. Returns `undefined` when the id has no
33053
+ * stack segment (single-stack app uses the lone stack) or the segment matches
33054
+ * no / multiple stacks — the caller then resolves without an image context,
33055
+ * which is exactly the no-`--from-cfn-stack` behavior.
33056
+ *
33057
+ * Exported for unit testing.
33058
+ */
33059
+ function resolveEcsServiceStack(targetId, stacks) {
33060
+ const parsed = parseEcsTarget(targetId);
33061
+ if (parsed.stackPattern === null) return stacks.length === 1 ? stacks[0] : void 0;
33062
+ const matched = matchStacks(stacks, [parsed.stackPattern]);
33063
+ return matched.length === 1 ? matched[0] : void 0;
33064
+ }
33065
+ /**
33066
+ * Pre-build the per-stack {@link EcsImageResolutionContext} map the boot-time
33067
+ * pin classifier needs (issue #354). `buildEcsImageResolutionContext` is
33068
+ * async (it may load deployed state / call STS), but the classify callback
33069
+ * `annotatePinnedEcsTargets` invokes is synchronous, so the contexts must be
33070
+ * materialized BEFORE classification.
33071
+ *
33072
+ * When `--from-cfn-stack` is NOT set, returns an empty map and the classifier
33073
+ * resolves against the synthed template only (the historical behavior). When
33074
+ * it IS set, for every distinct stack that owns a servable ECS service id this
33075
+ * builds a `LocalStateProvider` bound to THAT stack (so a multi-stack app with
33076
+ * bare `--from-cfn-stack` reads each stack's own CFn counterpart — the CFn
33077
+ * provider is stack-bound at construction, `load()` ignores its stack-name
33078
+ * argument), then builds + caches that stack's image-resolution context once.
33079
+ * Each per-stack provider is disposed before returning.
33080
+ *
33081
+ * A per-stack build failure is logged as a WARN and that stack maps to
33082
+ * `undefined` (resolve without an image context) rather than aborting boot.
33083
+ *
33084
+ * Exported for unit testing.
33085
+ */
33086
+ async function prepareEcsImageContexts(args) {
33087
+ const { serviceIds, stacks, options, logger } = args;
33088
+ const contextByStack = /* @__PURE__ */ new Map();
33089
+ if (!isCfnFlagPresent(options)) return contextByStack;
33090
+ const owningStacks = [];
33091
+ const seen = /* @__PURE__ */ new Set();
33092
+ for (const id of serviceIds) {
33093
+ let stack;
33094
+ try {
33095
+ stack = resolveEcsServiceStack(id, stacks);
33096
+ } catch {
33097
+ continue;
33098
+ }
33099
+ if (stack && !seen.has(stack.stackName)) {
33100
+ seen.add(stack.stackName);
33101
+ owningStacks.push(stack);
33102
+ }
33103
+ }
33104
+ rejectExplicitCfnStackWithMultipleStacks(options, owningStacks.length);
33105
+ for (const stack of owningStacks) {
33106
+ const stateProvider = createLocalStateProvider(options, stack.stackName, await resolveCfnFallbackRegion(options, stack.region));
33107
+ try {
33108
+ const ctx = await buildEcsImageResolutionContext(stack, stateProvider, options);
33109
+ contextByStack.set(stack.stackName, ctx);
33110
+ } catch (err) {
33111
+ logger.warn(`studio: could not build deployed-state image context for stack '${stack.stackName}'; ECS services in it resolve against the synthed template only. ${err instanceof Error ? err.message : String(err)}`);
33112
+ contextByStack.set(stack.stackName, void 0);
33113
+ } finally {
33114
+ stateProvider?.dispose();
33115
+ }
33116
+ }
33117
+ return contextByStack;
33118
+ }
33119
+ /**
33120
+ * Build the boot-time ECS pin classifier `annotatePinnedEcsTargets` calls per
33121
+ * servable service (issue #354). When `--from-cfn-stack` is set, a service
33122
+ * whose container image is an INTRINSIC ECR URI (e.g.
33123
+ * `ContainerImage.fromEcrRepository(repo)`) is only resolvable WITH the
33124
+ * deployed-state image-resolution context — without it the resolver throws
33125
+ * and the service was silently left unmarked, so the UI never offered the
33126
+ * image-override picker even though `cdkl start-service --from-cfn-stack`
33127
+ * detects the very same pin. This threads each service's owning-stack
33128
+ * {@link EcsImageResolutionContext} (pre-built by
33129
+ * {@link prepareEcsImageContexts}) into {@link resolveEcsServiceTarget} so
33130
+ * the pin resolves, and surfaces a WARN (not a silent DEBUG swallow) when a
33131
+ * service still cannot be classified.
33132
+ *
33133
+ * NOTE: the target list + these pinned flags are computed ONCE at boot. A
33134
+ * run-time Session-bar `--from-cfn-stack` change (`PATCH /api/config`) does
33135
+ * NOT re-classify — restart studio to re-detect pins under a new binding.
33136
+ *
33137
+ * Returns a `(id) => boolean` callback (true = pinned). Exported for testing.
33138
+ */
33139
+ function makePinClassifier(args) {
33140
+ const { stacks, contextByStack, logger } = args;
33141
+ return (id) => {
33142
+ try {
33143
+ const stack = resolveEcsServiceStack(id, stacks);
33144
+ return !isLocalCdkAssetImage(resolveEcsServiceTarget(id, stacks, stack ? contextByStack.get(stack.stackName) : void 0));
33145
+ } catch (err) {
33146
+ logger.warn(`studio: could not classify image-pin status for ECS service '${id}'; leaving it unmarked (the image-override picker will not be offered). ${err instanceof Error ? err.message : String(err)}`);
33147
+ return false;
33148
+ }
33149
+ };
33150
+ }
32054
33151
  async function localStudioCommand(options) {
32055
33152
  const logger = getLogger();
32056
33153
  if (options.verbose) logger.setLevel("debug");
@@ -32087,14 +33184,16 @@ async function localStudioCommand(options) {
32087
33184
  }
32088
33185
  const appLabel = stacks.map((s) => s.stackName).join(", ") || appCmd;
32089
33186
  const servableEcs = new Set(targetGroups.filter((g) => g.kind === "ecs").flatMap((g) => g.entries.filter((e) => e.servable).map((e) => e.id)));
32090
- const dockerfiles = annotatePinnedEcsTargets(targetGroups, (id) => {
32091
- try {
32092
- return !isLocalCdkAssetImage(resolveEcsServiceTarget(id, stacks));
32093
- } catch (err) {
32094
- logger.debug(`studio: could not classify pin status for '${id}': ${err instanceof Error ? err.message : String(err)}`);
32095
- return false;
32096
- }
32097
- }) ? discoverDockerfiles(process.cwd()) : [];
33187
+ const dockerfiles = annotatePinnedEcsTargets(targetGroups, makePinClassifier({
33188
+ stacks,
33189
+ contextByStack: await prepareEcsImageContexts({
33190
+ serviceIds: [...servableEcs],
33191
+ stacks,
33192
+ options,
33193
+ logger
33194
+ }),
33195
+ logger
33196
+ })) ? discoverDockerfiles(process.cwd()) : [];
32098
33197
  const bus = new StudioEventBus();
32099
33198
  const childConfig = {
32100
33199
  cliEntry: process.argv[1] ?? "",
@@ -32277,5 +33376,5 @@ function addStudioSpecificOptions(cmd) {
32277
33376
  }
32278
33377
 
32279
33378
  //#endregion
32280
- export { runImageOverrideBuilds as $, resolveCfnStackName as $n, defaultCredentialsLoader as $t, resolveAlbFrontDoor as A, ConnectionRegistry as An, resolveProfileCredentials as Ar, renderCodeDockerfile as At, buildEcsImageResolutionContext$1 as B, resolveRuntimeFileExtension as Bn, createFileWatcher as Bt, formatTargetListing as C, pickResponseTemplate as Cn, resolveAgentCoreTarget as Cr, AGENTCORE_SESSION_ID_HEADER as Ct, parseLbPortOverrides as D, HOST_GATEWAY_MIN_VERSION as Dn, tryResolveImageFnJoin as Dr, SUPPORTED_CODE_RUNTIMES as Dt, createLocalStartAlbCommand as E, VtlEvaluationError as En, substituteImagePlaceholders as Er, downloadAndExtractS3Bundle as Et, createLocalRunTaskCommand as F, buildDisconnectEvent as Fn, addStartApiSpecificOptions as Ft, resolveSharedSidecarCredentials as G, substituteEnvVarsFromState as Gn, availableApiIdentifiers as Gt, parseMaxTasks as H, EcsTaskResolutionError as Hn, buildStageMap as Ht, MAX_TASKS_SUBNET_RANGE_CAP as I, buildMessageEvent as In, createLocalStartApiCommand as It, buildImageOverrideTag as J, createLocalStateProvider as Jn, groupRoutesByServer as Jt, runEcsServiceEmulator as K, substituteEnvVarsFromStateAsync as Kn, filterRoutesByApiIdentifier as Kt, addCommonEcsServiceOptions as L, architectureToPlatform as Ln, createWatchPredicates as Lt, createLocalStartServiceCommand as M, handleConnectionsRequest as Mn, classifySourceChange as Mt, serviceStrategy as N, parseConnectionsPath as Nn, addInvokeSpecificOptions as Nt, resolveAlbTarget as O, probeHostGatewaySupport as On, LocalInvokeBuildError as Or, buildAgentCoreCodeImage as Ot, addRunTaskSpecificOptions as P, buildConnectEvent as Pn, createLocalInvokeCommand as Pt, resolveImageOverrides as Q, resolveCfnRegion as Qn, resolveServiceIntegrationParameters as Qt, addEcsAssumeRoleOptions as R, buildContainerImage as Rn, resolveApiTargetSubset as Rt, createLocalListCommand as S, evaluateResponseParameters as Sn, pickAgentCoreCandidateStack as Sr, signAgentCoreInvocation as St, albStrategy as T, tryParseStatus as Tn, formatStateRemedy as Tr, waitForAgentCorePing as Tt, parseRestartPolicy as U, substituteAgainstState as Un, materializeLayerFromArn as Ut, ecsClusterOption as V, resolveRuntimeImage as Vn, attachStageContext as Vt, resolveEcsAssumeRoleOption as W, substituteAgainstStateAsync as Wn, resolveEnvVars$1 as Wt, mergeForService as X, rejectExplicitCfnStackWithMultipleStacks as Xn, startApiServer as Xt, enforceImageOverrideOrphans as Y, isCfnFlagPresent as Yn, readMtlsMaterialsFromDisk as Yt, parseImageOverrideFlags as Z, resolveCfnFallbackRegion as Zn, resolveSelectionExpression as Zt, toStudioTargetGroups as _, matchRoute as _n, AGENTCORE_AGUI_PROTOCOL as _r, MCP_PATH as _t, createLocalStudioCommand as a, verifyJwtViaDiscovery as an, countTargets as ar, DEFAULT_SHADOW_READY_TIMEOUT_MS as at, StudioEventBus as b, buildHttpApiV2Event as bn, AGENTCORE_RUNTIME_TYPE as br, parseSseForJsonRpc as bt, startStudioProxy as c, evaluateCachedLambdaPolicy as cn, discoverWebSocketApis as cr, getContainerNetworkIp as ct, createStudioDispatcher as d, attachAuthorizers as dn, parseSelectionExpressionPath as dr, createLocalInvokeAgentCoreCommand as dt, buildCognitoJwksUrl as en, CfnLocalStateProvider as er, describePinnedImageUri as et, filterStudioCustomResources as f, applyCorsResponseHeaders as fn, webSocketApiMatchesIdentifier as fr, invokeAgentCoreWs as ft, startStudioServer as g, matchPreflight as gn, AGENTCORE_A2A_PROTOCOL as gr, MCP_CONTAINER_PORT as gt, filterStudioTargetGroups as h, isFunctionUrlOacFronted as hn, resolveLambdaArnIntrinsic as hr, a2aInvokeOnce as ht, coerceStopRequest as i, verifyJwtAuthorizer as in, resolveSingleTarget as ir, CloudMapRegistry as it, addStartServiceSpecificOptions as j, buildMgmtEndpointEnvUrl as jn, toCmdArgv as jt, isApplicationLoadBalancer as k, bufferToBody as kn, buildStsClientConfig as kr, computeCodeImageTag as kt, relayServeRequest as l, invokeRequestAuthorizer as ln, discoverWebSocketApisOrThrow as lr, attachContainerLogStreamer as lt, annotatePinnedEcsTargets as m, buildCorsConfigFromCloudFrontChain as mn, pickRefLogicalId as mr, A2A_PATH as mt, coerceRunRequest as n, createJwksCache as nn, resolveSsmParameters as nr, listPinnedTargets as nt, resolveServeBaseUrl as o, buildMethodArn as on, listTargets as or, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as ot, isCustomResourceLambdaTarget as p, buildCorsConfigByApiId as pn, discoverRoutes as pr, A2A_CONTAINER_PORT as pt, ImageOverrideError as q, LocalStateSourceError as qn, filterRoutesByApiIdentifiers as qt, coerceServeRequest as r, verifyCognitoJwt as rn, resolveWatchConfig as rr, buildCloudMapIndex as rt, createStudioServeManager as s, computeRequestIdentityHash as sn, availableWebSocketApiIdentifiers as sr, setShadowReadyTimeoutMs as st, addStudioSpecificOptions as t, buildJwksUrlFromIssuer as tn, collectSsmParameterRefs as tr, isLocalCdkAssetImage as tt, reinvoke as u, invokeTokenAuthorizer as un, filterWebSocketApisByIdentifiers as ur, addInvokeAgentCoreSpecificOptions as ut, renderStudioHtml as v, translateLambdaResponse as vn, AGENTCORE_HTTP_PROTOCOL as vr, MCP_PROTOCOL_VERSION as vt, addAlbSpecificOptions as w, selectIntegrationResponse as wn, derivePseudoParametersFromRegion as wr, invokeAgentCore as wt, addListSpecificOptions as x, buildRestV1Event as xn, AgentCoreResolutionError as xr, AGENTCORE_SIGV4_SERVICE as xt, createStudioStore as y, applyAuthorizerOverlay as yn, AGENTCORE_MCP_PROTOCOL as yr, mcpInvokeOnce as yt, addImageOverrideOptions as z, resolveRuntimeCodeMountPath as zn, createAuthorizerCache as zt };
32281
- //# sourceMappingURL=local-studio-BQC2kT2L.js.map
33379
+ export { addImageOverrideOptions as $, resolveRuntimeCodeMountPath as $n, createAuthorizerCache as $t, startCloudFrontServer as A, matchPreflight as An, AGENTCORE_A2A_PROTOCOL as Ar, MCP_CONTAINER_PORT as At, createLocalStartAlbCommand as B, VtlEvaluationError as Bn, substituteImagePlaceholders as Br, downloadAndExtractS3Bundle as Bt, formatTargetListing as C, invokeRequestAuthorizer as Cn, discoverWebSocketApisOrThrow as Cr, attachContainerLogStreamer as Ct, parseOriginOverrides as D, buildCorsConfigByApiId as Dn, discoverRoutes as Dr, A2A_CONTAINER_PORT as Dt, createLocalStartCloudFrontCommand as E, applyCorsResponseHeaders as En, webSocketApiMatchesIdentifier as Er, invokeAgentCoreWs as Et, compileCloudFrontFunction as F, buildRestV1Event as Fn, AgentCoreResolutionError as Fr, AGENTCORE_SIGV4_SERVICE as Ft, addStartServiceSpecificOptions as G, buildMgmtEndpointEnvUrl as Gn, toCmdArgv as Gt, resolveAlbTarget as H, probeHostGatewaySupport as Hn, LocalInvokeBuildError as Hr, buildAgentCoreCodeImage as Ht, runViewerRequest as I, evaluateResponseParameters as In, pickAgentCoreCandidateStack as Ir, signAgentCoreInvocation as It, addRunTaskSpecificOptions as J, buildConnectEvent as Jn, createLocalInvokeCommand as Jt, createLocalStartServiceCommand as K, handleConnectionsRequest as Kn, classifySourceChange as Kt, runViewerResponse as L, pickResponseTemplate as Ln, resolveAgentCoreTarget as Lr, AGENTCORE_SESSION_ID_HEADER as Lt, CLOUDFRONT_DISTRIBUTION_TYPE as M, translateLambdaResponse as Mn, AGENTCORE_HTTP_PROTOCOL as Mr, MCP_PROTOCOL_VERSION as Mt, isCloudFrontDistribution as N, applyAuthorizerOverlay as Nn, AGENTCORE_MCP_PROTOCOL as Nr, mcpInvokeOnce as Nt, resolveCloudFrontTarget as O, buildCorsConfigFromCloudFrontChain as On, pickRefLogicalId as Or, A2A_PATH as Ot, resolveCloudFrontDistribution as P, buildHttpApiV2Event as Pn, AGENTCORE_RUNTIME_TYPE as Pr, parseSseForJsonRpc as Pt, addEcsAssumeRoleOptions as Q, buildContainerImage as Qn, resolveApiTargetSubset as Qt, addAlbSpecificOptions as R, selectIntegrationResponse as Rn, derivePseudoParametersFromRegion as Rr, invokeAgentCore as Rt, createLocalListCommand as S, evaluateCachedLambdaPolicy as Sn, discoverWebSocketApis as Sr, getContainerNetworkIp as St, addStartCloudFrontSpecificOptions as T, attachAuthorizers as Tn, parseSelectionExpressionPath as Tr, createLocalInvokeAgentCoreCommand as Tt, isApplicationLoadBalancer as U, bufferToBody as Un, buildStsClientConfig as Ur, computeCodeImageTag as Ut, parseLbPortOverrides as V, HOST_GATEWAY_MIN_VERSION as Vn, tryResolveImageFnJoin as Vr, SUPPORTED_CODE_RUNTIMES as Vt, resolveAlbFrontDoor as W, ConnectionRegistry as Wn, resolveProfileCredentials as Wr, renderCodeDockerfile as Wt, MAX_TASKS_SUBNET_RANGE_CAP as X, buildMessageEvent as Xn, createLocalStartApiCommand as Xt, createLocalRunTaskCommand as Y, buildDisconnectEvent as Yn, addStartApiSpecificOptions as Yt, addCommonEcsServiceOptions as Z, architectureToPlatform as Zn, createWatchPredicates as Zt, toStudioTargetGroups as _, verifyCognitoJwt as _n, resolveWatchConfig as _r, buildCloudMapIndex as _t, createLocalStudioCommand as a, availableApiIdentifiers as an, substituteEnvVarsFromState as ar, resolveSharedSidecarCredentials as at, StudioEventBus as b, buildMethodArn as bn, listTargets as br, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as bt, startStudioProxy as c, groupRoutesByServer as cn, createLocalStateProvider as cr, buildImageOverrideTag as ct, createStudioDispatcher as d, resolveSelectionExpression as dn, resolveCfnFallbackRegion as dr, parseImageOverrideFlags as dt, createFileWatcher as en, resolveRuntimeFileExtension as er, buildEcsImageResolutionContext$1 as et, filterStudioCustomResources as f, resolveServiceIntegrationParameters as fn, resolveCfnRegion as fr, resolveImageOverrides as ft, startStudioServer as g, createJwksCache as gn, resolveSsmParameters as gr, listPinnedTargets as gt, filterStudioTargetGroups as h, buildJwksUrlFromIssuer as hn, collectSsmParameterRefs as hr, isLocalCdkAssetImage as ht, coerceStopRequest as i, resolveEnvVars$1 as in, substituteAgainstStateAsync as ir, resolveEcsAssumeRoleOption as it, serveFromStaticOrigin as j, matchRoute as jn, AGENTCORE_AGUI_PROTOCOL as jr, MCP_PATH as jt, matchBehavior as k, isFunctionUrlOacFronted as kn, resolveLambdaArnIntrinsic as kr, a2aInvokeOnce as kt, relayServeRequest as l, readMtlsMaterialsFromDisk as ln, isCfnFlagPresent as lr, enforceImageOverrideOrphans as lt, annotatePinnedEcsTargets as m, buildCognitoJwksUrl as mn, CfnLocalStateProvider as mr, describePinnedImageUri as mt, coerceRunRequest as n, buildStageMap as nn, EcsTaskResolutionError as nr, parseMaxTasks as nt, resolveServeBaseUrl as o, filterRoutesByApiIdentifier as on, substituteEnvVarsFromStateAsync as or, runEcsServiceEmulator as ot, isCustomResourceLambdaTarget as p, defaultCredentialsLoader as pn, resolveCfnStackName as pr, runImageOverrideBuilds as pt, serviceStrategy as q, parseConnectionsPath as qn, addInvokeSpecificOptions as qt, coerceServeRequest as r, materializeLayerFromArn as rn, substituteAgainstState as rr, parseRestartPolicy as rt, createStudioServeManager as s, filterRoutesByApiIdentifiers as sn, LocalStateSourceError as sr, ImageOverrideError as st, addStudioSpecificOptions as t, attachStageContext as tn, resolveRuntimeImage as tr, ecsClusterOption as tt, reinvoke as u, startApiServer as un, rejectExplicitCfnStackWithMultipleStacks as ur, mergeForService as ut, renderStudioHtml as v, verifyJwtAuthorizer as vn, resolveSingleTarget as vr, CloudMapRegistry as vt, LocalStartCloudFrontError as w, invokeTokenAuthorizer as wn, filterWebSocketApisByIdentifiers as wr, addInvokeAgentCoreSpecificOptions as wt, addListSpecificOptions as x, computeRequestIdentityHash as xn, availableWebSocketApiIdentifiers as xr, setShadowReadyTimeoutMs as xt, createStudioStore as y, verifyJwtViaDiscovery as yn, countTargets as yr, DEFAULT_SHADOW_READY_TIMEOUT_MS as yt, albStrategy as z, tryParseStatus as zn, formatStateRemedy as zr, waitForAgentCorePing as zt };
33380
+ //# sourceMappingURL=local-studio-ix442f3q.js.map