cdk-local 0.50.0 → 0.52.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 +3 -3
- package/dist/cli.js +3 -3
- package/dist/{cloud-map-resolver-BvhnCkSe.js → cloud-map-resolver-D0OCnW6R.js} +129 -6
- package/dist/cloud-map-resolver-D0OCnW6R.js.map +1 -0
- package/dist/{error-handler-Chucd8C_.d.ts → error-handler-CGh-_jQT.d.ts} +18 -2
- package/dist/error-handler-CGh-_jQT.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- 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 +1 -1
- package/dist/{local-list-C6xuYlRB.js → local-list-v8D51s5a.js} +411 -55
- package/dist/local-list-v8D51s5a.js.map +1 -0
- package/package.json +1 -1
- package/dist/cloud-map-resolver-BvhnCkSe.js.map +0 -1
- package/dist/error-handler-Chucd8C_.d.ts.map +0 -1
- package/dist/local-list-C6xuYlRB.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as runDockerStreaming, c as getEmbedConfig, r as getDockerCmd, s as getLogger, u as setEmbedConfig } from "./docker-cmd-voNPrcRh.js";
|
|
2
|
-
import { At as buildDockerImage, Bn as readCdkPathOrUndefined, Bt as streamLogs, Ct as getDockerImageBySourceHash, Dt as buildContainerImage, Et as architectureToPlatform, Fn as derivePseudoParametersFromRegion, Ft as execEnvForSecrets, Gn as withErrorHandling, Gt as TASK_ROLE_ACCOUNT_PLACEHOLDER, Hn as CdkLocalError, Ht as resolveRuntimeFileExtension, It as pickFreePort, Jn as commonOptions, Jt as derivePartitionAndUrlSuffix, Kn as applyRoleArnIfSet, Kt as applyCrossStackResolverToTask, Lt as pullImage, Mn as pickAgentCoreCandidateStack, Mt as SENSITIVE_ENV_KEYS, Nn as resolveAgentCoreTarget, Nt as appendEnvFlags, Ot as parseEcrUri, Pn as resolveLambdaTarget, Pt as ensureDockerAvailable, Q as verifyJwtViaDiscovery, Qn as warnIfDeprecatedRegion, Qt as applyDeployedEnvFallback, Rn as matchStacks, Rt as removeContainer, St as AssetManifestLoader, Tt as waitForRieReady, Un as LocalInvokeBuildError, Ut as resolveRuntimeImage, Vn as resolveCdkPathToLogicalIds, Vt as resolveRuntimeCodeMountPath, Wn as LocalStartServiceError, Wt as EcsTaskResolutionError, Xn as deprecatedRegionOption, Xt as parseEcsTarget, Y as createJwksCache, Yn as contextOptions, Yt as detectEcsImageResolutionNeeds, Zn as parseContextOptions, Zt as resolveEcsTaskTarget, _n as Synthesizer, at as invokeAgentCoreWs, bn as countTargets, bt as writeProfileCredentialsFile, c as resolveProfileCredentials$1, cn as rejectExplicitCfnStackWithMultipleStacks, en as substituteAgainstStateAsync, ft as invokeAgentCore, gt as buildAgentCoreCodeImage, hn as resolveApp, i as getPublishedHostPort, in as materializeLayerFromArn, jt as DockerRunnerError, kn as AGENTCORE_MCP_PROTOCOL, kt as pullEcrImage, ln as resolveCfnFallbackRegion, lt as mcpInvokeOnce, mt as downloadAndExtractS3Bundle, n as CloudMapRegistry, nn as substituteEnvVarsFromStateAsync, on as createLocalStateProvider, ot as MCP_CONTAINER_PORT, pt as waitForAgentCorePing, qn as appOptions, qt as checkVolumeHostPath, r as getContainerNetworkIp, rn as resolveEnvVars, st as MCP_PATH, t as buildCloudMapIndex, vn as resolveMultiTarget, wt as invokeRie, xn as listTargets, xt as singleFlight, yn as resolveSingleTarget, zn as buildCdkPathIndex, zt as runDetached } from "./cloud-map-resolver-
|
|
2
|
+
import { At as buildDockerImage, Bn as readCdkPathOrUndefined, Bt as streamLogs, Ct as getDockerImageBySourceHash, Dt as buildContainerImage, Et as architectureToPlatform, Fn as derivePseudoParametersFromRegion, Ft as execEnvForSecrets, Gn as withErrorHandling, Gt as TASK_ROLE_ACCOUNT_PLACEHOLDER, Hn as CdkLocalError, Ht as resolveRuntimeFileExtension, It as pickFreePort, Jn as commonOptions, Jt as derivePartitionAndUrlSuffix, Kn as applyRoleArnIfSet, Kt as applyCrossStackResolverToTask, Lt as pullImage, Mn as pickAgentCoreCandidateStack, Mt as SENSITIVE_ENV_KEYS, Nn as resolveAgentCoreTarget, Nt as appendEnvFlags, Ot as parseEcrUri, Pn as resolveLambdaTarget, Pt as ensureDockerAvailable, Q as verifyJwtViaDiscovery, Qn as warnIfDeprecatedRegion, Qt as applyDeployedEnvFallback, Rn as matchStacks, Rt as removeContainer, St as AssetManifestLoader, Tt as waitForRieReady, Un as LocalInvokeBuildError, Ut as resolveRuntimeImage, Vn as resolveCdkPathToLogicalIds, Vt as resolveRuntimeCodeMountPath, Wn as LocalStartServiceError, Wt as EcsTaskResolutionError, Xn as deprecatedRegionOption, Xt as parseEcsTarget, Y as createJwksCache, Yn as contextOptions, Yt as detectEcsImageResolutionNeeds, Zn as parseContextOptions, Zt as resolveEcsTaskTarget, _n as Synthesizer, at as invokeAgentCoreWs, bn as countTargets, bt as writeProfileCredentialsFile, c as resolveProfileCredentials$1, cn as rejectExplicitCfnStackWithMultipleStacks, en as substituteAgainstStateAsync, ft as invokeAgentCore, gt as buildAgentCoreCodeImage, hn as resolveApp, i as getPublishedHostPort, in as materializeLayerFromArn, jt as DockerRunnerError, kn as AGENTCORE_MCP_PROTOCOL, kt as pullEcrImage, ln as resolveCfnFallbackRegion, lt as mcpInvokeOnce, mt as downloadAndExtractS3Bundle, n as CloudMapRegistry, nn as substituteEnvVarsFromStateAsync, on as createLocalStateProvider, ot as MCP_CONTAINER_PORT, pt as waitForAgentCorePing, qn as appOptions, qt as checkVolumeHostPath, r as getContainerNetworkIp, rn as resolveEnvVars, st as MCP_PATH, t as buildCloudMapIndex, vn as resolveMultiTarget, wt as invokeRie, xn as listTargets, xt as singleFlight, yn as resolveSingleTarget, zn as buildCdkPathIndex, zt as runDetached } from "./cloud-map-resolver-D0OCnW6R.js";
|
|
3
3
|
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
@@ -764,7 +764,9 @@ async function resolveInboundAuthorization(resolved, options) {
|
|
|
764
764
|
if (!(await verifyJwtViaDiscovery({
|
|
765
765
|
discoveryUrl: authorizer.discoveryUrl,
|
|
766
766
|
...authorizer.allowedAudience && { allowedAudience: authorizer.allowedAudience },
|
|
767
|
-
...authorizer.allowedClients && { allowedClients: authorizer.allowedClients }
|
|
767
|
+
...authorizer.allowedClients && { allowedClients: authorizer.allowedClients },
|
|
768
|
+
...authorizer.allowedScopes && { allowedScopes: authorizer.allowedScopes },
|
|
769
|
+
...authorizer.customClaims && { customClaims: authorizer.customClaims }
|
|
768
770
|
}, header, createJwksCache(), { warned: /* @__PURE__ */ new Set() })).allow) throw new CdkLocalError(`Inbound JWT rejected by the runtime's customJwtAuthorizer (signature / issuer / expiry / audience check failed against ${authorizer.discoveryUrl}).`, "LOCAL_INVOKE_AGENTCORE_AUTH_DENIED");
|
|
769
771
|
logger.info(`Inbound JWT verified against ${authorizer.discoveryUrl}.`);
|
|
770
772
|
return header;
|
|
@@ -3670,7 +3672,10 @@ function handleProxyRequest(req, res, opts) {
|
|
|
3670
3672
|
if (opts.route) {
|
|
3671
3673
|
const action = opts.route({
|
|
3672
3674
|
path: url,
|
|
3673
|
-
...hostHeader(req)
|
|
3675
|
+
...hostHeader(req),
|
|
3676
|
+
headers: req.headers,
|
|
3677
|
+
...req.method !== void 0 && { method: req.method },
|
|
3678
|
+
...req.socket.remoteAddress !== void 0 && { sourceIp: req.socket.remoteAddress }
|
|
3674
3679
|
});
|
|
3675
3680
|
if (!action) return reply404(req, res, opts);
|
|
3676
3681
|
if (action.kind === "redirect" || action.kind === "fixed-response") {
|
|
@@ -3949,25 +3954,24 @@ function writeError(res, statusCode, message) {
|
|
|
3949
3954
|
* evaluated in ascending priority; the input order is irrelevant.
|
|
3950
3955
|
*
|
|
3951
3956
|
* Accepts either a request facts object or a bare path string (the path-only
|
|
3952
|
-
* form keeps the original signature working for callers that have no Host
|
|
3957
|
+
* form keeps the original signature working for callers that have no Host /
|
|
3958
|
+
* headers / method / source IP).
|
|
3953
3959
|
*/
|
|
3954
3960
|
function matchAlbPathRule(req, rules) {
|
|
3955
|
-
const
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
const requestPath = pathOf(path);
|
|
3960
|
-
const requestHost = host !== void 0 ? hostOf(host) : void 0;
|
|
3961
|
+
const facts = typeof req === "string" ? { path: req } : req;
|
|
3962
|
+
const requestPath = pathOf(facts.path);
|
|
3963
|
+
const requestQuery = queryOf(facts.path);
|
|
3964
|
+
const requestHost = facts.host !== void 0 ? hostOf(facts.host) : void 0;
|
|
3961
3965
|
const ordered = [...rules].sort((a, b) => a.priority - b.priority);
|
|
3962
|
-
for (const rule of ordered) if (ruleMatches(rule, requestPath, requestHost)) return rule.target;
|
|
3966
|
+
for (const rule of ordered) if (ruleMatches(rule, requestPath, requestQuery, requestHost, facts)) return rule.target;
|
|
3963
3967
|
}
|
|
3964
3968
|
/**
|
|
3965
|
-
* Whether a single rule's conditions all match.
|
|
3966
|
-
*
|
|
3967
|
-
*
|
|
3968
|
-
*
|
|
3969
|
+
* Whether a single rule's conditions all match. Each present condition field
|
|
3970
|
+
* is OR-matched within (multiple values) and AND'd across fields. An empty /
|
|
3971
|
+
* absent condition list means "no constraint on that field" (the condition was
|
|
3972
|
+
* absent on the rule).
|
|
3969
3973
|
*/
|
|
3970
|
-
function ruleMatches(rule, requestPath, requestHost) {
|
|
3974
|
+
function ruleMatches(rule, requestPath, requestQuery, requestHost, facts) {
|
|
3971
3975
|
if (rule.pathPatterns.length > 0) {
|
|
3972
3976
|
if (!rule.pathPatterns.some((pattern) => globToRegExp(pattern, false).test(requestPath))) return false;
|
|
3973
3977
|
}
|
|
@@ -3976,8 +3980,71 @@ function ruleMatches(rule, requestPath, requestHost) {
|
|
|
3976
3980
|
if (requestHost === void 0) return false;
|
|
3977
3981
|
if (!hostPatterns.some((pattern) => globToRegExp(pattern, true).test(requestHost))) return false;
|
|
3978
3982
|
}
|
|
3983
|
+
const headerConditions = rule.httpHeaderConditions ?? [];
|
|
3984
|
+
if (headerConditions.length > 0) {
|
|
3985
|
+
if (!headerConditions.every((cond) => httpHeaderConditionMatches(cond, facts.headers))) return false;
|
|
3986
|
+
}
|
|
3987
|
+
const methods = rule.httpRequestMethods ?? [];
|
|
3988
|
+
if (methods.length > 0) {
|
|
3989
|
+
if (facts.method === void 0) return false;
|
|
3990
|
+
if (!methods.includes(facts.method)) return false;
|
|
3991
|
+
}
|
|
3992
|
+
const queryConditions = rule.queryStringConditions ?? [];
|
|
3993
|
+
if (queryConditions.length > 0) {
|
|
3994
|
+
const params = parseQueryParams(requestQuery);
|
|
3995
|
+
if (!queryConditions.some((cond) => queryStringConditionMatches(cond, params))) return false;
|
|
3996
|
+
}
|
|
3997
|
+
const cidrs = rule.sourceIpCidrs ?? [];
|
|
3998
|
+
if (cidrs.length > 0) {
|
|
3999
|
+
if (facts.sourceIp === void 0) return false;
|
|
4000
|
+
const ip = unmapV4MappedV6(facts.sourceIp);
|
|
4001
|
+
if (!cidrs.some((cidr) => albCidrMatches(cidr, ip))) return false;
|
|
4002
|
+
}
|
|
3979
4003
|
return true;
|
|
3980
4004
|
}
|
|
4005
|
+
/**
|
|
4006
|
+
* Whether an `http-header` condition matches the request's headers. The
|
|
4007
|
+
* header name is looked up case-insensitively; each listed value glob is
|
|
4008
|
+
* matched case-insensitively against every value of that header (multi-valued
|
|
4009
|
+
* headers count each value separately).
|
|
4010
|
+
*/
|
|
4011
|
+
function httpHeaderConditionMatches(cond, headers) {
|
|
4012
|
+
if (!headers) return false;
|
|
4013
|
+
const rawValue = headerLookup(headers, cond.name.toLowerCase());
|
|
4014
|
+
if (rawValue === void 0) return false;
|
|
4015
|
+
const headerValues = Array.isArray(rawValue) ? rawValue : [rawValue];
|
|
4016
|
+
return cond.values.some((glob) => {
|
|
4017
|
+
const re = globToRegExp(glob, true);
|
|
4018
|
+
return headerValues.some((v) => re.test(v.toLowerCase()));
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
4021
|
+
/**
|
|
4022
|
+
* Whether a single `query-string` condition `{ Key?, Value }` matches the
|
|
4023
|
+
* request's parsed query parameters. Both Key and Value are case-insensitive
|
|
4024
|
+
* `*` / `?` globs; when `Key` is absent, ANY parameter whose value matches the
|
|
4025
|
+
* Value glob satisfies the condition.
|
|
4026
|
+
*/
|
|
4027
|
+
function queryStringConditionMatches(cond, params) {
|
|
4028
|
+
const valueRe = globToRegExp(cond.value, true);
|
|
4029
|
+
const keyRe = cond.key !== void 0 ? globToRegExp(cond.key, true) : void 0;
|
|
4030
|
+
return params.some((p) => {
|
|
4031
|
+
if (keyRe !== void 0 && !keyRe.test(p.key.toLowerCase())) return false;
|
|
4032
|
+
return valueRe.test(p.value.toLowerCase());
|
|
4033
|
+
});
|
|
4034
|
+
}
|
|
4035
|
+
/**
|
|
4036
|
+
* Whether an IPv4 or IPv6 address falls inside an IPv4 or IPv6 CIDR. Returns
|
|
4037
|
+
* `false` for mismatched families (an IPv4 address tested against an IPv6
|
|
4038
|
+
* CIDR, or vice versa) and for unparseable input.
|
|
4039
|
+
*/
|
|
4040
|
+
function albCidrMatches(cidr, ip) {
|
|
4041
|
+
const parsed = parseCidr(cidr);
|
|
4042
|
+
if (!parsed) return false;
|
|
4043
|
+
const addr = parseIpAddress(ip);
|
|
4044
|
+
if (!addr) return false;
|
|
4045
|
+
if (parsed.family !== addr.family) return false;
|
|
4046
|
+
return matchBitPrefix(parsed.bytes, addr.bytes, parsed.prefixLength);
|
|
4047
|
+
}
|
|
3981
4048
|
/** Strip the query string / fragment so only the URL path is matched. */
|
|
3982
4049
|
function pathOf(url) {
|
|
3983
4050
|
let end = url.length;
|
|
@@ -3988,6 +4055,45 @@ function pathOf(url) {
|
|
|
3988
4055
|
return url.slice(0, end);
|
|
3989
4056
|
}
|
|
3990
4057
|
/**
|
|
4058
|
+
* Pull the raw query string (`a=1&b=2`) out of a URL. Returns the empty string
|
|
4059
|
+
* when the URL has no `?`. The fragment is dropped.
|
|
4060
|
+
*/
|
|
4061
|
+
function queryOf(url) {
|
|
4062
|
+
const q = url.indexOf("?");
|
|
4063
|
+
if (q === -1) return "";
|
|
4064
|
+
const rest = url.slice(q + 1);
|
|
4065
|
+
const h = rest.indexOf("#");
|
|
4066
|
+
return h === -1 ? rest : rest.slice(0, h);
|
|
4067
|
+
}
|
|
4068
|
+
/**
|
|
4069
|
+
* Decode a raw query string into a flat list of `{ key, value }` entries
|
|
4070
|
+
* (preserving repeats), URI-decoding both sides and treating `+` as a space
|
|
4071
|
+
* (the `application/x-www-form-urlencoded` convention browsers use). Pairs
|
|
4072
|
+
* with no `=` are treated as `{ key, value: '' }`.
|
|
4073
|
+
*/
|
|
4074
|
+
function parseQueryParams(query) {
|
|
4075
|
+
if (!query) return [];
|
|
4076
|
+
const out = [];
|
|
4077
|
+
for (const pair of query.split("&")) {
|
|
4078
|
+
if (!pair) continue;
|
|
4079
|
+
const eq = pair.indexOf("=");
|
|
4080
|
+
const rawKey = eq === -1 ? pair : pair.slice(0, eq);
|
|
4081
|
+
const rawValue = eq === -1 ? "" : pair.slice(eq + 1);
|
|
4082
|
+
out.push({
|
|
4083
|
+
key: decodeQueryPart(rawKey),
|
|
4084
|
+
value: decodeQueryPart(rawValue)
|
|
4085
|
+
});
|
|
4086
|
+
}
|
|
4087
|
+
return out;
|
|
4088
|
+
}
|
|
4089
|
+
function decodeQueryPart(s) {
|
|
4090
|
+
try {
|
|
4091
|
+
return decodeURIComponent(s.replace(/\+/g, " "));
|
|
4092
|
+
} catch {
|
|
4093
|
+
return s;
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
/**
|
|
3991
4097
|
* Normalize a `Host` header for matching: drop any `:port` suffix and lower-case
|
|
3992
4098
|
* it (DNS hostnames are case-insensitive). IPv6 literals (`[::1]:8080`) keep the
|
|
3993
4099
|
* bracketed address and only the trailing port is removed.
|
|
@@ -4002,13 +4108,18 @@ function hostOf(host) {
|
|
|
4002
4108
|
const colon = trimmed.indexOf(":");
|
|
4003
4109
|
return (colon === -1 ? trimmed : trimmed.slice(0, colon)).toLowerCase();
|
|
4004
4110
|
}
|
|
4111
|
+
/** Case-insensitive header lookup against a Node-style headers dict. */
|
|
4112
|
+
function headerLookup(headers, lowerCaseName) {
|
|
4113
|
+
const direct = headers[lowerCaseName];
|
|
4114
|
+
if (direct !== void 0) return direct;
|
|
4115
|
+
for (const key of Object.keys(headers)) if (key.toLowerCase() === lowerCaseName) return headers[key];
|
|
4116
|
+
}
|
|
4005
4117
|
const REGEXP_META = /[.+^${}()|[\]\\]/;
|
|
4006
4118
|
/**
|
|
4007
4119
|
* Translate an ALB `*` / `?` glob into an anchored RegExp: `*` -> `.*`,
|
|
4008
|
-
* `?` -> `.`, every other character is escaped and matched literally.
|
|
4009
|
-
*
|
|
4010
|
-
* lower-cased
|
|
4011
|
-
* case-sensitive.
|
|
4120
|
+
* `?` -> `.`, every other character is escaped and matched literally. The
|
|
4121
|
+
* `caseInsensitive` form lower-cases the pattern (callers pair it with a
|
|
4122
|
+
* lower-cased input); path patterns are case-sensitive.
|
|
4012
4123
|
*/
|
|
4013
4124
|
function globToRegExp(pattern, caseInsensitive) {
|
|
4014
4125
|
const source = caseInsensitive ? pattern.toLowerCase() : pattern;
|
|
@@ -4019,6 +4130,131 @@ function globToRegExp(pattern, caseInsensitive) {
|
|
|
4019
4130
|
else body += ch;
|
|
4020
4131
|
return new RegExp(`^${body}$`);
|
|
4021
4132
|
}
|
|
4133
|
+
/** Parse a CIDR (`ip/prefix`) into its address bytes + prefix length. */
|
|
4134
|
+
function parseCidr(cidr) {
|
|
4135
|
+
const slash = cidr.indexOf("/");
|
|
4136
|
+
if (slash === -1) return void 0;
|
|
4137
|
+
const addrPart = cidr.slice(0, slash);
|
|
4138
|
+
const prefixPart = cidr.slice(slash + 1);
|
|
4139
|
+
if (!/^\d+$/.test(prefixPart)) return void 0;
|
|
4140
|
+
const prefixLength = parseInt(prefixPart, 10);
|
|
4141
|
+
const addr = parseIpAddress(addrPart);
|
|
4142
|
+
if (!addr) return void 0;
|
|
4143
|
+
const maxPrefix = addr.family === "v4" ? 32 : 128;
|
|
4144
|
+
if (prefixLength < 0 || prefixLength > maxPrefix) return void 0;
|
|
4145
|
+
return {
|
|
4146
|
+
family: addr.family,
|
|
4147
|
+
bytes: addr.bytes,
|
|
4148
|
+
prefixLength
|
|
4149
|
+
};
|
|
4150
|
+
}
|
|
4151
|
+
/** Parse an IPv4 (`1.2.3.4`) or IPv6 address into its byte representation. */
|
|
4152
|
+
function parseIpAddress(ip) {
|
|
4153
|
+
if (ip.includes(".") && !ip.includes(":")) return parseIpv4(ip);
|
|
4154
|
+
if (ip.includes(":")) return parseIpv6(ip);
|
|
4155
|
+
}
|
|
4156
|
+
function parseIpv4(ip) {
|
|
4157
|
+
const parts = ip.split(".");
|
|
4158
|
+
if (parts.length !== 4) return void 0;
|
|
4159
|
+
const bytes = new Uint8Array(4);
|
|
4160
|
+
for (let i = 0; i < 4; i++) {
|
|
4161
|
+
const part = parts[i];
|
|
4162
|
+
if (!/^\d+$/.test(part)) return void 0;
|
|
4163
|
+
const n = parseInt(part, 10);
|
|
4164
|
+
if (n < 0 || n > 255) return void 0;
|
|
4165
|
+
bytes[i] = n;
|
|
4166
|
+
}
|
|
4167
|
+
return {
|
|
4168
|
+
family: "v4",
|
|
4169
|
+
bytes
|
|
4170
|
+
};
|
|
4171
|
+
}
|
|
4172
|
+
/**
|
|
4173
|
+
* Parse a textual IPv6 address (incl. `::` shorthand and IPv4-suffix form like
|
|
4174
|
+
* `::ffff:1.2.3.4`) into its 16-byte representation.
|
|
4175
|
+
*/
|
|
4176
|
+
function parseIpv6(ip) {
|
|
4177
|
+
let s = ip;
|
|
4178
|
+
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
4179
|
+
let v4Suffix;
|
|
4180
|
+
const lastColon = s.lastIndexOf(":");
|
|
4181
|
+
if (lastColon !== -1 && s.slice(lastColon + 1).includes(".")) {
|
|
4182
|
+
const parsed = parseIpv4(s.slice(lastColon + 1));
|
|
4183
|
+
if (!parsed) return void 0;
|
|
4184
|
+
v4Suffix = parsed.bytes;
|
|
4185
|
+
s = s.slice(0, lastColon) + ":0:0";
|
|
4186
|
+
}
|
|
4187
|
+
const doubleColon = s.indexOf("::");
|
|
4188
|
+
let head;
|
|
4189
|
+
let tail;
|
|
4190
|
+
if (doubleColon === -1) {
|
|
4191
|
+
head = s.split(":");
|
|
4192
|
+
tail = [];
|
|
4193
|
+
} else {
|
|
4194
|
+
head = s.slice(0, doubleColon) === "" ? [] : s.slice(0, doubleColon).split(":");
|
|
4195
|
+
tail = s.slice(doubleColon + 2) === "" ? [] : s.slice(doubleColon + 2).split(":");
|
|
4196
|
+
}
|
|
4197
|
+
if (head.length + tail.length > 8) return void 0;
|
|
4198
|
+
if (doubleColon === -1 && head.length !== 8) return void 0;
|
|
4199
|
+
const groups = new Array(8).fill(0);
|
|
4200
|
+
for (let i = 0; i < head.length; i++) {
|
|
4201
|
+
const g = parseHexGroup(head[i]);
|
|
4202
|
+
if (g === void 0) return void 0;
|
|
4203
|
+
groups[i] = g;
|
|
4204
|
+
}
|
|
4205
|
+
for (let i = 0; i < tail.length; i++) {
|
|
4206
|
+
const g = parseHexGroup(tail[tail.length - 1 - i]);
|
|
4207
|
+
if (g === void 0) return void 0;
|
|
4208
|
+
groups[7 - i] = g;
|
|
4209
|
+
}
|
|
4210
|
+
const bytes = new Uint8Array(16);
|
|
4211
|
+
for (let i = 0; i < 8; i++) {
|
|
4212
|
+
bytes[i * 2] = groups[i] >> 8 & 255;
|
|
4213
|
+
bytes[i * 2 + 1] = groups[i] & 255;
|
|
4214
|
+
}
|
|
4215
|
+
if (v4Suffix) {
|
|
4216
|
+
bytes[12] = v4Suffix[0];
|
|
4217
|
+
bytes[13] = v4Suffix[1];
|
|
4218
|
+
bytes[14] = v4Suffix[2];
|
|
4219
|
+
bytes[15] = v4Suffix[3];
|
|
4220
|
+
}
|
|
4221
|
+
return {
|
|
4222
|
+
family: "v6",
|
|
4223
|
+
bytes
|
|
4224
|
+
};
|
|
4225
|
+
}
|
|
4226
|
+
function parseHexGroup(g) {
|
|
4227
|
+
if (g.length === 0 || g.length > 4) return void 0;
|
|
4228
|
+
if (!/^[0-9a-fA-F]+$/.test(g)) return void 0;
|
|
4229
|
+
return parseInt(g, 16);
|
|
4230
|
+
}
|
|
4231
|
+
/**
|
|
4232
|
+
* Unmap an IPv4-mapped IPv6 source IP (`::ffff:a.b.c.d` or `::ffff:NNNN:NNNN`)
|
|
4233
|
+
* to its bare IPv4 form. Node reports `::ffff:127.0.0.1` for an IPv4 client on
|
|
4234
|
+
* a dual-stack listener; rules that name a v4 CIDR should still match.
|
|
4235
|
+
*/
|
|
4236
|
+
function unmapV4MappedV6(ip) {
|
|
4237
|
+
const m = /^::ffff:(.+)$/i.exec(ip);
|
|
4238
|
+
if (!m) return ip;
|
|
4239
|
+
const suffix = m[1];
|
|
4240
|
+
if (suffix.includes(".")) return suffix;
|
|
4241
|
+
const parts = suffix.split(":");
|
|
4242
|
+
if (parts.length !== 2) return ip;
|
|
4243
|
+
const high = parseInt(parts[0], 16);
|
|
4244
|
+
const low = parseInt(parts[1], 16);
|
|
4245
|
+
if (!Number.isFinite(high) || !Number.isFinite(low)) return ip;
|
|
4246
|
+
return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
|
|
4247
|
+
}
|
|
4248
|
+
/** Whether the first `prefixLength` bits of `a` equal those of `b`. */
|
|
4249
|
+
function matchBitPrefix(a, b, prefixLength) {
|
|
4250
|
+
if (a.length !== b.length) return false;
|
|
4251
|
+
const fullBytes = Math.floor(prefixLength / 8);
|
|
4252
|
+
for (let i = 0; i < fullBytes; i++) if (a[i] !== b[i]) return false;
|
|
4253
|
+
const remainingBits = prefixLength % 8;
|
|
4254
|
+
if (remainingBits === 0) return true;
|
|
4255
|
+
const mask = 255 << 8 - remainingBits;
|
|
4256
|
+
return (a[fullBytes] & mask) === (b[fullBytes] & mask);
|
|
4257
|
+
}
|
|
4022
4258
|
|
|
4023
4259
|
//#endregion
|
|
4024
4260
|
//#region src/local/front-door-lambda-runner.ts
|
|
@@ -4511,6 +4747,10 @@ async function buildFrontDoor(plan, options, logger) {
|
|
|
4511
4747
|
priority: r.priority,
|
|
4512
4748
|
pathPatterns: r.pathPatterns,
|
|
4513
4749
|
hostPatterns: r.hostPatterns,
|
|
4750
|
+
httpHeaderConditions: r.httpHeaderConditions,
|
|
4751
|
+
httpRequestMethods: r.httpRequestMethods,
|
|
4752
|
+
queryStringConditions: r.queryStringConditions,
|
|
4753
|
+
sourceIpCidrs: r.sourceIpCidrs,
|
|
4514
4754
|
target: toRouteAction(r.action)
|
|
4515
4755
|
}));
|
|
4516
4756
|
const route = (req) => matchAlbPathRule(req, ruleRoutes) ?? defaultRoute;
|
|
@@ -4552,13 +4792,20 @@ async function buildFrontDoor(plan, options, logger) {
|
|
|
4552
4792
|
lambdaRunners: [...lambdaRegistry.values()]
|
|
4553
4793
|
};
|
|
4554
4794
|
}
|
|
4555
|
-
/** Human-readable summary of a planned rule's
|
|
4795
|
+
/** Human-readable summary of a planned rule's six ALB condition fields (for the boot banner). */
|
|
4556
4796
|
function describeConditions(rule) {
|
|
4557
4797
|
const parts = [];
|
|
4558
4798
|
if (rule.pathPatterns.length > 0) parts.push(`path ${rule.pathPatterns.join(", ")}`);
|
|
4559
4799
|
if (rule.hostPatterns.length > 0) parts.push(`host ${rule.hostPatterns.join(", ")}`);
|
|
4800
|
+
for (const h of rule.httpHeaderConditions) parts.push(`header ${h.name}: ${h.values.join(", ")}`);
|
|
4801
|
+
if (rule.httpRequestMethods.length > 0) parts.push(`method ${rule.httpRequestMethods.join(", ")}`);
|
|
4802
|
+
if (rule.queryStringConditions.length > 0) parts.push(`query ${rule.queryStringConditions.map(describeQueryStringCondition).join(", ")}`);
|
|
4803
|
+
if (rule.sourceIpCidrs.length > 0) parts.push(`source-ip ${rule.sourceIpCidrs.join(", ")}`);
|
|
4560
4804
|
return parts.join(" AND ") || "(no condition)";
|
|
4561
4805
|
}
|
|
4806
|
+
function describeQueryStringCondition(c) {
|
|
4807
|
+
return c.key !== void 0 ? `${c.key}=${c.value}` : c.value;
|
|
4808
|
+
}
|
|
4562
4809
|
/** Human-readable summary of a planned action (for the boot banner). */
|
|
4563
4810
|
function describeAction(action) {
|
|
4564
4811
|
if (action.kind === "redirect") return `redirect ${action.statusCode}`;
|
|
@@ -4787,7 +5034,11 @@ function createLocalStartServiceCommand(opts = {}) {
|
|
|
4787
5034
|
* DefaultActions:[ <action> ] }
|
|
4788
5035
|
* ElasticLoadBalancingV2::ListenerRule : { ListenerArn:{Ref:<Listener>}, Priority,
|
|
4789
5036
|
* Conditions:[{ Field:"path-pattern", PathPatternConfig:{ Values:["/api/*"] } },
|
|
4790
|
-
* { Field:"host-header", HostHeaderConfig:{ Values:["api.example.com"] } }
|
|
5037
|
+
* { Field:"host-header", HostHeaderConfig:{ Values:["api.example.com"] } },
|
|
5038
|
+
* { Field:"http-header", HttpHeaderConfig:{ HttpHeaderName:"X-API", Values:["*"] } },
|
|
5039
|
+
* { Field:"http-request-method", HttpRequestMethodConfig:{ Values:["POST"] } },
|
|
5040
|
+
* { Field:"query-string", QueryStringConfig:{ Values:[{ Key:"v", Value:"1" }] } },
|
|
5041
|
+
* { Field:"source-ip", SourceIpConfig:{ Values:["10.0.0.0/8"] } }],
|
|
4791
5042
|
* Actions:[ <action> ] }
|
|
4792
5043
|
* ElasticLoadBalancingV2::TargetGroup : { Port, Protocol, TargetType:"ip" }
|
|
4793
5044
|
* ECS::Service.LoadBalancers[] -> { ContainerName, ContainerPort, TargetGroupArn:{Ref:<TG>} }
|
|
@@ -4805,16 +5056,16 @@ function createLocalStartServiceCommand(opts = {}) {
|
|
|
4805
5056
|
* `LoadBalancers[]` references each target group (a reverse scan; there is no
|
|
4806
5057
|
* direct TG -> service pointer). Output is a per-listener routing table: a
|
|
4807
5058
|
* default action plus the ordered rules, each carrying its resolved action and
|
|
4808
|
-
* its
|
|
5059
|
+
* its full set of routing conditions.
|
|
4809
5060
|
*
|
|
4810
|
-
* Scope: HTTP listeners;
|
|
4811
|
-
*
|
|
4812
|
-
*
|
|
5061
|
+
* Scope: HTTP listeners; all six ALB rule-condition fields (`path-pattern`,
|
|
5062
|
+
* `host-header`, `http-header`, `http-request-method`, `query-string`,
|
|
5063
|
+
* `source-ip`); `forward` (single or weighted) to ECS services AND/OR Lambda
|
|
5064
|
+
* functions (`TargetType:"lambda"` target groups — #123: the TG -> backing
|
|
4813
5065
|
* `AWS::Lambda::Function` is resolved and the front-door invokes it locally per
|
|
4814
5066
|
* request); `redirect` / `fixed-response` actions. A single weighted forward may
|
|
4815
|
-
* mix ECS and Lambda targets. Skipped with a warning: HTTPS/TLS listeners
|
|
4816
|
-
*
|
|
4817
|
-
* source-ip), and `authenticate-cognito` / `authenticate-oidc` actions.
|
|
5067
|
+
* mix ECS and Lambda targets. Skipped with a warning: HTTPS/TLS listeners and
|
|
5068
|
+
* `authenticate-cognito` / `authenticate-oidc` actions.
|
|
4818
5069
|
*/
|
|
4819
5070
|
const ALB_TYPE = "AWS::ElasticLoadBalancingV2::LoadBalancer";
|
|
4820
5071
|
const LISTENER_TYPE = "AWS::ElasticLoadBalancingV2::Listener";
|
|
@@ -4850,18 +5101,20 @@ function resolveAlbFrontDoor(stack, albLogicalId) {
|
|
|
4850
5101
|
for (const { ruleLogicalId, ruleProps } of rulesByListener.get(listenerLogicalId) ?? []) {
|
|
4851
5102
|
const priority = parsePriority(ruleProps["Priority"]);
|
|
4852
5103
|
const ruleLabel = `Listener rule '${ruleLogicalId}' (priority ${priority})`;
|
|
4853
|
-
const
|
|
4854
|
-
if (
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
}
|
|
4858
|
-
if (pathPatterns.length === 0 && hostPatterns.length === 0) continue;
|
|
5104
|
+
const parsed = parseRuleConditions(ruleProps["Conditions"], ruleLabel, warnings);
|
|
5105
|
+
if (!parsed) continue;
|
|
5106
|
+
const { pathPatterns, hostPatterns, httpHeaderConditions, httpRequestMethods, queryStringConditions, sourceIpCidrs } = parsed;
|
|
5107
|
+
if (!(pathPatterns.length > 0 || hostPatterns.length > 0 || httpHeaderConditions.length > 0 || httpRequestMethods.length > 0 || queryStringConditions.length > 0 || sourceIpCidrs.length > 0)) continue;
|
|
4859
5108
|
const action = resolveAction(ruleProps["Actions"], resources, tgToService, stackName, `${ruleLabel} action`, warnings);
|
|
4860
5109
|
if (!action) continue;
|
|
4861
5110
|
rules.push({
|
|
4862
5111
|
priority,
|
|
4863
5112
|
pathPatterns,
|
|
4864
5113
|
hostPatterns,
|
|
5114
|
+
httpHeaderConditions,
|
|
5115
|
+
httpRequestMethods,
|
|
5116
|
+
queryStringConditions,
|
|
5117
|
+
sourceIpCidrs,
|
|
4865
5118
|
action
|
|
4866
5119
|
});
|
|
4867
5120
|
}
|
|
@@ -5094,36 +5347,135 @@ function indexRulesByListener(resources) {
|
|
|
5094
5347
|
return index;
|
|
5095
5348
|
}
|
|
5096
5349
|
/**
|
|
5097
|
-
* Parse a ListenerRule's `Conditions` into
|
|
5098
|
-
* `
|
|
5099
|
-
*
|
|
5100
|
-
*
|
|
5101
|
-
*
|
|
5350
|
+
* Parse a ListenerRule's `Conditions` into the six supported fields. Returns
|
|
5351
|
+
* `undefined` when an unknown field is encountered or a known field is
|
|
5352
|
+
* malformed enough that honoring the rule would silently drop a constraint
|
|
5353
|
+
* — both surface a warning and skip the whole rule (ALB ANDs conditions of
|
|
5354
|
+
* different fields, so dropping any condition would route requests it should
|
|
5355
|
+
* not).
|
|
5356
|
+
*
|
|
5357
|
+
* Multiple `http-header` conditions on different names are kept (they AND).
|
|
5358
|
+
* Multiple `path-pattern` / `host-header` / `http-request-method` /
|
|
5359
|
+
* `query-string` / `source-ip` conditions on the same field merge their
|
|
5360
|
+
* values (each field OR-matches). A `source-ip` value that does not parse as
|
|
5361
|
+
* a CIDR makes the whole rule unsupported (the rule's authoring intent was
|
|
5362
|
+
* specific; dropping the invalid range would silently widen the allow list).
|
|
5102
5363
|
*/
|
|
5103
|
-
function parseRuleConditions(conditions) {
|
|
5104
|
-
const
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5364
|
+
function parseRuleConditions(conditions, ruleLabel, warnings) {
|
|
5365
|
+
const out = {
|
|
5366
|
+
pathPatterns: [],
|
|
5367
|
+
hostPatterns: [],
|
|
5368
|
+
httpHeaderConditions: [],
|
|
5369
|
+
httpRequestMethods: [],
|
|
5370
|
+
queryStringConditions: [],
|
|
5371
|
+
sourceIpCidrs: []
|
|
5111
5372
|
};
|
|
5373
|
+
if (!Array.isArray(conditions)) return out;
|
|
5112
5374
|
for (const cond of conditions) {
|
|
5113
5375
|
if (!cond || typeof cond !== "object") continue;
|
|
5114
5376
|
const c = cond;
|
|
5115
5377
|
const field = typeof c["Field"] === "string" ? c["Field"] : "(unknown)";
|
|
5116
|
-
if (field === "path-pattern") pathPatterns.push(...conditionValues(c, "PathPatternConfig"));
|
|
5117
|
-
else if (field === "host-header") hostPatterns.push(...conditionValues(c, "HostHeaderConfig"));
|
|
5118
|
-
else
|
|
5378
|
+
if (field === "path-pattern") out.pathPatterns.push(...conditionValues(c, "PathPatternConfig"));
|
|
5379
|
+
else if (field === "host-header") out.hostPatterns.push(...conditionValues(c, "HostHeaderConfig"));
|
|
5380
|
+
else if (field === "http-header") {
|
|
5381
|
+
const parsed = parseHttpHeaderCondition(c);
|
|
5382
|
+
if (!parsed) {
|
|
5383
|
+
warnings.push(`${ruleLabel} has an http-header condition with no HttpHeaderName / Values; skipping the rule.`);
|
|
5384
|
+
return;
|
|
5385
|
+
}
|
|
5386
|
+
out.httpHeaderConditions.push(parsed);
|
|
5387
|
+
} else if (field === "http-request-method") out.httpRequestMethods.push(...conditionValues(c, "HttpRequestMethodConfig"));
|
|
5388
|
+
else if (field === "query-string") {
|
|
5389
|
+
const { parsed, hadEntries } = parseQueryStringConditionValues(c);
|
|
5390
|
+
if (hadEntries && parsed.length === 0) {
|
|
5391
|
+
warnings.push(`${ruleLabel} query-string condition has no parseable { Key?, Value } entries; skipping the rule.`);
|
|
5392
|
+
return;
|
|
5393
|
+
}
|
|
5394
|
+
out.queryStringConditions.push(...parsed);
|
|
5395
|
+
} else if (field === "source-ip") {
|
|
5396
|
+
const values = conditionValues(c, "SourceIpConfig");
|
|
5397
|
+
const invalid = values.filter((v) => !isValidCidr(v));
|
|
5398
|
+
if (invalid.length > 0) {
|
|
5399
|
+
warnings.push(`${ruleLabel} source-ip condition has unparseable CIDR(s): ${invalid.join(", ")}. The local ALB front-door requires valid IPv4 / IPv6 CIDRs; skipping the rule.`);
|
|
5400
|
+
return;
|
|
5401
|
+
}
|
|
5402
|
+
out.sourceIpCidrs.push(...values);
|
|
5403
|
+
} else {
|
|
5404
|
+
warnings.push(`${ruleLabel} uses unsupported condition field '${field}'. Skipping the rule.`);
|
|
5405
|
+
return;
|
|
5406
|
+
}
|
|
5119
5407
|
}
|
|
5408
|
+
return out;
|
|
5409
|
+
}
|
|
5410
|
+
/**
|
|
5411
|
+
* Parse an `http-header` condition into its `{ name, values }` form. Returns
|
|
5412
|
+
* `undefined` when `HttpHeaderName` is missing / non-string or `Values` is
|
|
5413
|
+
* empty (either makes the rule's authoring intent unrecoverable).
|
|
5414
|
+
*/
|
|
5415
|
+
function parseHttpHeaderCondition(cond) {
|
|
5416
|
+
const cfg = cond["HttpHeaderConfig"];
|
|
5417
|
+
if (!cfg || typeof cfg !== "object") return void 0;
|
|
5418
|
+
const c = cfg;
|
|
5419
|
+
const name = c["HttpHeaderName"];
|
|
5420
|
+
if (typeof name !== "string" || name.length === 0) return void 0;
|
|
5421
|
+
const values = (Array.isArray(c["Values"]) ? c["Values"] : []).filter((v) => typeof v === "string");
|
|
5422
|
+
if (values.length === 0) return void 0;
|
|
5120
5423
|
return {
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
unsupported
|
|
5424
|
+
name,
|
|
5425
|
+
values
|
|
5124
5426
|
};
|
|
5125
5427
|
}
|
|
5126
5428
|
/**
|
|
5429
|
+
* Parse a `query-string` condition's `Values` into `{ key?, value }` pairs.
|
|
5430
|
+
* ALB accepts either object entries (`{ Key?, Value }`) or bare strings
|
|
5431
|
+
* (legacy v1 shape: a string is treated as `{ value }`). Non-string `Key` /
|
|
5432
|
+
* `Value` fields are dropped; an entry with no `Value` is dropped. Returns
|
|
5433
|
+
* `hadEntries` so the caller can distinguish an absent / empty `Values` array
|
|
5434
|
+
* (drop the field silently) from one whose entries were all unparseable
|
|
5435
|
+
* (warn + skip the whole rule — silently dropping would widen routing).
|
|
5436
|
+
*/
|
|
5437
|
+
function parseQueryStringConditionValues(cond) {
|
|
5438
|
+
const cfg = cond["QueryStringConfig"];
|
|
5439
|
+
const rawValues = cfg && typeof cfg === "object" && Array.isArray(cfg["Values"]) ? cfg["Values"] : Array.isArray(cond["Values"]) ? cond["Values"] : [];
|
|
5440
|
+
const parsed = [];
|
|
5441
|
+
for (const v of rawValues) if (typeof v === "string") parsed.push({ value: v });
|
|
5442
|
+
else if (v && typeof v === "object") {
|
|
5443
|
+
const e = v;
|
|
5444
|
+
const value = e["Value"];
|
|
5445
|
+
if (typeof value !== "string") continue;
|
|
5446
|
+
const key = e["Key"];
|
|
5447
|
+
parsed.push(typeof key === "string" ? {
|
|
5448
|
+
key,
|
|
5449
|
+
value
|
|
5450
|
+
} : { value });
|
|
5451
|
+
}
|
|
5452
|
+
return {
|
|
5453
|
+
parsed,
|
|
5454
|
+
hadEntries: rawValues.length > 0
|
|
5455
|
+
};
|
|
5456
|
+
}
|
|
5457
|
+
/**
|
|
5458
|
+
* Cheap CIDR validity check used at parse time. Mirrors the more permissive
|
|
5459
|
+
* matcher (`albCidrMatches`) but only confirms shape; we do not need to
|
|
5460
|
+
* remember the parsed bytes here.
|
|
5461
|
+
*/
|
|
5462
|
+
function isValidCidr(value) {
|
|
5463
|
+
const slash = value.indexOf("/");
|
|
5464
|
+
if (slash === -1) return false;
|
|
5465
|
+
const addr = value.slice(0, slash);
|
|
5466
|
+
const prefix = value.slice(slash + 1);
|
|
5467
|
+
if (!/^\d+$/.test(prefix)) return false;
|
|
5468
|
+
const prefixLen = parseInt(prefix, 10);
|
|
5469
|
+
if (addr.includes(".") && !addr.includes(":")) {
|
|
5470
|
+
const parts = addr.split(".");
|
|
5471
|
+
if (parts.length !== 4) return false;
|
|
5472
|
+
if (!parts.every((p) => /^\d+$/.test(p) && parseInt(p, 10) <= 255)) return false;
|
|
5473
|
+
return prefixLen >= 0 && prefixLen <= 32;
|
|
5474
|
+
}
|
|
5475
|
+
if (addr.includes(":")) return prefixLen >= 0 && prefixLen <= 128;
|
|
5476
|
+
return false;
|
|
5477
|
+
}
|
|
5478
|
+
/**
|
|
5127
5479
|
* Extract a condition's string values from either the typed `<Field>Config`
|
|
5128
5480
|
* sub-object's `Values` or the legacy top-level `Values` array.
|
|
5129
5481
|
*/
|
|
@@ -5364,6 +5716,10 @@ function albStrategy(options) {
|
|
|
5364
5716
|
priority: r.priority,
|
|
5365
5717
|
pathPatterns: r.pathPatterns,
|
|
5366
5718
|
hostPatterns: r.hostPatterns,
|
|
5719
|
+
httpHeaderConditions: r.httpHeaderConditions,
|
|
5720
|
+
httpRequestMethods: r.httpRequestMethods,
|
|
5721
|
+
queryStringConditions: r.queryStringConditions,
|
|
5722
|
+
sourceIpCidrs: r.sourceIpCidrs,
|
|
5367
5723
|
action: qualify(r.action)
|
|
5368
5724
|
}))
|
|
5369
5725
|
});
|
|
@@ -5470,4 +5826,4 @@ function createLocalListCommand(opts = {}) {
|
|
|
5470
5826
|
|
|
5471
5827
|
//#endregion
|
|
5472
5828
|
export { createLocalRunTaskCommand as a, createLocalStartServiceCommand as i, formatTargetListing as n, createLocalInvokeAgentCoreCommand as o, createLocalStartAlbCommand as r, createLocalInvokeCommand as s, createLocalListCommand as t };
|
|
5473
|
-
//# sourceMappingURL=local-list-
|
|
5829
|
+
//# sourceMappingURL=local-list-v8D51s5a.js.map
|