cdk-local 0.63.0 → 0.64.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.
@@ -798,7 +798,7 @@ function parseTarget(target) {
798
798
  function resolveLambdaTarget(target, stacks) {
799
799
  if (stacks.length === 0) throw new LocalInvokeResolutionError("No stacks found in the synthesized assembly.");
800
800
  const parsed = parseTarget(target);
801
- const stack = pickStack$3(parsed, stacks);
801
+ const stack = pickStack$4(parsed, stacks);
802
802
  const template = stack.template;
803
803
  const resources = template.Resources ?? {};
804
804
  let match;
@@ -832,7 +832,7 @@ function resolveLambdaTarget(target, stacks) {
832
832
  * user may omit the stack prefix. Otherwise an explicit stack pattern is
833
833
  * required.
834
834
  */
835
- function pickStack$3(parsed, stacks) {
835
+ function pickStack$4(parsed, stacks) {
836
836
  if (parsed.stackPattern === null) {
837
837
  if (stacks.length === 1) return stacks[0];
838
838
  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(", ")}.`);
@@ -1241,7 +1241,7 @@ var AgentCoreResolutionError = class AgentCoreResolutionError extends Error {
1241
1241
  function resolveAgentCoreTarget(target, stacks, imageContext) {
1242
1242
  if (stacks.length === 0) throw new AgentCoreResolutionError("No stacks found in the synthesized assembly.");
1243
1243
  const parsed = parseTarget(target);
1244
- const stack = pickStack$2(parsed, stacks);
1244
+ const stack = pickStack$3(parsed, stacks);
1245
1245
  const resources = stack.template.Resources ?? {};
1246
1246
  const { logicalId, resource } = matchRuntime(parsed, target, stack, resources);
1247
1247
  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.`);
@@ -1269,7 +1269,7 @@ function pickAgentCoreCandidateStack(target, stacks) {
1269
1269
  * Mirrors the Lambda / ECS resolvers' behavior via the shared
1270
1270
  * stack-matcher.
1271
1271
  */
1272
- function pickStack$2(parsed, stacks) {
1272
+ function pickStack$3(parsed, stacks) {
1273
1273
  if (parsed.stackPattern === null) {
1274
1274
  if (stacks.length === 1) return stacks[0];
1275
1275
  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(", ")}.`);
@@ -5620,7 +5620,7 @@ function parseEcsTarget(target) {
5620
5620
  function resolveEcsTaskTarget(target, stacks, context) {
5621
5621
  if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
5622
5622
  const parsed = parseEcsTarget(target);
5623
- const stack = pickStack$1(parsed, stacks);
5623
+ const stack = pickStack$2(parsed, stacks);
5624
5624
  const resources = stack.template.Resources ?? {};
5625
5625
  let logicalId;
5626
5626
  let resource;
@@ -5641,7 +5641,7 @@ function resolveEcsTaskTarget(target, stacks, context) {
5641
5641
  if (resource.Type !== "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not an AWS::ECS::TaskDefinition.`);
5642
5642
  return extractTaskDefinitionProperties(stack, logicalId, resource, context);
5643
5643
  }
5644
- function pickStack$1(parsed, stacks) {
5644
+ function pickStack$2(parsed, stacks) {
5645
5645
  if (parsed.stackPattern === null) {
5646
5646
  if (stacks.length === 1) return stacks[0];
5647
5647
  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(", ")}.`);
@@ -18672,7 +18672,7 @@ function shellJoin(parts) {
18672
18672
  function resolveEcsServiceTarget(target, stacks, context) {
18673
18673
  if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
18674
18674
  const parsed = parseEcsTarget(target);
18675
- const stack = pickStack(parsed, stacks);
18675
+ const stack = pickStack$1(parsed, stacks);
18676
18676
  const resources = stack.template.Resources ?? {};
18677
18677
  let serviceLogicalId;
18678
18678
  let serviceResource;
@@ -18874,7 +18874,7 @@ function parseServiceName(raw, serviceLogicalId) {
18874
18874
  * service-specific extensions (e.g. cross-stack service-to-task refs)
18875
18875
  * can diverge without breaking the run-task code path.
18876
18876
  */
18877
- function pickStack(parsed, stacks) {
18877
+ function pickStack$1(parsed, stacks) {
18878
18878
  if (parsed.stackPattern === null) {
18879
18879
  if (stacks.length === 1) return stacks[0];
18880
18880
  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'.`);
@@ -20104,8 +20104,13 @@ const DEFAULT_UPSTREAM_TIMEOUT_MS = 3e4;
20104
20104
  async function startFrontDoorServer(opts) {
20105
20105
  const logger = getLogger().child("front-door");
20106
20106
  const host = opts.host ?? "127.0.0.1";
20107
+ const forwardedProto = opts.forwardedProto ?? (opts.tls ? "https" : "http");
20108
+ const effectiveOpts = {
20109
+ ...opts,
20110
+ forwardedProto
20111
+ };
20107
20112
  const requestHandler = (req, res) => {
20108
- handleProxyRequest(req, res, opts).catch((err) => {
20113
+ handleProxyRequest(req, res, effectiveOpts).catch((err) => {
20109
20114
  logger.debug(`front-door request error: ${err instanceof Error ? err.message : String(err)}`);
20110
20115
  if (!res.headersSent) writeError(res, 502, "Bad Gateway");
20111
20116
  });
@@ -20117,7 +20122,7 @@ async function startFrontDoorServer(opts) {
20117
20122
  const scheme = opts.tls ? "https" : "http";
20118
20123
  server.on("connection", (socket) => socket.setNoDelay(true));
20119
20124
  server.on("upgrade", (req, clientSocket, head) => {
20120
- handleUpgrade(req, clientSocket, head, opts).catch((err) => {
20125
+ handleUpgrade(req, clientSocket, head, effectiveOpts).catch((err) => {
20121
20126
  logger.debug(`front-door upgrade error: ${err instanceof Error ? err.message : String(err)}`);
20122
20127
  if (!clientSocket.destroyed) clientSocket.destroy();
20123
20128
  });
@@ -20206,7 +20211,7 @@ function handleProxyRequest(req, res, opts) {
20206
20211
  function serveAction(req, res, action, opts) {
20207
20212
  if (action.kind === "redirect" || action.kind === "fixed-response") {
20208
20213
  req.resume();
20209
- if (action.kind === "redirect") writeRedirect(res, action, req, opts.listenerPort, opts.tls ? "https" : "http");
20214
+ if (action.kind === "redirect") writeRedirect(res, action, req, opts.listenerPort, resolveForwardedProto(opts));
20210
20215
  else writeFixedResponse(res, action);
20211
20216
  return Promise.resolve();
20212
20217
  }
@@ -20258,7 +20263,7 @@ function handlePoolRequest(req, res, pool, opts) {
20258
20263
  };
20259
20264
  const headers = { ...req.headers };
20260
20265
  stripHopByHopHeaders(headers);
20261
- appendForwardedHeaders(headers, req, opts.listenerPort, opts.tls ? "https" : "http");
20266
+ appendForwardedHeaders(headers, req, opts.listenerPort, resolveForwardedProto(opts));
20262
20267
  const proxyReq = request({
20263
20268
  host: endpoint.host,
20264
20269
  port: endpoint.port,
@@ -20414,7 +20419,7 @@ function handleLambdaRequest(req, res, lambda, opts) {
20414
20419
  const body = Buffer.concat(chunks);
20415
20420
  const forwardHeaders = { ...req.headers };
20416
20421
  stripHopByHopHeaders(forwardHeaders);
20417
- appendForwardedHeaders(forwardHeaders, req, opts.listenerPort, opts.tls ? "https" : "http");
20422
+ appendForwardedHeaders(forwardHeaders, req, opts.listenerPort, resolveForwardedProto(opts));
20418
20423
  const snapshot = snapshotFromIncoming(req, body);
20419
20424
  for (const [name, value] of Object.entries(forwardHeaders)) {
20420
20425
  if (value === void 0) continue;
@@ -20474,6 +20479,14 @@ function stripHopByHopHeaders(headers) {
20474
20479
  for (const name of HOP_BY_HOP_HEADERS) delete headers[name];
20475
20480
  }
20476
20481
  /**
20482
+ * Resolve the scheme to stamp on `X-Forwarded-Proto` and to default
20483
+ * redirect `#{protocol}` to: an explicit {@link StartFrontDoorServerOptions.forwardedProto}
20484
+ * override wins, otherwise it follows the wire (TLS = `https`, no TLS = `http`).
20485
+ */
20486
+ function resolveForwardedProto(opts) {
20487
+ return opts.forwardedProto ?? (opts.tls ? "https" : "http");
20488
+ }
20489
+ /**
20477
20490
  * Inject the ALB-style forwarding headers a downstream app may read. Appends
20478
20491
  * the client IP to any existing `X-Forwarded-For` chain (ALB appends rather
20479
20492
  * than replaces) and stamps the scheme / listener port. `scheme` follows the
@@ -20519,7 +20532,7 @@ function handleUpgrade(req, clientSocket, head, opts) {
20519
20532
  }
20520
20533
  const proceed = () => {
20521
20534
  if (action.kind === "redirect") {
20522
- writeRawHttpRedirect(clientSocket, action, req, opts.listenerPort, opts.tls ? "https" : "http");
20535
+ writeRawHttpRedirect(clientSocket, action, req, opts.listenerPort, resolveForwardedProto(opts));
20523
20536
  return Promise.resolve();
20524
20537
  }
20525
20538
  if (action.kind === "fixed-response") {
@@ -20585,7 +20598,7 @@ function bridgeWebSocket(req, clientSocket, head, pool, opts) {
20585
20598
  upstream.on("connect", () => {
20586
20599
  upstreamConnected = true;
20587
20600
  const headers = { ...req.headers };
20588
- appendForwardedHeaders(headers, req, opts.listenerPort, opts.tls ? "https" : "http");
20601
+ appendForwardedHeaders(headers, req, opts.listenerPort, resolveForwardedProto(opts));
20589
20602
  const lines = [`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/${req.httpVersion}`];
20590
20603
  for (const [name, value] of Object.entries(headers)) {
20591
20604
  if (value === void 0) continue;
@@ -21722,7 +21735,9 @@ async function buildFrontDoor(plan, options, logger) {
21722
21735
  ...action.messageBody !== void 0 && { messageBody: action.messageBody }
21723
21736
  };
21724
21737
  };
21725
- const tlsMaterials = plan.listeners.some((l) => l.protocol === "HTTPS") ? await resolveFrontDoorTlsMaterials({
21738
+ const hasHttpsListener = plan.listeners.some((l) => l.protocol === "HTTPS");
21739
+ const wantTls = options.tls === true || options.tlsCert !== void 0 || options.tlsKey !== void 0;
21740
+ const tlsMaterials = hasHttpsListener && wantTls ? await resolveFrontDoorTlsMaterials({
21726
21741
  certPath: options.tlsCert,
21727
21742
  keyPath: options.tlsKey
21728
21743
  }) : void 0;
@@ -21751,17 +21766,21 @@ async function buildFrontDoor(plan, options, logger) {
21751
21766
  target: attachAuth(toRouteAction(r.action), r.authGuard)
21752
21767
  }));
21753
21768
  const route = (req) => matchAlbPathRule(req, ruleRoutes) ?? defaultRoute;
21754
- const tls = listener.protocol === "HTTPS" ? tlsMaterials : void 0;
21769
+ const tls = listener.protocol === "HTTPS" && wantTls ? tlsMaterials : void 0;
21770
+ const forwardedProto = listener.protocol === "HTTPS" ? "https" : "http";
21771
+ const degradedHttps = listener.protocol === "HTTPS" && !wantTls;
21755
21772
  const server = await startFrontDoorServer({
21756
21773
  route,
21757
21774
  port: listener.hostPort,
21758
21775
  host: containerHost,
21759
21776
  listenerPort: listener.listenerPort,
21760
21777
  label: `listener port ${listener.listenerPort}`,
21778
+ forwardedProto,
21761
21779
  ...tls ? { tls } : {}
21762
21780
  });
21763
21781
  servers.push(server);
21764
21782
  logger.info(`ALB front-door: ${server.scheme}://${server.host}:${server.port} (listener port ${listener.listenerPort})`);
21783
+ if (degradedHttps) logger.warn(` WARN: listener port ${listener.listenerPort} is HTTPS in the cloud but serving HTTP locally (X-Forwarded-Proto: https preserved). Pass --tls to terminate TLS locally with a self-signed or user-supplied cert.`);
21765
21784
  if (listener.defaultAction) logger.info(` default -> ${describeAction(listener.defaultAction)}`);
21766
21785
  for (const r of [...listener.rules].sort((a, b) => a.priority - b.priority)) logger.info(` ${describeConditions(r)} (priority ${r.priority}) -> ${describeAction(r.action)}`);
21767
21786
  if (!listener.defaultAction) logger.info(" (no default action: unmatched requests return 404)");
@@ -22667,5 +22686,209 @@ function parseAuthenticateAction(action, type, label, warnings) {
22667
22686
  }
22668
22687
 
22669
22688
  //#endregion
22670
- export { buildMgmtEndpointEnvUrl as $, resolveCdkPathToLogicalIds as $n, resolveRuntimeCodeMountPath as $t, startApiServer as A, countTargets as An, waitForAgentCorePing as At, translateLambdaResponse as B, AGENTCORE_HTTP_PROTOCOL as Bn, getDockerImageBySourceHash as Bt, attachStageContext as C, CfnLocalStateProvider as Cn, MCP_PROTOCOL_VERSION as Ct, filterRoutesByApiIdentifiers as D, resolveWatchConfig as Dn, signAgentCoreInvocation as Dt, filterRoutesByApiIdentifier as E, resolveApp as En, AGENTCORE_SIGV4_SERVICE as Et, computeRequestIdentityHash as F, discoverRoutes as Fn, renderCodeDockerfile as Ft, pickResponseTemplate as G, resolveAgentCoreTarget as Gn, parseEcrUri as Gt, buildHttpApiV2Event as H, AGENTCORE_RUNTIME_TYPE as Hn, waitForRieReady as Ht, evaluateCachedLambdaPolicy as I, pickRefLogicalId as In, toCmdArgv as It, VtlEvaluationError as J, substituteImagePlaceholders as Jn, pickFreePort as Jt, selectIntegrationResponse as K, resolveLambdaTarget as Kn, pullEcrImage as Kt, invokeRequestAuthorizer as L, resolveLambdaArnIntrinsic as Ln, writeProfileCredentialsFile as Lt, resolveServiceIntegrationParameters as M, discoverWebSocketApis as Mn, SUPPORTED_CODE_RUNTIMES as Mt, defaultCredentialsLoader as N, discoverWebSocketApisOrThrow as Nn, buildAgentCoreCodeImage as Nt, groupRoutesByServer as O, Synthesizer as On, AGENTCORE_SESSION_ID_HEADER as Ot, buildMethodArn as P, parseSelectionExpressionPath as Pn, computeCodeImageTag as Pt, ConnectionRegistry as Q, readCdkPathOrUndefined as Qn, streamLogs as Qt, invokeTokenAuthorizer as R, AGENTCORE_A2A_PROTOCOL as Rn, singleFlight as Rt, createFileWatcher as S, resolveCfnStackName as Sn, MCP_PATH as St, availableApiIdentifiers as T, resolveSsmParameters as Tn, parseSseForJsonRpc as Tt, buildRestV1Event as U, AgentCoreResolutionError as Un, architectureToPlatform as Ut, applyAuthorizerOverlay as V, AGENTCORE_MCP_PROTOCOL as Vn, invokeRie as Vt, evaluateResponseParameters as W, pickAgentCoreCandidateStack as Wn, buildContainerImage as Wt, probeHostGatewaySupport as X, matchStacks as Xn, removeContainer as Xt, HOST_GATEWAY_MIN_VERSION as Y, tryResolveImageFnJoin as Yn, pullImage as Yt, bufferToBody as Z, buildCdkPathIndex as Zn, runDetached as Zt, createLocalStartApiCommand as _, createLocalStateProvider as _n, invokeAgentCoreWs as _t, buildEcsImageResolutionContext as a, derivePartitionAndUrlSuffix as an, appOptions as ar, buildCognitoJwksUrl as at, resolveProfileCredentials as b, resolveCfnFallbackRegion as bn, a2aInvokeOnce as bt, resolveSharedSidecarCredentials as c, resolveEcsTaskTarget as cn, deprecatedRegionOption as cr, verifyCognitoJwt as ct, CloudMapRegistry as d, substituteAgainstStateAsync as dn, attachAuthorizers as dt, resolveRuntimeFileExtension as en, CdkLocalError as er, handleConnectionsRequest as et, getContainerNetworkIp as f, substituteEnvVarsFromState as fn, applyCorsResponseHeaders as ft, runEcsTask as g, LocalStateSourceError as gn, matchPreflight as gt, parseHostPortOverrides as h, materializeLayerFromArn as hn, isFunctionUrlOacFronted as ht, addCommonEcsServiceOptions as i, applyCrossStackResolverToTask as in, applyRoleArnIfSet as ir, buildMessageEvent as it, resolveSelectionExpression as j, listTargets as jn, downloadAndExtractS3Bundle as jt, readMtlsMaterialsFromDisk as k, resolveSingleTarget as kn, invokeAgentCore as kt, runEcsServiceEmulator as l, applyDeployedEnvFallback as ln, parseContextOptions as lr, verifyJwtAuthorizer as lt, createEcsRunState as m, resolveEnvVars as mn, buildCorsConfigFromCloudFrontChain as mt, resolveAlbFrontDoor as n, EcsTaskResolutionError as nn, LocalStartServiceError as nr, buildConnectEvent as nt, parseMaxTasks as o, detectEcsImageResolutionNeeds as on, commonOptions as or, buildJwksUrlFromIssuer as ot, cleanupEcsRun as p, substituteEnvVarsFromStateAsync as pn, buildCorsConfigByApiId as pt, tryParseStatus as q, derivePseudoParametersFromRegion as qn, ensureDockerAvailable as qt, MAX_TASKS_SUBNET_RANGE_CAP as r, TASK_ROLE_ACCOUNT_PLACEHOLDER as rn, withErrorHandling as rr, buildDisconnectEvent as rt, parseRestartPolicy as s, parseEcsTarget as sn, contextOptions as sr, createJwksCache as st, isApplicationLoadBalancer as t, resolveRuntimeImage as tn, LocalInvokeBuildError as tr, parseConnectionsPath as tt, buildCloudMapIndex as u, substituteAgainstState as un, warnIfDeprecatedRegion as ur, verifyJwtViaDiscovery as ut, createWatchPredicates as v, isCfnFlagPresent as vn, A2A_CONTAINER_PORT as vt, buildStageMap as w, collectSsmParameterRefs as wn, mcpInvokeOnce as wt, createAuthorizerCache as x, resolveCfnRegion as xn, MCP_CONTAINER_PORT as xt, resolveApiTargetSubset as y, rejectExplicitCfnStackWithMultipleStacks as yn, A2A_PATH as yt, matchRoute as z, AGENTCORE_AGUI_PROTOCOL as zn, AssetManifestLoader as zt };
22671
- //# sourceMappingURL=elb-front-door-resolver-y5FicgLi.js.map
22689
+ //#region src/cli/commands/local-start-alb.ts
22690
+ /**
22691
+ * Issue #86 v1 — parse `--lb-port <listenerPort>=<hostPort>` overrides into a
22692
+ * `listenerPort -> hostPort` map. The local ALB front-door binds the listener
22693
+ * port on the host by default, but a privileged listener port (e.g. 80 / 443)
22694
+ * fails to bind as non-root on macOS, so the user opts in to a non-privileged
22695
+ * host port (e.g. `--lb-port 80=8080`). Repeatable; each value is
22696
+ * `<listenerPort>=<hostPort>` with both in 1-65535.
22697
+ */
22698
+ function parseLbPortOverrides(values) {
22699
+ const out = {};
22700
+ for (const raw of values ?? []) {
22701
+ const m = /^(\d+)=(\d+)$/.exec(raw.trim());
22702
+ if (!m) throw new LocalStartServiceError(`Invalid --lb-port '${raw}'. Expected <listenerPort>=<hostPort> (e.g. 80=8080).`);
22703
+ const listenerPort = Number(m[1]);
22704
+ const hostPort = Number(m[2]);
22705
+ for (const [label, p] of [["listener", listenerPort], ["host", hostPort]]) if (p < 1 || p > 65535) throw new LocalStartServiceError(`Invalid --lb-port '${raw}': ${label} port must be 1-65535.`);
22706
+ out[listenerPort] = hostPort;
22707
+ }
22708
+ return out;
22709
+ }
22710
+ /**
22711
+ * Resolve an ALB target string (`Stack/Path` display path or `Stack:LogicalId`)
22712
+ * to its stack + `AWS::ElasticLoadBalancingV2::LoadBalancer` logical id. Mirrors
22713
+ * the ECS service resolver's target grammar.
22714
+ */
22715
+ function resolveAlbTarget(target, stacks) {
22716
+ if (stacks.length === 0) throw new LocalStartServiceError("No stacks found in the synthesized assembly.");
22717
+ const parsed = parseEcsTarget(target);
22718
+ const stack = pickStack(parsed.stackPattern, stacks, target);
22719
+ const resources = stack.template.Resources ?? {};
22720
+ if (parsed.isPath) {
22721
+ const index = buildCdkPathIndex(stack.template);
22722
+ const albs = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId }) => {
22723
+ const r = resources[logicalId];
22724
+ return r !== void 0 && isApplicationLoadBalancer(r);
22725
+ });
22726
+ if (albs.length === 0) throw notFound(target, stack, resources);
22727
+ 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.`);
22728
+ return {
22729
+ stack,
22730
+ albLogicalId: albs[0].logicalId
22731
+ };
22732
+ }
22733
+ const res = resources[parsed.pathOrId];
22734
+ if (!res || !isApplicationLoadBalancer(res)) throw notFound(target, stack, resources);
22735
+ return {
22736
+ stack,
22737
+ albLogicalId: parsed.pathOrId
22738
+ };
22739
+ }
22740
+ function pickStack(stackPattern, stacks, target) {
22741
+ if (stackPattern === null) {
22742
+ if (stacks.length === 1) return stacks[0];
22743
+ 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'.`);
22744
+ }
22745
+ const matched = matchStacks(stacks, [stackPattern]);
22746
+ if (matched.length === 0) throw new LocalStartServiceError(`No stack matches '${stackPattern}'. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
22747
+ if (matched.length > 1) throw new LocalStartServiceError(`Multiple stacks match '${stackPattern}': ${matched.map((s) => s.stackName).join(", ")}. Refine the pattern.`);
22748
+ return matched[0];
22749
+ }
22750
+ function notFound(target, stack, resources) {
22751
+ const albs = Object.entries(resources).filter(([, r]) => r.Type === "AWS::ElasticLoadBalancingV2::LoadBalancer").map(([logicalId]) => logicalId);
22752
+ const available = albs.length > 0 ? ` Available load balancers in ${stack.stackName}: ${albs.join(", ")}.` : ` ${stack.stackName} declares no AWS::ElasticLoadBalancingV2::LoadBalancer resources.`;
22753
+ return new LocalStartServiceError(`Target '${target}' did not match an application Load Balancer in ${stack.stackName}.${available}`);
22754
+ }
22755
+ /**
22756
+ * `cdkl start-alb` strategy — name the ALB, boot the ECS service(s) behind it,
22757
+ * and expose each listener via a local front-door. Mirrors how `start-api`
22758
+ * names the API and serves its backing Lambdas.
22759
+ */
22760
+ function albStrategy(options) {
22761
+ const lbPortOverrides = parseLbPortOverrides(options.lbPort);
22762
+ return {
22763
+ pickEntries: (stacks) => listTargets(stacks).loadBalancers,
22764
+ pickerMessage: "Select one or more Application Load Balancers to run",
22765
+ pickerNoun: "Application Load Balancers",
22766
+ 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.`),
22767
+ resolveBoots: (stacks, chosenTargets) => {
22768
+ const warnings = [];
22769
+ const serviceTargets = /* @__PURE__ */ new Set();
22770
+ const listeners = [];
22771
+ const claimedHostPorts = /* @__PURE__ */ new Map();
22772
+ for (const albTarget of chosenTargets) {
22773
+ const { stack, albLogicalId } = resolveAlbTarget(albTarget, stacks);
22774
+ const resolution = resolveAlbFrontDoor(stack, albLogicalId);
22775
+ warnings.push(...resolution.warnings);
22776
+ const qualifyTarget = (t) => {
22777
+ if (t.kind === "lambda") return {
22778
+ kind: "lambda",
22779
+ lambda: resolveLambdaTarget(`${stack.stackName}:${t.lambdaLogicalId}`, stacks),
22780
+ targetGroupArn: `${stack.stackName}:${t.targetGroupLogicalId}`,
22781
+ multiValueHeaders: t.multiValueHeaders,
22782
+ weight: t.weight
22783
+ };
22784
+ const serviceTarget = `${stack.stackName}:${t.serviceLogicalId}`;
22785
+ serviceTargets.add(serviceTarget);
22786
+ return {
22787
+ kind: "ecs",
22788
+ serviceTarget,
22789
+ targetContainerName: t.targetContainerName,
22790
+ targetContainerPort: t.targetContainerPort,
22791
+ weight: t.weight
22792
+ };
22793
+ };
22794
+ const qualify = (action) => {
22795
+ if (action.kind === "forward") return {
22796
+ kind: "forward",
22797
+ targets: action.targets.map(qualifyTarget)
22798
+ };
22799
+ if (action.kind === "redirect") return {
22800
+ kind: "redirect",
22801
+ statusCode: action.statusCode,
22802
+ ...action.protocol !== void 0 && { protocol: action.protocol },
22803
+ ...action.host !== void 0 && { host: action.host },
22804
+ ...action.port !== void 0 && { port: action.port },
22805
+ ...action.path !== void 0 && { path: action.path },
22806
+ ...action.query !== void 0 && { query: action.query }
22807
+ };
22808
+ return {
22809
+ kind: "fixed-response",
22810
+ statusCode: action.statusCode,
22811
+ ...action.contentType !== void 0 && { contentType: action.contentType },
22812
+ ...action.messageBody !== void 0 && { messageBody: action.messageBody }
22813
+ };
22814
+ };
22815
+ for (const listener of resolution.listeners) {
22816
+ const hostPort = lbPortOverrides[listener.listenerPort] ?? listener.listenerPort;
22817
+ const claimedBy = claimedHostPorts.get(hostPort);
22818
+ if (claimedBy !== void 0) {
22819
+ 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.`);
22820
+ continue;
22821
+ }
22822
+ claimedHostPorts.set(hostPort, listener.listenerPort);
22823
+ listeners.push({
22824
+ listenerPort: listener.listenerPort,
22825
+ hostPort,
22826
+ protocol: listener.listenerProtocol,
22827
+ ...listener.defaultAction ? { defaultAction: qualify(listener.defaultAction) } : {},
22828
+ ...listener.defaultAuthGuard ? { defaultAuthGuard: listener.defaultAuthGuard } : {},
22829
+ rules: listener.rules.map((r) => ({
22830
+ priority: r.priority,
22831
+ pathPatterns: r.pathPatterns,
22832
+ hostPatterns: r.hostPatterns,
22833
+ httpHeaderConditions: r.httpHeaderConditions,
22834
+ httpRequestMethods: r.httpRequestMethods,
22835
+ queryStringConditions: r.queryStringConditions,
22836
+ sourceIpCidrs: r.sourceIpCidrs,
22837
+ action: qualify(r.action),
22838
+ ...r.authGuard ? { authGuard: r.authGuard } : {}
22839
+ }))
22840
+ });
22841
+ }
22842
+ }
22843
+ const boots = [...serviceTargets].map((target) => ({ target }));
22844
+ const resolvedPorts = new Set(listeners.map((l) => l.listenerPort));
22845
+ for (const portStr of Object.keys(lbPortOverrides)) {
22846
+ const port = Number(portStr);
22847
+ 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.`);
22848
+ }
22849
+ return {
22850
+ boots,
22851
+ ...listeners.length > 0 ? { frontDoor: { listeners } } : {},
22852
+ warnings
22853
+ };
22854
+ },
22855
+ lbPortOverrides
22856
+ };
22857
+ }
22858
+ /**
22859
+ * `cdkl start-alb <Stack/Alb>` — Issue #86 v1. Names an
22860
+ * `AWS::ElasticLoadBalancingV2::LoadBalancer`, discovers the ECS service(s)
22861
+ * behind its HTTP `forward` listeners, boots their replicas, and stands up a
22862
+ * local front-door on each listener port that round-robins across the replicas.
22863
+ * The symmetric ALB counterpart of `start-api`.
22864
+ */
22865
+ function createLocalStartAlbCommand(opts = {}) {
22866
+ setEmbedConfig(opts.embedConfig);
22867
+ const cmd = new Command("start-alb").description("Run an Application Load Balancer locally: name the ALB, and cdk-local boots the ECS service(s) behind its listeners and stands up a local front-door on each listener port that round-robins across the running replicas and routes its listener 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 and HTTPS listeners — by default a cloud-HTTPS listener is served over plain HTTP locally (with X-Forwarded-Proto: https preserved). Pass --tls (or --tls-cert / --tls-key) to terminate TLS locally with a self-signed or user-supplied cert. All six ALB rule-condition fields are honored (path-pattern / host-header / http-header / http-request-method / query-string / source-ip); forward (single and weighted), redirect, and fixed-response actions; and ECS or Lambda targets (a Lambda target group is invoked locally via the Lambda RIE). authenticate-cognito / authenticate-oidc actions enforce a local Bearer-JWT check (or AWSELBAuthSessionCookie pass-through) against the same JWKS / OIDC discovery URL the deployed ALB would; use --bearer-token <jwt> to inject a default token or --no-verify-auth to disable the guard. 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)").action(withErrorHandling(async (targets, options) => {
22868
+ await runEcsServiceEmulator(targets, options, albStrategy(options), opts.extraStateProviders);
22869
+ }));
22870
+ addAlbSpecificOptions(cmd);
22871
+ return addCommonEcsServiceOptions(cmd);
22872
+ }
22873
+ /**
22874
+ * Register the option block that `start-alb` adds on top of
22875
+ * {@link addCommonEcsServiceOptions} — the flags that only make sense for an
22876
+ * ALB-fronted local emulator (front-door port mapping, TLS termination,
22877
+ * authenticate-* gating). Shared between `cdkl start-alb` and any host CLI
22878
+ * (e.g. cdkd's `local start-alb`) that wraps {@link runEcsServiceEmulator}
22879
+ * with the ALB strategy, so adding or renaming an ALB-only flag here
22880
+ * propagates to every embedder without duplicate `.addOption(...)` blocks.
22881
+ *
22882
+ * Calling order only affects `--help` presentation (Commander parses
22883
+ * insertion-order-independent). The host-CLI convention is host-specific
22884
+ * options first, then this helper, then {@link addCommonEcsServiceOptions}
22885
+ * — host flags / ALB flags / common flags grouped in three `--help`
22886
+ * clusters. Chainable: returns `cmd`.
22887
+ */
22888
+ function addAlbSpecificOptions(cmd) {
22889
+ 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 injected as Authorization: Bearer <jwt> when the inbound request has none. Verified against the same JWKS / OIDC discovery URL the deployed ALB would (signature + iss + aud + exp). Local-dev convenience; cookie pass-through (AWSELBAuthSessionCookie-*) also works."));
22890
+ }
22891
+
22892
+ //#endregion
22893
+ export { VtlEvaluationError as $, substituteImagePlaceholders as $n, pickFreePort as $t, availableApiIdentifiers as A, resolveSsmParameters as An, parseSseForJsonRpc as At, computeRequestIdentityHash as B, discoverRoutes as Bn, renderCodeDockerfile as Bt, createWatchPredicates as C, isCfnFlagPresent as Cn, A2A_CONTAINER_PORT as Ct, createFileWatcher as D, resolveCfnStackName as Dn, MCP_PATH as Dt, createAuthorizerCache as E, resolveCfnRegion as En, MCP_CONTAINER_PORT as Et, startApiServer as F, countTargets as Fn, waitForAgentCorePing as Ft, translateLambdaResponse as G, AGENTCORE_HTTP_PROTOCOL as Gn, getDockerImageBySourceHash as Gt, invokeRequestAuthorizer as H, resolveLambdaArnIntrinsic as Hn, writeProfileCredentialsFile as Ht, resolveSelectionExpression as I, listTargets as In, downloadAndExtractS3Bundle as It, buildRestV1Event as J, AgentCoreResolutionError as Jn, architectureToPlatform as Jt, applyAuthorizerOverlay as K, AGENTCORE_MCP_PROTOCOL as Kn, invokeRie as Kt, resolveServiceIntegrationParameters as L, discoverWebSocketApis as Ln, SUPPORTED_CODE_RUNTIMES as Lt, filterRoutesByApiIdentifiers as M, resolveWatchConfig as Mn, signAgentCoreInvocation as Mt, groupRoutesByServer as N, Synthesizer as Nn, AGENTCORE_SESSION_ID_HEADER as Nt, attachStageContext as O, CfnLocalStateProvider as On, MCP_PROTOCOL_VERSION as Ot, readMtlsMaterialsFromDisk as P, resolveSingleTarget as Pn, invokeAgentCore as Pt, tryParseStatus as Q, derivePseudoParametersFromRegion as Qn, ensureDockerAvailable as Qt, defaultCredentialsLoader as R, discoverWebSocketApisOrThrow as Rn, buildAgentCoreCodeImage as Rt, createLocalStartApiCommand as S, createLocalStateProvider as Sn, invokeAgentCoreWs as St, resolveProfileCredentials as T, resolveCfnFallbackRegion as Tn, a2aInvokeOnce as Tt, invokeTokenAuthorizer as U, AGENTCORE_A2A_PROTOCOL as Un, singleFlight as Ut, evaluateCachedLambdaPolicy as V, pickRefLogicalId as Vn, toCmdArgv as Vt, matchRoute as W, AGENTCORE_AGUI_PROTOCOL as Wn, AssetManifestLoader as Wt, pickResponseTemplate as X, resolveAgentCoreTarget as Xn, parseEcrUri as Xt, evaluateResponseParameters as Y, pickAgentCoreCandidateStack as Yn, buildContainerImage as Yt, selectIntegrationResponse as Z, resolveLambdaTarget as Zn, pullEcrImage as Zt, getContainerNetworkIp as _, substituteEnvVarsFromState as _n, applyCorsResponseHeaders as _t, resolveAlbTarget as a, resolveRuntimeFileExtension as an, LocalStartServiceError as ar, handleConnectionsRequest as at, parseHostPortOverrides as b, materializeLayerFromArn as bn, isFunctionUrlOacFronted as bt, MAX_TASKS_SUBNET_RANGE_CAP as c, TASK_ROLE_ACCOUNT_PLACEHOLDER as cn, appOptions as cr, buildDisconnectEvent as ct, parseMaxTasks as d, detectEcsImageResolutionNeeds as dn, deprecatedRegionOption as dr, buildJwksUrlFromIssuer as dt, pullImage as en, tryResolveImageFnJoin as er, HOST_GATEWAY_MIN_VERSION as et, parseRestartPolicy as f, parseEcsTarget as fn, parseContextOptions as fr, createJwksCache as ft, CloudMapRegistry as g, substituteAgainstStateAsync as gn, attachAuthorizers as gt, buildCloudMapIndex as h, substituteAgainstState as hn, verifyJwtViaDiscovery as ht, parseLbPortOverrides as i, resolveRuntimeCodeMountPath as in, LocalInvokeBuildError as ir, buildMgmtEndpointEnvUrl as it, filterRoutesByApiIdentifier as j, resolveApp as jn, AGENTCORE_SIGV4_SERVICE as jt, buildStageMap as k, collectSsmParameterRefs as kn, mcpInvokeOnce as kt, addCommonEcsServiceOptions as l, applyCrossStackResolverToTask as ln, commonOptions as lr, buildMessageEvent as lt, runEcsServiceEmulator as m, applyDeployedEnvFallback as mn, verifyJwtAuthorizer as mt, albStrategy as n, runDetached as nn, readCdkPathOrUndefined as nr, bufferToBody as nt, isApplicationLoadBalancer as o, resolveRuntimeImage as on, withErrorHandling as or, parseConnectionsPath as ot, resolveSharedSidecarCredentials as p, resolveEcsTaskTarget as pn, warnIfDeprecatedRegion as pr, verifyCognitoJwt as pt, buildHttpApiV2Event as q, AGENTCORE_RUNTIME_TYPE as qn, waitForRieReady as qt, createLocalStartAlbCommand as r, streamLogs as rn, CdkLocalError as rr, ConnectionRegistry as rt, resolveAlbFrontDoor as s, EcsTaskResolutionError as sn, applyRoleArnIfSet as sr, buildConnectEvent as st, addAlbSpecificOptions as t, removeContainer as tn, matchStacks as tr, probeHostGatewaySupport as tt, buildEcsImageResolutionContext as u, derivePartitionAndUrlSuffix as un, contextOptions as ur, buildCognitoJwksUrl as ut, cleanupEcsRun as v, substituteEnvVarsFromStateAsync as vn, buildCorsConfigByApiId as vt, resolveApiTargetSubset as w, rejectExplicitCfnStackWithMultipleStacks as wn, A2A_PATH as wt, runEcsTask as x, LocalStateSourceError as xn, matchPreflight as xt, createEcsRunState as y, resolveEnvVars as yn, buildCorsConfigFromCloudFrontChain as yt, buildMethodArn as z, parseSelectionExpressionPath as zn, computeCodeImageTag as zt };
22894
+ //# sourceMappingURL=local-start-alb-CIE0TMpn.js.map