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