cdk-local 0.39.0 → 0.41.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.
@@ -3036,10 +3036,10 @@ function listTargets(stacks) {
3036
3036
  loadBalancers: sortEntries(scanApplicationLoadBalancers(stacks))
3037
3037
  };
3038
3038
  }
3039
- const pathOf = (e) => e.displayPath ?? e.qualifiedId;
3039
+ const pathOf$1 = (e) => e.displayPath ?? e.qualifiedId;
3040
3040
  /** Stable, human-readable ordering: by display path, falling back to the qualified ID. */
3041
3041
  function sortEntries(entries) {
3042
- return [...entries].sort((a, b) => pathOf(a).localeCompare(pathOf(b)));
3042
+ return [...entries].sort((a, b) => pathOf$1(a).localeCompare(pathOf$1(b)));
3043
3043
  }
3044
3044
  /** Display order for API surface kinds; entries are grouped in this order. */
3045
3045
  const API_KIND_ORDER = [
@@ -3060,7 +3060,7 @@ function sortApiEntries(entries) {
3060
3060
  const ka = API_KIND_ORDER.indexOf(a.kind ?? "");
3061
3061
  const kb = API_KIND_ORDER.indexOf(b.kind ?? "");
3062
3062
  if (ka !== kb) return ka - kb;
3063
- return pathOf(a).localeCompare(pathOf(b));
3063
+ return pathOf$1(a).localeCompare(pathOf$1(b));
3064
3064
  });
3065
3065
  }
3066
3066
  /** Total number of targets across every category. */
@@ -20230,9 +20230,15 @@ async function startFrontDoorServer(opts) {
20230
20230
  }
20231
20231
  function handleProxyRequest(req, res, opts) {
20232
20232
  return new Promise((resolve) => {
20233
- const endpoint = opts.pool.next();
20233
+ const pool = opts.selectPool(req.url ?? "/");
20234
+ if (!pool) {
20235
+ writeError(res, 404, `No listener rule matched '${req.url ?? "/"}' on ${opts.label}, and the listener has no default action forwarding to a local target.`);
20236
+ resolve();
20237
+ return;
20238
+ }
20239
+ const endpoint = pool.next();
20234
20240
  if (!endpoint) {
20235
- writeError(res, 503, `No running replicas for service '${opts.serviceName}'. The front-door has no healthy target to forward to.`);
20241
+ writeError(res, 503, `No running replicas behind ${opts.label} for the matched target. The front-door has no healthy target to forward to.`);
20236
20242
  resolve();
20237
20243
  return;
20238
20244
  }
@@ -20243,6 +20249,7 @@ function handleProxyRequest(req, res, opts) {
20243
20249
  resolve();
20244
20250
  };
20245
20251
  const headers = { ...req.headers };
20252
+ stripHopByHopHeaders(headers);
20246
20253
  appendForwardedHeaders(headers, req, opts.listenerPort);
20247
20254
  const proxyReq = request({
20248
20255
  host: endpoint.host,
@@ -20251,7 +20258,9 @@ function handleProxyRequest(req, res, opts) {
20251
20258
  path: req.url,
20252
20259
  headers
20253
20260
  }, (proxyRes) => {
20254
- res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
20261
+ const resHeaders = { ...proxyRes.headers };
20262
+ stripHopByHopHeaders(resHeaders);
20263
+ res.writeHead(proxyRes.statusCode ?? 502, resHeaders);
20255
20264
  proxyRes.pipe(res);
20256
20265
  proxyRes.on("end", done);
20257
20266
  proxyRes.on("error", () => {
@@ -20260,13 +20269,13 @@ function handleProxyRequest(req, res, opts) {
20260
20269
  });
20261
20270
  });
20262
20271
  proxyReq.setTimeout(opts.upstreamTimeoutMs ?? 3e4, () => {
20263
- if (!res.headersSent) writeError(res, 504, `Replica ${endpoint.host}:${endpoint.port} for service '${opts.serviceName}' did not respond in time.`);
20272
+ if (!res.headersSent) writeError(res, 504, `Replica ${endpoint.host}:${endpoint.port} behind ${opts.label} did not respond in time.`);
20264
20273
  else if (!res.writableEnded) res.destroy();
20265
20274
  proxyReq.destroy();
20266
20275
  done();
20267
20276
  });
20268
20277
  proxyReq.on("error", () => {
20269
- if (!res.headersSent) writeError(res, 502, `Failed to reach replica ${endpoint.host}:${endpoint.port} for service '${opts.serviceName}'.`);
20278
+ if (!res.headersSent) writeError(res, 502, `Failed to reach replica ${endpoint.host}:${endpoint.port} behind ${opts.label}.`);
20270
20279
  else if (!res.writableEnded) res.destroy();
20271
20280
  done();
20272
20281
  });
@@ -20276,6 +20285,33 @@ function handleProxyRequest(req, res, opts) {
20276
20285
  req.pipe(proxyReq);
20277
20286
  });
20278
20287
  }
20288
+ /** Standard hop-by-hop headers (RFC 7230 §6.1) — a proxy must not forward these. */
20289
+ const HOP_BY_HOP_HEADERS = [
20290
+ "connection",
20291
+ "keep-alive",
20292
+ "proxy-authenticate",
20293
+ "proxy-authorization",
20294
+ "te",
20295
+ "trailer",
20296
+ "transfer-encoding",
20297
+ "upgrade"
20298
+ ];
20299
+ /**
20300
+ * Strip hop-by-hop headers before relaying a request to / a response from the
20301
+ * upstream, mirroring what a real ALB does. Forwarding the upstream's
20302
+ * `Transfer-Encoding` / `Connection` verbatim while Node re-frames the body can
20303
+ * produce a malformed response; the headers named in a `Connection` token list
20304
+ * are also hop-by-hop and removed. Mutates `headers` in place.
20305
+ */
20306
+ function stripHopByHopHeaders(headers) {
20307
+ const connection = headers["connection"];
20308
+ const connectionValue = Array.isArray(connection) ? connection.join(",") : connection;
20309
+ if (connectionValue) for (const token of connectionValue.split(",")) {
20310
+ const name = token.trim().toLowerCase();
20311
+ if (name) delete headers[name];
20312
+ }
20313
+ for (const name of HOP_BY_HOP_HEADERS) delete headers[name];
20314
+ }
20279
20315
  /**
20280
20316
  * Inject the ALB-style forwarding headers a downstream app may read. Appends
20281
20317
  * the client IP to any existing `X-Forwarded-For` chain (ALB appends rather
@@ -20294,6 +20330,49 @@ function writeError(res, statusCode, message) {
20294
20330
  res.end(`${message}\n`);
20295
20331
  }
20296
20332
 
20333
+ //#endregion
20334
+ //#region src/local/alb-path-matcher.ts
20335
+ /**
20336
+ * Return the target of the highest-priority rule whose `path-pattern` matches
20337
+ * `requestPath`, or `undefined` when none match (caller uses the default).
20338
+ * Rules are evaluated in ascending priority; the input order is irrelevant.
20339
+ */
20340
+ function matchAlbPathRule(requestPath, rules) {
20341
+ const path = pathOf(requestPath);
20342
+ const ordered = [...rules].sort((a, b) => a.priority - b.priority);
20343
+ for (const rule of ordered) if (rule.pathPatterns.some((pattern) => albPathPatternMatches(pattern, path))) return rule.target;
20344
+ }
20345
+ /**
20346
+ * Whether a single ALB `path-pattern` value matches a request path. The path
20347
+ * must already be query-stripped, or pass a raw URL and it is stripped here.
20348
+ */
20349
+ function albPathPatternMatches(pattern, requestPath) {
20350
+ return globToRegExp(pattern).test(pathOf(requestPath));
20351
+ }
20352
+ /** Strip the query string / fragment so only the URL path is matched. */
20353
+ function pathOf(url) {
20354
+ let end = url.length;
20355
+ const q = url.indexOf("?");
20356
+ if (q !== -1) end = q;
20357
+ const h = url.indexOf("#");
20358
+ if (h !== -1 && h < end) end = h;
20359
+ return url.slice(0, end);
20360
+ }
20361
+ const REGEXP_META = /[.+^${}()|[\]\\]/;
20362
+ /**
20363
+ * Translate an ALB path-pattern glob into an anchored, case-sensitive RegExp:
20364
+ * `*` -> `.*`, `?` -> `.`, every other character is escaped and matched
20365
+ * literally.
20366
+ */
20367
+ function globToRegExp(pattern) {
20368
+ let body = "";
20369
+ for (const ch of pattern) if (ch === "*") body += ".*";
20370
+ else if (ch === "?") body += ".";
20371
+ else if (REGEXP_META.test(ch)) body += `\\${ch}`;
20372
+ else body += ch;
20373
+ return new RegExp(`^${body}$`);
20374
+ }
20375
+
20297
20376
  //#endregion
20298
20377
  //#region src/cli/commands/ecs-service-emulator.ts
20299
20378
  /**
@@ -20312,6 +20391,8 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
20312
20391
  let sigintCount = 0;
20313
20392
  let sharedNetwork;
20314
20393
  let profileCredsFile;
20394
+ let frontDoorServers = [];
20395
+ let frontDoorByService = /* @__PURE__ */ new Map();
20315
20396
  const cleanup = singleFlight(async () => {
20316
20397
  await Promise.allSettled(perTarget.map(async (pt) => {
20317
20398
  if (pt.controller) await pt.controller.shutdown();
@@ -20319,8 +20400,9 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
20319
20400
  await Promise.allSettled(pt.runState.replicas.map((r) => r.inFlightBoot).filter((p) => p !== void 0));
20320
20401
  await Promise.allSettled(pt.runState.replicas.map((r) => cleanupEcsRun(r.state, { keepRunning: false }).catch(() => void 0)));
20321
20402
  }
20322
- await Promise.allSettled((pt.frontDoorServers ?? []).map((s) => s.close().catch((err) => getLogger().warn(`front-door server teardown failed: ${err instanceof Error ? err.message : String(err)}`))));
20323
20403
  }));
20404
+ await Promise.allSettled(frontDoorServers.map((s) => s.close().catch((err) => getLogger().warn(`front-door server teardown failed: ${err instanceof Error ? err.message : String(err)}`))));
20405
+ frontDoorServers = [];
20324
20406
  if (profileCredsFile) {
20325
20407
  try {
20326
20408
  await profileCredsFile.dispose();
@@ -20363,7 +20445,7 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
20363
20445
  noun: strategy.pickerNoun,
20364
20446
  onMissing: () => strategy.onMissing()
20365
20447
  });
20366
- const { boots, warnings } = strategy.resolveBoots(stacks, resolvedTargets);
20448
+ const { boots, frontDoor, warnings } = strategy.resolveBoots(stacks, resolvedTargets);
20367
20449
  for (const w of warnings) logger.warn(w);
20368
20450
  if (boots.length === 0) throw new LocalStartServiceError(`No runnable ECS service resolved from ${resolvedTargets.join(", ")}.`);
20369
20451
  rejectExplicitCfnStackWithMultipleStacks(options, boots.length);
@@ -20395,6 +20477,11 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
20395
20477
  cloudMapIndexByStack,
20396
20478
  sharedNetwork
20397
20479
  };
20480
+ if (frontDoor && frontDoor.listeners.length > 0) {
20481
+ const built = await buildFrontDoor(frontDoor, options.containerHost, logger);
20482
+ frontDoorServers = built.servers;
20483
+ frontDoorByService = built.frontDoorByService;
20484
+ }
20398
20485
  sigintHandler = () => {
20399
20486
  sigintCount += 1;
20400
20487
  if (sigintCount >= 2) {
@@ -20406,11 +20493,7 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
20406
20493
  };
20407
20494
  process.on("SIGINT", sigintHandler);
20408
20495
  process.on("SIGTERM", sigintHandler);
20409
- for (const pt of perTarget) {
20410
- const booted = await bootOneTarget(pt.boot, pt.runState, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, strategy.lbPortOverrides);
20411
- pt.controller = booted.controller;
20412
- pt.frontDoorServers = booted.frontDoorServers;
20413
- }
20496
+ for (const pt of perTarget) pt.controller = await bootOneTarget(pt.boot, pt.runState, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorByService.get(pt.boot.target));
20414
20497
  const summary = perTarget.map((pt) => `${pt.controller.service.serviceName} (${pt.controller.activeReplicaCount()} replica(s))`).join(", ");
20415
20498
  logger.info(`Service(s) running: ${summary}.`);
20416
20499
  logger.info("Press ^C to shut down.");
@@ -20423,16 +20506,16 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
20423
20506
  await cleanup();
20424
20507
  }
20425
20508
  }
20426
- async function bootOneTarget(boot, runState, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, lbPortOverrides) {
20509
+ async function bootOneTarget(boot, runState, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools) {
20427
20510
  const candidate = pickCandidateStack(parseEcsTarget(boot.target).stackPattern, stacks);
20428
20511
  const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
20429
20512
  try {
20430
- return await runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, lbPortOverrides);
20513
+ return await runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools);
20431
20514
  } finally {
20432
20515
  if (stateProvider) stateProvider.dispose();
20433
20516
  }
20434
20517
  }
20435
- async function runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, lbPortOverrides) {
20518
+ async function runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools) {
20436
20519
  const logger = getLogger();
20437
20520
  const target = boot.target;
20438
20521
  const imageContext = await buildEcsImageResolutionContext(target, stacks, options, stateProvider);
@@ -20488,73 +20571,89 @@ async function runOneTarget(boot, runState, stacks, options, discovery, skipPull
20488
20571
  containerPath: profileCredsFile.containerPath,
20489
20572
  profileName: profileCredsFile.profileName
20490
20573
  };
20491
- const { frontDoorContext, frontDoorServers } = await startFrontDoorServers(boot.frontDoorTargets, service, options.containerHost, lbPortOverrides, logger);
20492
- const runnerOpts = {
20574
+ return startEcsService(service, {
20493
20575
  maxTasks: options.maxTasks,
20494
20576
  restartPolicy: options.restartPolicy,
20495
20577
  taskOptions: taskOpts,
20496
20578
  discovery,
20497
- ...frontDoorContext ? { frontDoor: frontDoorContext } : {}
20498
- };
20499
- let controller;
20500
- try {
20501
- controller = await startEcsService(service, runnerOpts, runState);
20502
- } catch (err) {
20503
- await Promise.allSettled(frontDoorServers.map((s) => s.close()));
20504
- throw err;
20505
- }
20506
- return {
20507
- controller,
20508
- frontDoorServers
20509
- };
20579
+ ...frontDoorPools && frontDoorPools.length > 0 ? { frontDoor: { pools: frontDoorPools } } : {}
20580
+ }, runState);
20510
20581
  }
20511
20582
  /**
20512
- * Issue #86 v1 — stand up one host-side reverse-proxy server per resolved
20513
- * front-door listener and return the {@link FrontDoorRunnerContext} to thread
20514
- * into the runner (so each replica publishes + registers its ephemeral
20515
- * endpoint), plus the started servers for teardown. Pure front-door MECHANISM:
20516
- * the ALB-specific resolution that produced `frontDoorTargets` lives in the
20517
- * `start-alb` command. Returns an undefined context + empty servers when there
20518
- * are no front-door targets (the pure-compute path).
20583
+ * Stand up one host-side reverse-proxy server PER LISTENER PORT from the
20584
+ * resolved {@link FrontDoorPlan}, path-routing each request across the services
20585
+ * the listener fronts, and return the started servers (for teardown) plus a
20586
+ * per-service-target pool list to thread into each service's runner (so every
20587
+ * replica publishes + registers its ephemeral endpoint into the right pool).
20588
+ *
20589
+ * One `FrontDoorEndpointPool` is created per distinct (service, container,
20590
+ * port) forward target and SHARED between the listener's routing table and the
20591
+ * owning service's runner context — same object on both sides, so a replica
20592
+ * registering itself is immediately reachable through the front-door.
20519
20593
  *
20520
20594
  * On a bind failure (e.g. EACCES on a privileged listener port, or the port is
20521
20595
  * already in use) every server started so far is closed and the error is
20522
20596
  * re-thrown with a `--lb-port` hint.
20523
20597
  */
20524
- async function startFrontDoorServers(frontDoorTargets, service, containerHost, lbPortOverrides, logger) {
20525
- if (frontDoorTargets.length === 0) return {
20526
- frontDoorContext: void 0,
20527
- frontDoorServers: []
20598
+ async function buildFrontDoor(plan, containerHost, logger) {
20599
+ const servers = [];
20600
+ const registry = /* @__PURE__ */ new Map();
20601
+ const poolFor = (t) => {
20602
+ const key = `${t.serviceTarget} ${t.targetContainerName} ${t.targetContainerPort}`;
20603
+ let entry = registry.get(key);
20604
+ if (!entry) {
20605
+ entry = {
20606
+ pool: new FrontDoorEndpointPool(),
20607
+ target: t
20608
+ };
20609
+ registry.set(key, entry);
20610
+ }
20611
+ return entry.pool;
20528
20612
  };
20529
- const frontDoorServers = [];
20530
- const pools = [];
20531
20613
  try {
20532
- for (const t of frontDoorTargets) {
20533
- const pool = new FrontDoorEndpointPool();
20614
+ for (const listener of plan.listeners) {
20615
+ const defaultPool = listener.defaultTarget ? poolFor(listener.defaultTarget) : void 0;
20616
+ const ruleRoutes = listener.rules.map((r) => ({
20617
+ priority: r.priority,
20618
+ pathPatterns: r.pathPatterns,
20619
+ target: poolFor(r.target)
20620
+ }));
20621
+ const selectPool = (requestPath) => matchAlbPathRule(requestPath, ruleRoutes) ?? defaultPool;
20534
20622
  const server = await startFrontDoorServer({
20535
- pool,
20536
- port: lbPortOverrides[t.listenerPort] ?? t.listenerPort,
20623
+ selectPool,
20624
+ port: listener.hostPort,
20537
20625
  host: containerHost,
20538
- listenerPort: t.listenerPort,
20539
- serviceName: service.serviceName
20540
- });
20541
- frontDoorServers.push(server);
20542
- pools.push({
20543
- pool,
20544
- targetContainerName: t.targetContainerName,
20545
- targetContainerPort: t.targetContainerPort
20626
+ listenerPort: listener.listenerPort,
20627
+ label: `listener port ${listener.listenerPort}`
20546
20628
  });
20547
- logger.info(`ALB front-door: http://${server.host}:${server.port} -> ${service.serviceName} (listener port ${t.listenerPort} -> container ${t.targetContainerName}:${t.targetContainerPort}, round-robin across replicas)`);
20629
+ servers.push(server);
20630
+ logger.info(`ALB front-door: http://${server.host}:${server.port} (listener port ${listener.listenerPort})`);
20631
+ if (listener.defaultTarget) logger.info(` default -> ${describeTarget(listener.defaultTarget)} (round-robin)`);
20632
+ for (const r of [...listener.rules].sort((a, b) => a.priority - b.priority)) logger.info(` path ${r.pathPatterns.join(", ")} (priority ${r.priority}) -> ${describeTarget(r.target)}`);
20633
+ if (!listener.defaultTarget) logger.info(" (no default action: unmatched paths return 404)");
20548
20634
  }
20549
20635
  } catch (err) {
20550
- await Promise.allSettled(frontDoorServers.map((s) => s.close()));
20551
- throw new LocalStartServiceError(`Failed to start ALB front-door for service '${service.serviceName}': ${err instanceof Error ? err.message : String(err)}. If the listener port is privileged (< 1024), remap it to a non-privileged host port with --lb-port <listenerPort>=<hostPort> (e.g. --lb-port 80=8080).`);
20636
+ await Promise.allSettled(servers.map((s) => s.close()));
20637
+ throw new LocalStartServiceError(`Failed to start ALB front-door: ${err instanceof Error ? err.message : String(err)}. If a listener port is privileged (< 1024), remap it to a non-privileged host port with --lb-port <listenerPort>=<hostPort> (e.g. --lb-port 80=8080).`);
20638
+ }
20639
+ const frontDoorByService = /* @__PURE__ */ new Map();
20640
+ for (const { pool, target } of registry.values()) {
20641
+ const list = frontDoorByService.get(target.serviceTarget) ?? [];
20642
+ list.push({
20643
+ pool,
20644
+ targetContainerName: target.targetContainerName,
20645
+ targetContainerPort: target.targetContainerPort
20646
+ });
20647
+ frontDoorByService.set(target.serviceTarget, list);
20552
20648
  }
20553
20649
  return {
20554
- frontDoorContext: { pools },
20555
- frontDoorServers
20650
+ servers,
20651
+ frontDoorByService
20556
20652
  };
20557
20653
  }
20654
+ function describeTarget(t) {
20655
+ return `${t.serviceTarget} (container ${t.targetContainerName}:${t.targetContainerPort})`;
20656
+ }
20558
20657
  async function resolvePlaceholderAccount(arn, region) {
20559
20658
  if (!arn.includes("${AWS::AccountId}")) return arn;
20560
20659
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
@@ -20729,10 +20828,7 @@ function serviceStrategy() {
20729
20828
  pickerNoun: "ECS services",
20730
20829
  onMissing: () => new LocalStartServiceError(`${getEmbedConfig().cliName} start-service requires at least one <target>. Pass one or more service paths like 'Stack/Orders' 'Stack/Frontend', or run it in a TTY to pick interactively.`),
20731
20830
  resolveBoots: (_stacks, chosenTargets) => ({
20732
- boots: chosenTargets.map((target) => ({
20733
- target,
20734
- frontDoorTargets: []
20735
- })),
20831
+ boots: chosenTargets.map((target) => ({ target })),
20736
20832
  warnings: []
20737
20833
  }),
20738
20834
  lbPortOverrides: {}
@@ -20755,47 +20851,57 @@ function createLocalStartServiceCommand(opts = {}) {
20755
20851
  //#endregion
20756
20852
  //#region src/local/elb-front-door-resolver.ts
20757
20853
  /**
20758
- * Issue #86 v1 — resolve an `AWS::ElasticLoadBalancingV2::LoadBalancer` (an
20759
- * ALB) into the backing ECS service(s) and the host listener port(s) a local
20760
- * front-door should expose for each. This is the `cdkl start-alb` entry: you
20761
- * name the ALB, and cdk-local discovers the services behind it (mirroring how
20762
- * `start-api` names the API and discovers the backing Lambdas).
20854
+ * Resolve an `AWS::ElasticLoadBalancingV2::LoadBalancer` (an ALB) into the
20855
+ * backing ECS service(s) and the host listener port(s) a local front-door
20856
+ * should expose. This is the `cdkl start-alb` entry: you name the ALB, and
20857
+ * cdk-local discovers the services behind it (mirroring how `start-api` names
20858
+ * the API and discovers the backing Lambdas).
20763
20859
  *
20764
20860
  * The synthesized linkage (confirmed against real `cdk synth` of
20765
- * `ApplicationLoadBalancedFargateService`):
20861
+ * `ApplicationLoadBalancedFargateService` + an `addAction` path rule):
20766
20862
  *
20767
20863
  * ```
20768
20864
  * ElasticLoadBalancingV2::LoadBalancer (the ALB you name)
20769
20865
  * ElasticLoadBalancingV2::Listener : { LoadBalancerArn:{Ref:<ALB>}, Port, Protocol,
20770
20866
  * DefaultActions:[{ Type:"forward", TargetGroupArn:{Ref:<TG>} }] }
20867
+ * ElasticLoadBalancingV2::ListenerRule : { ListenerArn:{Ref:<Listener>}, Priority,
20868
+ * Conditions:[{ Field:"path-pattern", PathPatternConfig:{ Values:["/api/*"] } }],
20869
+ * Actions:[{ Type:"forward", TargetGroupArn:{Ref:<TG>} }] }
20771
20870
  * ElasticLoadBalancingV2::TargetGroup : { Port, Protocol, TargetType:"ip" }
20772
20871
  * ECS::Service.LoadBalancers[] -> { ContainerName, ContainerPort, TargetGroupArn:{Ref:<TG>} }
20773
20872
  * ```
20774
20873
  *
20775
- * Resolution walks ALB -> listeners (by `LoadBalancerArn` Ref) -> default
20776
- * `forward` target groups -> the ECS Service whose `LoadBalancers[]` references
20777
- * that target group (a reverse scan; there is no direct TG -> service pointer).
20874
+ * Resolution walks ALB -> listeners (by `LoadBalancerArn` Ref) -> their default
20875
+ * `forward` action AND any `path-pattern` ListenerRules -> the ECS Service whose
20876
+ * `LoadBalancers[]` references each target group (a reverse scan; there is no
20877
+ * direct TG -> service pointer). Output is a per-listener routing table: a
20878
+ * default forward target (when the default action is a resolvable forward) plus
20879
+ * the ordered path-pattern rules.
20778
20880
  *
20779
- * v1 scope (single forward): only listener `DefaultActions` are honored.
20780
- * `AWS::ElasticLoadBalancingV2::ListenerRule` (path / host / weighted routing)
20781
- * is ignored — tracked in #123. HTTPS / TLS listeners and `TargetType:"lambda"`
20782
- * target groups are skipped with a warning.
20881
+ * Scope: HTTP listeners, `path-pattern` conditions, single-target `forward`
20882
+ * actions to ECS services. Skipped with a warning: HTTPS/TLS listeners,
20883
+ * `TargetType:"lambda"` target groups, weighted (multi-target) forwards, rules
20884
+ * with non-`path-pattern` conditions (host-header / http-header / query-string
20885
+ * / etc.), and `redirect` / `fixed-response` / `authenticate-*` actions. Those
20886
+ * remaining listener-rule features are tracked in #123.
20783
20887
  */
20784
20888
  const ALB_TYPE = "AWS::ElasticLoadBalancingV2::LoadBalancer";
20785
20889
  const LISTENER_TYPE = "AWS::ElasticLoadBalancingV2::Listener";
20890
+ const LISTENER_RULE_TYPE = "AWS::ElasticLoadBalancingV2::ListenerRule";
20786
20891
  const TARGET_GROUP_TYPE = "AWS::ElasticLoadBalancingV2::TargetGroup";
20787
20892
  const SERVICE_TYPE = "AWS::ECS::Service";
20788
20893
  /**
20789
- * Resolve an ALB into its backing services + front-door targets. Pure — reads
20790
- * only the supplied stack template. Returns an empty `services` array (with
20894
+ * Resolve an ALB into its front-door listeners + routing tables. Pure — reads
20895
+ * only the supplied stack template. Returns an empty `listeners` array (with
20791
20896
  * warnings) when the ALB fronts nothing cdk-local can serve locally.
20792
20897
  */
20793
20898
  function resolveAlbFrontDoor(stack, albLogicalId) {
20794
20899
  const warnings = [];
20795
20900
  const resources = stack.template.Resources ?? {};
20901
+ const stackName = stack.stackName;
20796
20902
  const tgToService = indexTargetGroupToService(resources);
20797
- const byService = /* @__PURE__ */ new Map();
20798
- const seenPortsByService = /* @__PURE__ */ new Map();
20903
+ const rulesByListener = indexRulesByListener(resources);
20904
+ const listeners = [];
20799
20905
  for (const [listenerLogicalId, resource] of Object.entries(resources)) {
20800
20906
  if (resource.Type !== LISTENER_TYPE) continue;
20801
20907
  const props = resource.Properties ?? {};
@@ -20803,54 +20909,40 @@ function resolveAlbFrontDoor(stack, albLogicalId) {
20803
20909
  const port = parsePort(props["Port"]);
20804
20910
  if (port === void 0) continue;
20805
20911
  const protocol = typeof props["Protocol"] === "string" ? props["Protocol"] : "HTTP";
20806
- const tgRefs = collectForwardTargetGroupRefs(props["DefaultActions"]);
20807
- if (tgRefs.size === 0) {
20808
- if (hasUnresolvableForward(props["DefaultActions"])) warnings.push(`Listener '${listenerLogicalId}' on port ${port} forwards to a non-Ref TargetGroupArn (literal / cross-stack / imported); the local front-door only supports in-stack target groups. Skipping it.`);
20809
- continue;
20810
- }
20811
20912
  if (protocol !== "HTTP") {
20812
- warnings.push(`Listener '${listenerLogicalId}' on port ${port} uses protocol ${protocol}; the local ALB front-door supports HTTP listeners only in v1 (TLS termination is deferred). Skipping it.`);
20913
+ warnings.push(`Listener '${listenerLogicalId}' on port ${port} uses protocol ${protocol}; the local ALB front-door supports HTTP listeners only (TLS termination is deferred). Skipping it.`);
20813
20914
  continue;
20814
20915
  }
20815
- for (const tgRef of tgRefs) {
20816
- const tg = resources[tgRef];
20817
- if (!tg || tg.Type !== TARGET_GROUP_TYPE) {
20818
- warnings.push(`Listener '${listenerLogicalId}' forwards to target group '${tgRef}', but no ${TARGET_GROUP_TYPE} with that logical id exists in ${stack.stackName}. Skipping it.`);
20819
- continue;
20820
- }
20821
- if (tg.Properties?.["TargetType"] === "lambda") {
20822
- warnings.push(`Target group '${tgRef}' is a Lambda target (TargetType: lambda). The local ALB front-door supports ECS targets only in v1; Lambda targets are deferred to a follow-up. Skipping it.`);
20823
- continue;
20824
- }
20825
- const backing = tgToService.get(tgRef);
20826
- if (!backing) {
20827
- warnings.push(`Target group '${tgRef}' (listener '${listenerLogicalId}', port ${port}) is not referenced by any ${SERVICE_TYPE}.LoadBalancers[] in ${stack.stackName}; cdk-local has no ECS service to front behind it. Skipping it.`);
20916
+ const defaultTarget = resolveForwardTarget(props["DefaultActions"], resources, tgToService, stackName, `Listener '${listenerLogicalId}' (port ${port}) default action`, warnings);
20917
+ const rules = [];
20918
+ for (const { ruleLogicalId, ruleProps } of rulesByListener.get(listenerLogicalId) ?? []) {
20919
+ const priority = parsePriority(ruleProps["Priority"]);
20920
+ const ruleLabel = `Listener rule '${ruleLogicalId}' (priority ${priority})`;
20921
+ const { patterns, unsupported } = parseRulePathPatterns(ruleProps["Conditions"]);
20922
+ if (unsupported.length > 0) {
20923
+ warnings.push(`${ruleLabel} uses unsupported condition(s): ${unsupported.join(", ")}. The local ALB front-door supports path-pattern conditions only (host-header / http-header / query-string / http-request-method / source-ip deferred). Skipping it.`);
20828
20924
  continue;
20829
20925
  }
20830
- const seenPorts = seenPortsByService.get(backing.serviceLogicalId) ?? /* @__PURE__ */ new Set();
20831
- if (seenPorts.has(port)) {
20832
- warnings.push(`Service '${backing.serviceLogicalId}' is fronted by more than one listener on host port ${port}; the local front-door fronts only the first.`);
20833
- continue;
20834
- }
20835
- seenPorts.add(port);
20836
- seenPortsByService.set(backing.serviceLogicalId, seenPorts);
20837
- const targets = byService.get(backing.serviceLogicalId) ?? [];
20838
- targets.push({
20839
- listenerPort: port,
20840
- listenerProtocol: "HTTP",
20841
- targetContainerName: backing.containerName,
20842
- targetContainerPort: backing.containerPort,
20843
- targetGroupLogicalId: tgRef,
20844
- listenerLogicalId
20926
+ if (patterns.length === 0) continue;
20927
+ const target = resolveForwardTarget(ruleProps["Actions"], resources, tgToService, stackName, `${ruleLabel} action`, warnings);
20928
+ if (!target) continue;
20929
+ rules.push({
20930
+ priority,
20931
+ pathPatterns: patterns,
20932
+ target
20845
20933
  });
20846
- byService.set(backing.serviceLogicalId, targets);
20847
20934
  }
20935
+ if (!defaultTarget && rules.length === 0) continue;
20936
+ listeners.push({
20937
+ listenerPort: port,
20938
+ listenerProtocol: "HTTP",
20939
+ listenerLogicalId,
20940
+ ...defaultTarget ? { defaultTarget } : {},
20941
+ rules
20942
+ });
20848
20943
  }
20849
20944
  return {
20850
- services: [...byService.entries()].map(([serviceLogicalId, targets]) => ({
20851
- serviceLogicalId,
20852
- targets
20853
- })),
20945
+ listeners,
20854
20946
  warnings
20855
20947
  };
20856
20948
  }
@@ -20861,6 +20953,43 @@ function isApplicationLoadBalancer(resource) {
20861
20953
  return type === void 0 || type === "application";
20862
20954
  }
20863
20955
  /**
20956
+ * Resolve a listener / rule `Actions` (or `DefaultActions`) array to a single
20957
+ * ECS-service forward target, or `undefined` when it is not a resolvable
20958
+ * single forward (warning emitted for the cases worth surfacing).
20959
+ */
20960
+ function resolveForwardTarget(actions, resources, tgToService, stackName, label, warnings) {
20961
+ const tgRefs = collectForwardTargetGroupRefs(actions);
20962
+ if (tgRefs.size === 0) {
20963
+ if (hasUnresolvableForward(actions)) warnings.push(`${label} forwards to a non-Ref TargetGroupArn (literal / cross-stack / imported); the local front-door only supports in-stack target groups. Skipping it.`);
20964
+ return;
20965
+ }
20966
+ if (tgRefs.size > 1) {
20967
+ warnings.push(`${label} is a weighted forward (multiple target groups); the local front-door supports a single target group per action (weighted routing deferred). Skipping it.`);
20968
+ return;
20969
+ }
20970
+ const tgRef = [...tgRefs][0];
20971
+ const tg = resources[tgRef];
20972
+ if (!tg || tg.Type !== TARGET_GROUP_TYPE) {
20973
+ warnings.push(`${label} forwards to target group '${tgRef}', but no ${TARGET_GROUP_TYPE} with that logical id exists in ${stackName}. Skipping it.`);
20974
+ return;
20975
+ }
20976
+ if (tg.Properties?.["TargetType"] === "lambda") {
20977
+ warnings.push(`${label} forwards to a Lambda target group '${tgRef}' (TargetType: lambda). The local ALB front-door supports ECS targets only; Lambda targets are deferred. Skipping it.`);
20978
+ return;
20979
+ }
20980
+ const backing = tgToService.get(tgRef);
20981
+ if (!backing) {
20982
+ warnings.push(`${label} forwards to target group '${tgRef}', which is not referenced by any ${SERVICE_TYPE}.LoadBalancers[] in ${stackName}; cdk-local has no ECS service to front behind it. Skipping it.`);
20983
+ return;
20984
+ }
20985
+ return {
20986
+ serviceLogicalId: backing.serviceLogicalId,
20987
+ targetContainerName: backing.containerName,
20988
+ targetContainerPort: backing.containerPort,
20989
+ targetGroupLogicalId: tgRef
20990
+ };
20991
+ }
20992
+ /**
20864
20993
  * Build a `targetGroupLogicalId -> backing ECS service` index by scanning every
20865
20994
  * `AWS::ECS::Service.LoadBalancers[]`. First service wins on a shared target
20866
20995
  * group (unusual; would only happen with a hand-rolled template).
@@ -20887,10 +21016,58 @@ function indexTargetGroupToService(resources) {
20887
21016
  }
20888
21017
  return index;
20889
21018
  }
20890
- function collectForwardTargetGroupRefs(defaultActions) {
21019
+ /** Group every `AWS::ElasticLoadBalancingV2::ListenerRule` by the listener it references. */
21020
+ function indexRulesByListener(resources) {
21021
+ const index = /* @__PURE__ */ new Map();
21022
+ for (const [ruleLogicalId, resource] of Object.entries(resources)) {
21023
+ if (resource.Type !== LISTENER_RULE_TYPE) continue;
21024
+ const ruleProps = resource.Properties ?? {};
21025
+ const listenerRef = refOf(ruleProps["ListenerArn"]);
21026
+ if (!listenerRef) continue;
21027
+ const list = index.get(listenerRef) ?? [];
21028
+ list.push({
21029
+ ruleLogicalId,
21030
+ ruleProps
21031
+ });
21032
+ index.set(listenerRef, list);
21033
+ }
21034
+ return index;
21035
+ }
21036
+ /**
21037
+ * Parse a ListenerRule's `Conditions` into its `path-pattern` values plus the
21038
+ * field names of any non-`path-pattern` conditions (which make the rule
21039
+ * unsupported in this version). A rule is only usable when every condition is a
21040
+ * `path-pattern` — ALB ANDs conditions together, and we cannot honor the others
21041
+ * locally yet.
21042
+ */
21043
+ function parseRulePathPatterns(conditions) {
21044
+ const patterns = [];
21045
+ const unsupported = [];
21046
+ if (!Array.isArray(conditions)) return {
21047
+ patterns,
21048
+ unsupported
21049
+ };
21050
+ for (const cond of conditions) {
21051
+ if (!cond || typeof cond !== "object") continue;
21052
+ const c = cond;
21053
+ const field = typeof c["Field"] === "string" ? c["Field"] : "(unknown)";
21054
+ if (field !== "path-pattern") {
21055
+ unsupported.push(field);
21056
+ continue;
21057
+ }
21058
+ const cfg = c["PathPatternConfig"];
21059
+ const values = cfg && typeof cfg === "object" && Array.isArray(cfg["Values"]) ? cfg["Values"] : Array.isArray(c["Values"]) ? c["Values"] : [];
21060
+ for (const v of values) if (typeof v === "string") patterns.push(v);
21061
+ }
21062
+ return {
21063
+ patterns,
21064
+ unsupported
21065
+ };
21066
+ }
21067
+ function collectForwardTargetGroupRefs(actions) {
20891
21068
  const refs = /* @__PURE__ */ new Set();
20892
- if (!Array.isArray(defaultActions)) return refs;
20893
- for (const action of defaultActions) {
21069
+ if (!Array.isArray(actions)) return refs;
21070
+ for (const action of actions) {
20894
21071
  if (!action || typeof action !== "object") continue;
20895
21072
  const a = action;
20896
21073
  if (a["Type"] !== "forward") continue;
@@ -20909,14 +21086,14 @@ function collectForwardTargetGroupRefs(defaultActions) {
20909
21086
  return refs;
20910
21087
  }
20911
21088
  /**
20912
- * True when `DefaultActions` has at least one `forward` action that references
20913
- * a target group via a NON-`Ref` arn (literal / `Fn::GetAtt` / cross-stack) —
20914
- * i.e. a forward we could not resolve to an in-stack target group. Used to warn
20915
- * rather than silently skip such a listener.
21089
+ * True when `actions` has at least one `forward` action that references a target
21090
+ * group via a NON-`Ref` arn (literal / `Fn::GetAtt` / cross-stack) — i.e. a
21091
+ * forward we could not resolve to an in-stack target group. Used to warn rather
21092
+ * than silently skip such a listener / rule.
20916
21093
  */
20917
- function hasUnresolvableForward(defaultActions) {
20918
- if (!Array.isArray(defaultActions)) return false;
20919
- for (const action of defaultActions) {
21094
+ function hasUnresolvableForward(actions) {
21095
+ if (!Array.isArray(actions)) return false;
21096
+ for (const action of actions) {
20920
21097
  if (!action || typeof action !== "object") continue;
20921
21098
  const a = action;
20922
21099
  if (a["Type"] !== "forward") continue;
@@ -20949,6 +21126,16 @@ function parsePort(raw) {
20949
21126
  function parseContainerPort(raw) {
20950
21127
  return parsePort(raw);
20951
21128
  }
21129
+ /**
21130
+ * Parse a ListenerRule `Priority` (ALB priorities are 1-50000, lower = higher
21131
+ * precedence). A missing / unparseable priority sorts last so an explicitly
21132
+ * prioritized rule always wins over it.
21133
+ */
21134
+ function parsePriority(raw) {
21135
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
21136
+ if (typeof raw === "string" && /^\d+$/.test(raw)) return parseInt(raw, 10);
21137
+ return Number.MAX_SAFE_INTEGER;
21138
+ }
20952
21139
 
20953
21140
  //#endregion
20954
21141
  //#region src/cli/commands/local-start-alb.ts
@@ -21030,31 +21217,52 @@ function albStrategy(options) {
21030
21217
  pickerNoun: "Application Load Balancers",
21031
21218
  onMissing: () => new LocalStartServiceError(`${getEmbedConfig().cliName} start-alb requires at least one <target>. Pass one or more ALB paths like 'Stack/MyAlb', or run it in a TTY to pick interactively.`),
21032
21219
  resolveBoots: (stacks, chosenTargets) => {
21033
- const byServiceTarget = /* @__PURE__ */ new Map();
21034
21220
  const warnings = [];
21221
+ const serviceTargets = /* @__PURE__ */ new Set();
21222
+ const listeners = [];
21223
+ const claimedHostPorts = /* @__PURE__ */ new Map();
21035
21224
  for (const albTarget of chosenTargets) {
21036
21225
  const { stack, albLogicalId } = resolveAlbTarget(albTarget, stacks);
21037
21226
  const resolution = resolveAlbFrontDoor(stack, albLogicalId);
21038
21227
  warnings.push(...resolution.warnings);
21039
- for (const svc of resolution.services) {
21040
- const target = `${stack.stackName}:${svc.serviceLogicalId}`;
21041
- const existing = byServiceTarget.get(target);
21042
- if (existing) existing.frontDoorTargets.push(...svc.targets);
21043
- else byServiceTarget.set(target, {
21044
- target,
21045
- frontDoorTargets: [...svc.targets]
21228
+ const qualify = (t) => {
21229
+ const serviceTarget = `${stack.stackName}:${t.serviceLogicalId}`;
21230
+ serviceTargets.add(serviceTarget);
21231
+ return {
21232
+ serviceTarget,
21233
+ targetContainerName: t.targetContainerName,
21234
+ targetContainerPort: t.targetContainerPort
21235
+ };
21236
+ };
21237
+ for (const listener of resolution.listeners) {
21238
+ const hostPort = lbPortOverrides[listener.listenerPort] ?? listener.listenerPort;
21239
+ const claimedBy = claimedHostPorts.get(hostPort);
21240
+ if (claimedBy !== void 0) {
21241
+ warnings.push(`Listener port ${listener.listenerPort} would bind host port ${hostPort}, already claimed by listener port ${claimedBy}; the local front-door fronts only the first. Use --lb-port to remap one of them.`);
21242
+ continue;
21243
+ }
21244
+ claimedHostPorts.set(hostPort, listener.listenerPort);
21245
+ listeners.push({
21246
+ listenerPort: listener.listenerPort,
21247
+ hostPort,
21248
+ ...listener.defaultTarget ? { defaultTarget: qualify(listener.defaultTarget) } : {},
21249
+ rules: listener.rules.map((r) => ({
21250
+ priority: r.priority,
21251
+ pathPatterns: r.pathPatterns,
21252
+ target: qualify(r.target)
21253
+ }))
21046
21254
  });
21047
21255
  }
21048
21256
  }
21049
- const boots = [...byServiceTarget.values()];
21050
- const resolvedPorts = /* @__PURE__ */ new Set();
21051
- for (const b of boots) for (const t of b.frontDoorTargets) resolvedPorts.add(t.listenerPort);
21257
+ const boots = [...serviceTargets].map((target) => ({ target }));
21258
+ const resolvedPorts = new Set(listeners.map((l) => l.listenerPort));
21052
21259
  for (const portStr of Object.keys(lbPortOverrides)) {
21053
21260
  const port = Number(portStr);
21054
21261
  if (!resolvedPorts.has(port)) warnings.push(`--lb-port override for listener port ${port} matched no ALB listener resolved for the named target(s); it was ignored.`);
21055
21262
  }
21056
21263
  return {
21057
21264
  boots,
21265
+ ...listeners.length > 0 ? { frontDoor: { listeners } } : {},
21058
21266
  warnings
21059
21267
  };
21060
21268
  },
@@ -21070,7 +21278,7 @@ function albStrategy(options) {
21070
21278
  */
21071
21279
  function createLocalStartAlbCommand(opts = {}) {
21072
21280
  setEmbedConfig(opts.embedConfig);
21073
- return addCommonEcsServiceOptions(new Command("start-alb").description("Run an Application Load Balancer locally: name the ALB, and cdk-local boots the ECS service(s) behind its HTTP forward listeners and stands up a local front-door on each listener port that round-robins across the running replicas — a single stable host endpoint, like behind a real load balancer. The symmetric ALB counterpart of `start-api`. Each <target> accepts a CDK display path (MyStack/MyAlb) or stack-qualified logical ID; single-stack apps may omit the stack prefix. v1 supports a single default-action forward to an HTTP listener; HTTPS listeners and Lambda target groups are skipped with a warning, and listener-rule routing is tracked separately. Omit <targets> in an interactive terminal to multi-select the load balancers from a list.").argument("[targets...]", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ElasticLoadBalancingV2::LoadBalancer resources to run (omit to multi-select interactively in a TTY)").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.")).action(withErrorHandling(async (targets, options) => {
21281
+ return addCommonEcsServiceOptions(new Command("start-alb").description("Run an Application Load Balancer locally: name the ALB, and cdk-local boots the ECS service(s) behind its HTTP listeners and stands up a local front-door on each listener port that round-robins across the running replicas and path-routes its path-pattern rules across the backing services — a stable host endpoint, like behind a real load balancer. The symmetric ALB counterpart of `start-api`. Each <target> accepts a CDK display path (MyStack/MyAlb) or stack-qualified logical ID; single-stack apps may omit the stack prefix. Supports HTTP listeners, single-target forward actions, and path-pattern rules; HTTPS listeners, Lambda target groups, weighted forwards, other rule conditions, and redirect/fixed-response actions are skipped with a warning. Omit <targets> in an interactive terminal to multi-select the load balancers from a list.").argument("[targets...]", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ElasticLoadBalancingV2::LoadBalancer resources to run (omit to multi-select interactively in a TTY)").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.")).action(withErrorHandling(async (targets, options) => {
21074
21282
  await runEcsServiceEmulator(targets, options, albStrategy(options), opts.extraStateProviders);
21075
21283
  })));
21076
21284
  }
@@ -21147,4 +21355,4 @@ function createLocalListCommand(opts = {}) {
21147
21355
 
21148
21356
  //#endregion
21149
21357
  export { createJwksCache as $, invokeTokenAuthorizer as A, resolveCfnRegion as At, VtlEvaluationError as B, parseSelectionExpressionPath as Bt, resolveSelectionExpression as C, resolveEnvVars as Ct, computeRequestIdentityHash as D, isCfnFlagPresent as Dt, buildMethodArn as E, createLocalStateProvider as Et, buildRestV1Event as F, resolveWatchConfig as Ft, buildMgmtEndpointEnvUrl as G, AGENTCORE_RUNTIME_TYPE as Gt, probeHostGatewaySupport as H, pickRefLogicalId as Ht, evaluateResponseParameters as I, countTargets as It, buildConnectEvent as J, derivePseudoParametersFromRegion as Jt, handleConnectionsRequest as K, AgentCoreResolutionError as Kt, pickResponseTemplate as L, listTargets as Lt, translateLambdaResponse as M, CfnLocalStateProvider as Mt, applyAuthorizerOverlay as N, collectSsmParameterRefs as Nt, evaluateCachedLambdaPolicy as O, rejectExplicitCfnStackWithMultipleStacks as Ot, buildHttpApiV2Event as P, resolveSsmParameters as Pt, buildJwksUrlFromIssuer as Q, selectIntegrationResponse as R, discoverWebSocketApis as Rt, startApiServer as S, substituteEnvVarsFromStateAsync as St, defaultCredentialsLoader as T, LocalStateSourceError as Tt, bufferToBody as U, resolveLambdaArnIntrinsic as Ut, HOST_GATEWAY_MIN_VERSION as V, discoverRoutes as Vt, ConnectionRegistry as W, AGENTCORE_HTTP_PROTOCOL as Wt, buildMessageEvent as X, tryResolveImageFnJoin as Xt, buildDisconnectEvent as Y, substituteImagePlaceholders as Yt, buildCognitoJwksUrl as Z, LocalInvokeBuildError as Zt, availableApiIdentifiers as _, resolveRuntimeImage as _t, buildCloudMapIndex as a, buildCorsConfigByApiId as at, groupRoutesByServer as b, substituteAgainstStateAsync as bt, createLocalRunTaskCommand as c, matchPreflight as ct, createWatchPredicates as d, waitForAgentCorePing as dt, verifyCognitoJwt as et, resolveApiTargetSubset as f, createLocalInvokeCommand as ft, buildStageMap as g, resolveRuntimeFileExtension as gt, attachStageContext as h, resolveRuntimeCodeMountPath as ht, createLocalStartServiceCommand as i, applyCorsResponseHeaders as it, matchRoute as j, resolveCfnStackName as jt, invokeRequestAuthorizer as k, resolveCfnFallbackRegion as kt, createLocalInvokeAgentCoreCommand as l, AGENTCORE_SESSION_ID_HEADER as lt, createFileWatcher as m, buildContainerImage as mt, formatTargetListing as n, verifyJwtViaDiscovery as nt, CloudMapRegistry as o, buildCorsConfigFromCloudFrontChain as ot, createAuthorizerCache as p, architectureToPlatform as pt, parseConnectionsPath as q, resolveAgentCoreTarget as qt, createLocalStartAlbCommand as r, attachAuthorizers as rt, getContainerNetworkIp as s, isFunctionUrlOacFronted as st, createLocalListCommand as t, verifyJwtAuthorizer as tt, createLocalStartApiCommand as u, invokeAgentCore as ut, filterRoutesByApiIdentifier as v, EcsTaskResolutionError as vt, resolveServiceIntegrationParameters as w, materializeLayerFromArn as wt, readMtlsMaterialsFromDisk as x, substituteEnvVarsFromState as xt, filterRoutesByApiIdentifiers as y, substituteAgainstState as yt, tryParseStatus as z, discoverWebSocketApisOrThrow as zt };
21150
- //# sourceMappingURL=local-list-iRB83Vtx.js.map
21358
+ //# sourceMappingURL=local-list-DDsa8FJV.js.map