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.
- package/README.md +2 -2
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +1 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +2 -2
- package/dist/{local-list-Dxwno_0V.js → local-list-BNkoH5tV.js} +3 -187
- package/dist/local-list-BNkoH5tV.js.map +1 -0
- package/dist/{elb-front-door-resolver-y5FicgLi.js → local-start-alb-CIE0TMpn.js} +242 -19
- package/dist/local-start-alb-CIE0TMpn.js.map +1 -0
- package/dist/{ecs-service-emulator-BgO3gb5D.d.ts → local-start-alb-CTu5lvrU.d.ts} +17 -2
- package/dist/local-start-alb-CTu5lvrU.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/ecs-service-emulator-BgO3gb5D.d.ts.map +0 -1
- package/dist/elb-front-door-resolver-y5FicgLi.js.map +0 -1
- package/dist/local-list-Dxwno_0V.js.map +0 -1
|
@@ -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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
22671
|
-
|
|
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
|