cdk-local 0.72.0 → 0.73.1
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 +18 -2
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +23 -3
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -2
- package/dist/{local-list-D-TP9Cy0.d.ts → local-list-BUY3Tweh.d.ts} +8 -7
- package/dist/{local-list-D-TP9Cy0.d.ts.map → local-list-BUY3Tweh.d.ts.map} +1 -1
- package/dist/{local-list-BOmsOq0r.js → local-list-CE7RB46x.js} +314 -58
- package/dist/local-list-CE7RB46x.js.map +1 -0
- package/package.json +1 -1
- package/dist/local-list-BOmsOq0r.js.map +0 -1
|
@@ -8488,7 +8488,7 @@ function createLocalInvokeCommand(opts = {}) {
|
|
|
8488
8488
|
* `--help` clusters. Chainable: returns `cmd`.
|
|
8489
8489
|
*/
|
|
8490
8490
|
function addInvokeSpecificOptions(cmd) {
|
|
8491
|
-
return cmd.addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--no-pull", "Skip docker pull
|
|
8491
|
+
return cmd.addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--no-pull", "Skip docker pull. Semantics differ by code path: ZIP Lambdas skip pulling the public Lambda base image; Container Lambdas on the local-build path are a no-op (docker build does not refresh the FROM cache by default); Container Lambdas on the ECR-pull fallback skip docker pull AND error if the image is not in the local cache (re-run without --no-pull or pre-pull manually).")).addOption(new Option("--no-build", "Skip docker build on the IMAGE local-build path (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ZIP Lambdas and the IMAGE ECR-pull path. Compatible with --no-pull.")).addOption(new Option("--debug-port <port>", "Node --inspect-brk port (default: off)")).addOption(new Option("--container-host <host>", "Host to bind the RIE port to").default("127.0.0.1")).addOption(new Option("--assume-role [arn]", "Assume the Lambda's deployed execution role and forward STS-issued temp credentials to the container so the handler runs with the deployed function's narrow permissions. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) auto-resolves the function's execution role ARN from state (requires an active state source); (3) `--no-assume-role` explicitly opts out. Off by default — when omitted, the developer's shell credentials are forwarded unchanged (SAM-compatible default). STS failures degrade to a warn + dev-creds fallback.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers. Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the resolved stack name; pass an explicit value when CFn stack name differs. Fn::GetAtt is warn-and-dropped in v1 (CFn ListStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
|
|
8492
8492
|
}
|
|
8493
8493
|
|
|
8494
8494
|
//#endregion
|
|
@@ -10154,6 +10154,30 @@ function shortJson(value) {
|
|
|
10154
10154
|
|
|
10155
10155
|
//#endregion
|
|
10156
10156
|
//#region src/local/cognito-jwt.ts
|
|
10157
|
+
/**
|
|
10158
|
+
* Re-emit window for unreachable-endpoint warns. 5 minutes is short enough
|
|
10159
|
+
* the user notices the degraded state on a normal `--watch` session, long
|
|
10160
|
+
* enough not to spam the log on every request when the upstream IdP is down.
|
|
10161
|
+
*/
|
|
10162
|
+
const WARN_REEMIT_INTERVAL_MS = 300 * 1e3;
|
|
10163
|
+
/**
|
|
10164
|
+
* Returns true iff `key`'s last warn timestamp is older than
|
|
10165
|
+
* {@link WARN_REEMIT_INTERVAL_MS} (or has never been recorded). When true,
|
|
10166
|
+
* `warnedAt[key]` is updated to `now` as a side effect so the next call
|
|
10167
|
+
* within the window returns false.
|
|
10168
|
+
*
|
|
10169
|
+
* Callers that omit `warnedAt` (the per-AuthCheck local fallback in tests
|
|
10170
|
+
* and the no-dedup branch in shape-only call sites) get a no-op true on
|
|
10171
|
+
* every call.
|
|
10172
|
+
*/
|
|
10173
|
+
function shouldWarn(key, warnedAt, now) {
|
|
10174
|
+
if (!warnedAt) return true;
|
|
10175
|
+
const last = warnedAt.get(key);
|
|
10176
|
+
const t = now();
|
|
10177
|
+
if (last !== void 0 && t - last < 3e5) return false;
|
|
10178
|
+
warnedAt.set(key, t);
|
|
10179
|
+
return true;
|
|
10180
|
+
}
|
|
10157
10181
|
const DEFAULT_JWKS_TTL_MS = 3600 * 1e3;
|
|
10158
10182
|
/**
|
|
10159
10183
|
* Failure-mode TTL for JWKS-unreachable entries. Pre-fix the failure
|
|
@@ -10300,7 +10324,7 @@ async function verifyCognitoJwt(authorizer, authorizationHeader, jwksCache, opts
|
|
|
10300
10324
|
identityHash,
|
|
10301
10325
|
ttlSeconds: 0
|
|
10302
10326
|
};
|
|
10303
|
-
return verifyAndShape(token, buildCognitoJwksUrl(selectedPool.region, selectedPool.userPoolId), buildCognitoIssuer(selectedPool.region, selectedPool.userPoolId), void 0, void 0, void 0, jwksCache, opts.
|
|
10327
|
+
return verifyAndShape(token, buildCognitoJwksUrl(selectedPool.region, selectedPool.userPoolId), buildCognitoIssuer(selectedPool.region, selectedPool.userPoolId), void 0, void 0, void 0, jwksCache, opts.warnedAt, now);
|
|
10304
10328
|
}
|
|
10305
10329
|
/**
|
|
10306
10330
|
* Verify a Bearer JWT against an HTTP v2 JWT authorizer's `JwtConfiguration`.
|
|
@@ -10313,7 +10337,7 @@ async function verifyJwtAuthorizer(authorizer, authorizationHeader, jwksCache, o
|
|
|
10313
10337
|
identityHash: void 0,
|
|
10314
10338
|
ttlSeconds: 0
|
|
10315
10339
|
};
|
|
10316
|
-
return verifyAndShape(token, authorizer.region && authorizer.userPoolId ? buildCognitoJwksUrl(authorizer.region, authorizer.userPoolId) : buildJwksUrlFromIssuer(authorizer.issuer), authorizer.issuer.replace(/\/+$/, ""), authorizer.audience, void 0, void 0, jwksCache, opts.
|
|
10340
|
+
return verifyAndShape(token, authorizer.region && authorizer.userPoolId ? buildCognitoJwksUrl(authorizer.region, authorizer.userPoolId) : buildJwksUrlFromIssuer(authorizer.issuer), authorizer.issuer.replace(/\/+$/, ""), authorizer.audience, void 0, void 0, jwksCache, opts.warnedAt, now);
|
|
10317
10341
|
}
|
|
10318
10342
|
/**
|
|
10319
10343
|
* Verify a Bearer JWT against an OIDC-discovery-URL authorizer (Bedrock
|
|
@@ -10349,10 +10373,7 @@ async function verifyJwtViaDiscovery(authorizer, authorizationHeader, jwksCache,
|
|
|
10349
10373
|
issuer = doc.issuer;
|
|
10350
10374
|
jwksUri = doc.jwks_uri;
|
|
10351
10375
|
} catch (err) {
|
|
10352
|
-
if (opts.
|
|
10353
|
-
opts.warned.add(authorizer.discoveryUrl);
|
|
10354
|
-
getLogger().child("cognito-jwt").warn(`OIDC discovery unreachable at ${authorizer.discoveryUrl}: ${err instanceof Error ? err.message : String(err)}. Token accepted without verification — local dev fallback.`);
|
|
10355
|
-
}
|
|
10376
|
+
if (shouldWarn(authorizer.discoveryUrl, opts.warnedAt, now)) getLogger().child("cognito-jwt").warn(`OIDC discovery unreachable at ${authorizer.discoveryUrl}: ${err instanceof Error ? err.message : String(err)}. Token accepted without verification — local dev fallback.`);
|
|
10356
10377
|
const identityHash = buildIdentityHash([token]);
|
|
10357
10378
|
const parsed = parseJwt(token);
|
|
10358
10379
|
if (parsed) return shapeAllowResult(parsed, identityHash, now);
|
|
@@ -10365,16 +10386,13 @@ async function verifyJwtViaDiscovery(authorizer, authorizationHeader, jwksCache,
|
|
|
10365
10386
|
};
|
|
10366
10387
|
}
|
|
10367
10388
|
const allowlist = [...authorizer.allowedAudience ?? [], ...authorizer.allowedClients ?? []];
|
|
10368
|
-
return verifyAndShape(token, jwksUri, issuer.replace(/\/+$/, ""), allowlist.length > 0 ? allowlist : void 0, authorizer.allowedScopes, authorizer.customClaims, jwksCache, opts.
|
|
10389
|
+
return verifyAndShape(token, jwksUri, issuer.replace(/\/+$/, ""), allowlist.length > 0 ? allowlist : void 0, authorizer.allowedScopes, authorizer.customClaims, jwksCache, opts.warnedAt, now);
|
|
10369
10390
|
}
|
|
10370
|
-
async function verifyAndShape(token, jwksUrl, expectedIssuer, expectedAudience, requiredScopes, customClaims, jwksCache,
|
|
10391
|
+
async function verifyAndShape(token, jwksUrl, expectedIssuer, expectedAudience, requiredScopes, customClaims, jwksCache, warnedAt, now) {
|
|
10371
10392
|
const identityHash = buildIdentityHash([token]);
|
|
10372
10393
|
const jwks = await jwksCache.fetchAndCache(jwksUrl);
|
|
10373
10394
|
if (jwks.passThrough) {
|
|
10374
|
-
if (
|
|
10375
|
-
warned.add(jwksUrl);
|
|
10376
|
-
getLogger().child("cognito-jwt").warn(`JWKS pass-through mode for ${jwksUrl}: token accepted without signature verification.`);
|
|
10377
|
-
}
|
|
10395
|
+
if (shouldWarn(jwksUrl, warnedAt, now)) getLogger().child("cognito-jwt").warn(`JWKS pass-through mode for ${jwksUrl}: token accepted without signature verification.`);
|
|
10378
10396
|
const parsed = parseJwt(token);
|
|
10379
10397
|
if (parsed) return shapeAllowResult(parsed, identityHash, now);
|
|
10380
10398
|
return {
|
|
@@ -15669,7 +15687,7 @@ async function runAuthorizerPass(authorizer, snapshot, matchCtx, state, opts, re
|
|
|
15669
15687
|
denyKind: "policy-deny"
|
|
15670
15688
|
};
|
|
15671
15689
|
const authHeader = headers["authorization"];
|
|
15672
|
-
const jwksOpts = { ...opts.
|
|
15690
|
+
const jwksOpts = { ...opts.jwksWarnedAt && { warnedAt: opts.jwksWarnedAt } };
|
|
15673
15691
|
if (authorizer.kind === "cognito") {
|
|
15674
15692
|
if (cache && authHeader !== void 0) {
|
|
15675
15693
|
const cached = cache.get(authorizer.logicalId, hashOne(authHeader));
|
|
@@ -16625,7 +16643,7 @@ async function localStartApiCommand(targets, options, extraStateProviders) {
|
|
|
16625
16643
|
let profileCredsFile;
|
|
16626
16644
|
const authorizerCache = createAuthorizerCache();
|
|
16627
16645
|
const jwksCache = createJwksCache();
|
|
16628
|
-
const
|
|
16646
|
+
const jwksWarnedAt = /* @__PURE__ */ new Map();
|
|
16629
16647
|
let sigV4CredentialsLoader;
|
|
16630
16648
|
const sigV4WarnedForeignIds = /* @__PURE__ */ new Set();
|
|
16631
16649
|
const fromCfnTipEmitted = { value: false };
|
|
@@ -16802,7 +16820,7 @@ async function localStartApiCommand(targets, options, extraStateProviders) {
|
|
|
16802
16820
|
port: basePort === 0 ? 0 : nextPort,
|
|
16803
16821
|
authorizerCache,
|
|
16804
16822
|
jwksCache,
|
|
16805
|
-
|
|
16823
|
+
jwksWarnedAt,
|
|
16806
16824
|
sigV4CredentialsLoader,
|
|
16807
16825
|
sigV4WarnedForeignIds,
|
|
16808
16826
|
sigV4Strict: options.strictSigv4 === true,
|
|
@@ -16849,7 +16867,7 @@ async function localStartApiCommand(targets, options, extraStateProviders) {
|
|
|
16849
16867
|
port: basePort === 0 ? 0 : nextPort,
|
|
16850
16868
|
authorizerCache,
|
|
16851
16869
|
jwksCache,
|
|
16852
|
-
|
|
16870
|
+
jwksWarnedAt,
|
|
16853
16871
|
sigV4WarnedForeignIds,
|
|
16854
16872
|
sigV4Strict: options.strictSigv4 === true,
|
|
16855
16873
|
preDispatch: async (req, res) => {
|
|
@@ -18544,7 +18562,7 @@ async function resolveInboundAuthorization(resolved, options) {
|
|
|
18544
18562
|
...authorizer.allowedClients && { allowedClients: authorizer.allowedClients },
|
|
18545
18563
|
...authorizer.allowedScopes && { allowedScopes: authorizer.allowedScopes },
|
|
18546
18564
|
...authorizer.customClaims && { customClaims: authorizer.customClaims }
|
|
18547
|
-
}, header, createJwksCache(), {
|
|
18565
|
+
}, header, createJwksCache(), { warnedAt: /* @__PURE__ */ new Map() })).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");
|
|
18548
18566
|
logger.info(`Inbound JWT verified against ${authorizer.discoveryUrl}.`);
|
|
18549
18567
|
return header;
|
|
18550
18568
|
}
|
|
@@ -19151,7 +19169,7 @@ function readEnvOverridesFile$2(filePath) {
|
|
|
19151
19169
|
}
|
|
19152
19170
|
function createLocalInvokeAgentCoreCommand(opts = {}) {
|
|
19153
19171
|
setEmbedConfig(opts.embedConfig);
|
|
19154
|
-
const cmd = new Command("invoke-agentcore").description("Run a Bedrock AgentCore Runtime container locally and invoke it once over its protocol contract: HTTP (POST /invocations + GET /ping on 8080)
|
|
19172
|
+
const cmd = new Command("invoke-agentcore").description("Run a Bedrock AgentCore Runtime container locally and invoke it once over its protocol contract: HTTP (POST /invocations + GET /ping on 8080; SSE / WebSocket are HTTP wire-shape variants on the same port), MCP (POST /mcp Streamable HTTP on 8000), A2A, or AGUI. Resolves the AWS::BedrockAgentCore::Runtime, pulls/builds its container, injects env vars + AWS credentials, and prints the response. For an MCP runtime, runs the session handshake then sends one JSON-RPC request (tools/list by default, or the method/params from --event). Target accepts a CDK display path (MyStack/MyAgent) or stack-qualified logical ID (MyStack:MyAgentRuntime1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick from a list. Supports the container artifact and the CodeConfiguration managed-runtime artifact (fromCodeAsset, built from source) on all four protocols; the agent calls real AWS for managed services.").argument("[target]", "CDK display path or stack-qualified logical ID of the AgentCore Runtime to invoke (omit to pick interactively in a TTY)").action(withErrorHandling(async (target, options) => {
|
|
19155
19173
|
await localInvokeAgentCoreCommand(target, options, opts.extraStateProviders);
|
|
19156
19174
|
}));
|
|
19157
19175
|
addInvokeAgentCoreSpecificOptions(cmd);
|
|
@@ -20640,7 +20658,7 @@ function createLocalRunTaskCommand(opts = {}) {
|
|
|
20640
20658
|
* surface non-trivially. Each command keeps its own helper.
|
|
20641
20659
|
*/
|
|
20642
20660
|
function addRunTaskSpecificOptions(cmd) {
|
|
20643
|
-
return cmd.addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default(getEmbedConfig().resourceNamePrefix)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--host-port <containerPort=hostPort...>", "Publish a container port on a specific host port (e.g. 80=8080); repeatable. Default: host port == container port. Use this on macOS to map a privileged container port (< 1024) to a non-privileged host port and avoid the Docker Desktop admin-password prompt.")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed function role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--keep-running", "Don't docker rm -f the user containers on task exit (network + sidecar are still torn down). Use when you want to docker exec into a stopped container for post-mortems.").default(false)).addOption(new Option("--detach", "Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", `Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (\`cdk deploy\`). Bare form uses the ${getEmbedConfig().binaryName} stack name; pass an explicit value when the CFn stack name differs. Fn::GetAtt is warn-and-dropped
|
|
20661
|
+
return cmd.addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default(getEmbedConfig().resourceNamePrefix)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--host-port <containerPort=hostPort...>", "Publish a container port on a specific host port (e.g. 80=8080); repeatable. Default: host port == container port. Use this on macOS to map a privileged container port (< 1024) to a non-privileged host port and avoid the Docker Desktop admin-password prompt.")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed function role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--keep-running", "Don't docker rm -f the user containers on task exit (network + sidecar are still torn down). Use when you want to docker exec into a stopped container for post-mortems.").default(false)).addOption(new Option("--detach", "Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", `Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (\`cdk deploy\`). Bare form uses the ${getEmbedConfig().binaryName} stack name; pass an explicit value when the CFn stack name differs. Fn::GetAtt in container Environment[].Value is warn-and-dropped: CFn ListStackResources does not return per-attribute values, and unlike Lambda (where \`cdkl invoke --from-cfn-stack\` recovers Fn::GetAtt from the deployed function via lambda:GetFunctionConfiguration), no ECS-side equivalent resolves attributes off a deployed task / service.`)).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
|
|
20644
20662
|
}
|
|
20645
20663
|
|
|
20646
20664
|
//#endregion
|
|
@@ -24108,7 +24126,7 @@ function buildAuthCheck(guard, jwksCache, opts = {}) {
|
|
|
24108
24126
|
realm,
|
|
24109
24127
|
check: async () => ({ allow: true })
|
|
24110
24128
|
};
|
|
24111
|
-
const
|
|
24129
|
+
const warnedAt = opts.warnedAt ?? /* @__PURE__ */ new Map();
|
|
24112
24130
|
const sessionCookiePrefix = guard.sessionCookieName;
|
|
24113
24131
|
const injectedBearer = opts.bearerToken;
|
|
24114
24132
|
return {
|
|
@@ -24135,7 +24153,7 @@ function buildAuthCheck(guard, jwksCache, opts = {}) {
|
|
|
24135
24153
|
audience: [guard.audience],
|
|
24136
24154
|
...guard.region !== void 0 && { region: guard.region },
|
|
24137
24155
|
...guard.userPoolId !== void 0 && { userPoolId: guard.userPoolId }
|
|
24138
|
-
}, authorization, jwksCache, {
|
|
24156
|
+
}, authorization, jwksCache, { warnedAt })).allow) return { allow: true };
|
|
24139
24157
|
return {
|
|
24140
24158
|
allow: false,
|
|
24141
24159
|
reason: "Bearer token rejected (signature / iss / aud / exp check failed)."
|
|
@@ -24248,6 +24266,42 @@ function describePinnedImageUri(service) {
|
|
|
24248
24266
|
if (image.kind === "cdk-asset") return void 0;
|
|
24249
24267
|
return image.uri;
|
|
24250
24268
|
}
|
|
24269
|
+
/**
|
|
24270
|
+
* Issue #242 / N1 — dedupe pinned-target detection across the two
|
|
24271
|
+
* sites that need it during a `cdkl start-service` / `cdkl start-alb`
|
|
24272
|
+
* boot:
|
|
24273
|
+
*
|
|
24274
|
+
* 1. The `--image-override` engine's pre-boot resolution
|
|
24275
|
+
* ({@link resolveImageOverrides}) — needs the pinned set + per-
|
|
24276
|
+
* target deployed-registry URI labels so the picker + boot-prompt
|
|
24277
|
+
* hint can name the image the user is overriding.
|
|
24278
|
+
* 2. The post-boot WARN loop in `runEcsServiceEmulator` — surfaces a
|
|
24279
|
+
* per-target WARN naming the deployed-registry URI for every
|
|
24280
|
+
* pinned target the override engine did NOT cover.
|
|
24281
|
+
*
|
|
24282
|
+
* Before this helper both sites re-walked `isLocalCdkAssetImage` +
|
|
24283
|
+
* `describePinnedImageUri` independently. The semantic computation is
|
|
24284
|
+
* identical — given a `ResolvedEcsService`, decide pinned vs local-
|
|
24285
|
+
* asset and (for pinned targets) surface the URI label. Hoisting the
|
|
24286
|
+
* walk here keeps the two call sites in lock-step and gives both a
|
|
24287
|
+
* single test surface.
|
|
24288
|
+
*
|
|
24289
|
+
* Returns one entry per pinned target in the input's iteration order.
|
|
24290
|
+
* Targets whose representative image is a local CDK asset are
|
|
24291
|
+
* filtered out — the caller never sees them.
|
|
24292
|
+
*/
|
|
24293
|
+
function listPinnedTargets(resolvedServices) {
|
|
24294
|
+
const out = [];
|
|
24295
|
+
for (const { target, service } of resolvedServices) {
|
|
24296
|
+
if (isLocalCdkAssetImage(service)) continue;
|
|
24297
|
+
const label = describePinnedImageUri(service);
|
|
24298
|
+
out.push({
|
|
24299
|
+
target,
|
|
24300
|
+
...label !== void 0 && { label }
|
|
24301
|
+
});
|
|
24302
|
+
}
|
|
24303
|
+
return out;
|
|
24304
|
+
}
|
|
24251
24305
|
|
|
24252
24306
|
//#endregion
|
|
24253
24307
|
//#region src/local/image-override-engine.ts
|
|
@@ -24274,16 +24328,76 @@ var ImageOverrideError = class ImageOverrideError extends CdkLocalError {
|
|
|
24274
24328
|
* - `--image-override <svc>=<dockerfile>` -> `explicit.set(svc, dockerfile)`
|
|
24275
24329
|
* - `--image-override <dockerfile>` (no `=`) -> `pickerPaths.push(...)`
|
|
24276
24330
|
* - `--image-build-arg KEY=VAL` -> `globals.buildArgs.set('KEY', 'VAL')`
|
|
24331
|
+
* - `--image-build-arg <svc>:KEY=VAL` -> `perService(svc).buildArgs.set('KEY', 'VAL')` (issue #240)
|
|
24277
24332
|
* - `--image-build-secret id=src` -> `globals.buildSecrets.set('id', 'src')`
|
|
24333
|
+
* - `--image-build-secret <svc>:id=src` -> `perService(svc).buildSecrets.set('id', 'src')` (issue #240)
|
|
24278
24334
|
* - `--image-target <stage>` -> `globals.targetStage = '<stage>'`
|
|
24335
|
+
* - `--image-target <svc>=<stage>` -> `perService(svc).targetStage = '<stage>'` (issue #240)
|
|
24336
|
+
*
|
|
24337
|
+
* Per-service syntax convention (issue #240):
|
|
24338
|
+
* - Flags whose payload already contains `=` (build-arg, build-secret)
|
|
24339
|
+
* use `:` to separate the service prefix from the `<key>=<value>`
|
|
24340
|
+
* payload — `<svc>:KEY=VAL`. The FIRST `:` before the FIRST `=` (if
|
|
24341
|
+
* any) is treated as the prefix delimiter.
|
|
24342
|
+
* - Flags whose payload is a single token (target) use `=` to separate
|
|
24343
|
+
* the service prefix from the stage — `<svc>=stage`. Matches the
|
|
24344
|
+
* `--image-override <svc>=<dockerfile>` convention.
|
|
24345
|
+
* - Per-service entries override the global entry per-key when both
|
|
24346
|
+
* forms set the same key on the same target. Globals still apply to
|
|
24347
|
+
* every OTHER overridden target.
|
|
24279
24348
|
*
|
|
24280
24349
|
* Collision detection: a service target named more than once in
|
|
24281
24350
|
* `--image-override <svc>=...` is an error (last-write-wins would
|
|
24282
24351
|
* silently drop the earlier mapping; explicit error is clearer).
|
|
24352
|
+
* Repeated per-service `--image-build-arg <svc>:KEY=...` entries on
|
|
24353
|
+
* the same `<svc>:KEY` pair are last-write-wins (the same shape the
|
|
24354
|
+
* global form already uses for repeated `KEY=...` entries — both
|
|
24355
|
+
* forms behave identically inside their respective bucket).
|
|
24356
|
+
*
|
|
24357
|
+
* Empty-value semantics:
|
|
24358
|
+
* - `--image-build-arg KEY=` (empty value) is ACCEPTED. The empty
|
|
24359
|
+
* string is forwarded verbatim to `docker build --build-arg KEY=`,
|
|
24360
|
+
* which docker itself accepts (the canonical way to unset a
|
|
24361
|
+
* Dockerfile `ARG`'s default). `KEY=` (empty key) is rejected.
|
|
24362
|
+
* Per-service form `<svc>:KEY=` is similarly accepted (same empty-
|
|
24363
|
+
* value semantics applied to the per-service entry).
|
|
24364
|
+
* - `--image-target ""` (empty value) is REJECTED — an empty
|
|
24365
|
+
* `--target` is meaningless to `docker build` (`--target` is a
|
|
24366
|
+
* single stage name, not a list). Per-service `<svc>=` is similarly
|
|
24367
|
+
* rejected (empty stage).
|
|
24368
|
+
* - `--image-override <svc>=` and `--image-override =<dockerfile>`
|
|
24369
|
+
* are REJECTED — both halves must be non-empty.
|
|
24370
|
+
* - `--image-build-secret id=` and `--image-build-secret =src` are
|
|
24371
|
+
* REJECTED — both halves must be non-empty. Per-service form
|
|
24372
|
+
* `<svc>:id=` and `<svc>:=src` are similarly rejected.
|
|
24283
24373
|
*/
|
|
24284
24374
|
function parseImageOverrideFlags(input) {
|
|
24285
24375
|
const explicit = /* @__PURE__ */ new Map();
|
|
24286
24376
|
const pickerPaths = [];
|
|
24377
|
+
const perService = /* @__PURE__ */ new Map();
|
|
24378
|
+
const getPerSvc = (svc) => {
|
|
24379
|
+
let entry = perService.get(svc);
|
|
24380
|
+
if (!entry) {
|
|
24381
|
+
entry = {
|
|
24382
|
+
buildArgs: /* @__PURE__ */ new Map(),
|
|
24383
|
+
buildSecrets: /* @__PURE__ */ new Map()
|
|
24384
|
+
};
|
|
24385
|
+
perService.set(svc, entry);
|
|
24386
|
+
}
|
|
24387
|
+
return entry;
|
|
24388
|
+
};
|
|
24389
|
+
const splitPerServicePrefix = (raw) => {
|
|
24390
|
+
const colon = raw.indexOf(":");
|
|
24391
|
+
const eq = raw.indexOf("=");
|
|
24392
|
+
if (colon < 0) return null;
|
|
24393
|
+
if (eq >= 0 && eq < colon) return null;
|
|
24394
|
+
const svc = raw.slice(0, colon).trim();
|
|
24395
|
+
if (!svc) return null;
|
|
24396
|
+
return {
|
|
24397
|
+
svc,
|
|
24398
|
+
rest: raw.slice(colon + 1)
|
|
24399
|
+
};
|
|
24400
|
+
};
|
|
24287
24401
|
for (const raw of input.imageOverride ?? []) {
|
|
24288
24402
|
const eq = raw.indexOf("=");
|
|
24289
24403
|
if (eq < 0) {
|
|
@@ -24300,6 +24414,16 @@ function parseImageOverrideFlags(input) {
|
|
|
24300
24414
|
}
|
|
24301
24415
|
const buildArgs = /* @__PURE__ */ new Map();
|
|
24302
24416
|
for (const raw of input.imageBuildArg ?? []) {
|
|
24417
|
+
const prefix = splitPerServicePrefix(raw);
|
|
24418
|
+
if (prefix) {
|
|
24419
|
+
const eq = prefix.rest.indexOf("=");
|
|
24420
|
+
if (eq <= 0) throw new ImageOverrideError(`Invalid --image-build-arg value "${raw}": expected <service>:KEY=VAL.`);
|
|
24421
|
+
const key = prefix.rest.slice(0, eq).trim();
|
|
24422
|
+
const value = prefix.rest.slice(eq + 1);
|
|
24423
|
+
if (!key) throw new ImageOverrideError(`Invalid --image-build-arg value "${raw}": empty key (after service prefix).`);
|
|
24424
|
+
getPerSvc(prefix.svc).buildArgs.set(key, value);
|
|
24425
|
+
continue;
|
|
24426
|
+
}
|
|
24303
24427
|
const eq = raw.indexOf("=");
|
|
24304
24428
|
if (eq <= 0) throw new ImageOverrideError(`Invalid --image-build-arg value "${raw}": expected KEY=VAL.`);
|
|
24305
24429
|
const key = raw.slice(0, eq).trim();
|
|
@@ -24309,6 +24433,17 @@ function parseImageOverrideFlags(input) {
|
|
|
24309
24433
|
}
|
|
24310
24434
|
const buildSecrets = /* @__PURE__ */ new Map();
|
|
24311
24435
|
for (const raw of input.imageBuildSecret ?? []) {
|
|
24436
|
+
const prefix = splitPerServicePrefix(raw);
|
|
24437
|
+
if (prefix) {
|
|
24438
|
+
const eq = prefix.rest.indexOf("=");
|
|
24439
|
+
if (eq <= 0) throw new ImageOverrideError(`Invalid --image-build-secret value "${raw}": expected <service>:id=src (e.g. AppService:npmrc=./.npmrc).`);
|
|
24440
|
+
const id = prefix.rest.slice(0, eq).trim();
|
|
24441
|
+
const src = prefix.rest.slice(eq + 1).trim();
|
|
24442
|
+
if (!id || !src) throw new ImageOverrideError(`Invalid --image-build-secret value "${raw}": id and src must both be non-empty (after service prefix).`);
|
|
24443
|
+
const absSrcPS = isAbsolute(src) ? src : resolve(process.cwd(), src);
|
|
24444
|
+
getPerSvc(prefix.svc).buildSecrets.set(id, absSrcPS);
|
|
24445
|
+
continue;
|
|
24446
|
+
}
|
|
24312
24447
|
const eq = raw.indexOf("=");
|
|
24313
24448
|
if (eq <= 0) throw new ImageOverrideError(`Invalid --image-build-secret value "${raw}": expected id=src (e.g. npmrc=./.npmrc).`);
|
|
24314
24449
|
const id = raw.slice(0, eq).trim();
|
|
@@ -24321,14 +24456,26 @@ function parseImageOverrideFlags(input) {
|
|
|
24321
24456
|
buildArgs,
|
|
24322
24457
|
buildSecrets
|
|
24323
24458
|
};
|
|
24324
|
-
|
|
24325
|
-
|
|
24326
|
-
|
|
24459
|
+
const imageTargetList = input.imageTarget === void 0 ? [] : Array.isArray(input.imageTarget) ? input.imageTarget : [input.imageTarget];
|
|
24460
|
+
for (const raw of imageTargetList) {
|
|
24461
|
+
if (raw === void 0 || raw === null) continue;
|
|
24462
|
+
if (!raw) throw new ImageOverrideError("Invalid --image-target value: empty string.");
|
|
24463
|
+
const eq = raw.indexOf("=");
|
|
24464
|
+
if (eq < 0) {
|
|
24465
|
+
globals.targetStage = raw.trim();
|
|
24466
|
+
continue;
|
|
24467
|
+
}
|
|
24468
|
+
const svc = raw.slice(0, eq).trim();
|
|
24469
|
+
const stage = raw.slice(eq + 1).trim();
|
|
24470
|
+
if (!svc) throw new ImageOverrideError(`Invalid --image-target value "${raw}": left side (service target) is empty.`);
|
|
24471
|
+
if (!stage) throw new ImageOverrideError(`Invalid --image-target value "${raw}": right side (stage) is empty.`);
|
|
24472
|
+
getPerSvc(svc).targetStage = stage;
|
|
24327
24473
|
}
|
|
24328
24474
|
return {
|
|
24329
24475
|
explicit,
|
|
24330
24476
|
pickerPaths,
|
|
24331
|
-
globals
|
|
24477
|
+
globals,
|
|
24478
|
+
perService
|
|
24332
24479
|
};
|
|
24333
24480
|
}
|
|
24334
24481
|
/**
|
|
@@ -24370,7 +24517,7 @@ async function resolveImageOverrides(args) {
|
|
|
24370
24517
|
logger.warn(`--image-override: service '${svc}' is not in the pinned-target set (no deployed-registry pin detected for it). Mapping ignored.`);
|
|
24371
24518
|
continue;
|
|
24372
24519
|
}
|
|
24373
|
-
out.set(svc, makeEntryFromPath(dockerfileRaw, rawFlags
|
|
24520
|
+
out.set(svc, makeEntryFromPath(dockerfileRaw, svc, rawFlags, cwd));
|
|
24374
24521
|
}
|
|
24375
24522
|
if (rawFlags.pickerPaths.length > 0) if (!(isInteractive() && noInteractive !== true)) logger.warn("--image-override <dockerfile> (picker form) requires an interactive TTY and --no-interactive-overrides not to be set. Skipping picker-form mapping(s).");
|
|
24376
24523
|
else for (const dockerfileRaw of rawFlags.pickerPaths) {
|
|
@@ -24395,8 +24542,7 @@ async function resolveImageOverrides(args) {
|
|
|
24395
24542
|
logger.warn(`--image-override ${dockerfileRaw}: empty selection. Skipped.`);
|
|
24396
24543
|
continue;
|
|
24397
24544
|
}
|
|
24398
|
-
const
|
|
24399
|
-
for (const t of chosen) out.set(t, entry);
|
|
24545
|
+
for (const t of chosen) out.set(t, makeEntryFromPath(dockerfileRaw, t, rawFlags, cwd));
|
|
24400
24546
|
}
|
|
24401
24547
|
if (args.interactiveBootPrompt === true && isInteractive() && noInteractive !== true) for (const target of pinnedTargets) {
|
|
24402
24548
|
if (out.has(target)) continue;
|
|
@@ -24410,28 +24556,61 @@ async function resolveImageOverrides(args) {
|
|
|
24410
24556
|
if (!value) continue;
|
|
24411
24557
|
const lower = value.toLowerCase();
|
|
24412
24558
|
if (lower === "n" || lower === "no") continue;
|
|
24413
|
-
out.set(target, makeEntryFromPath(value, rawFlags
|
|
24559
|
+
out.set(target, makeEntryFromPath(value, target, rawFlags, cwd));
|
|
24414
24560
|
}
|
|
24415
24561
|
return out;
|
|
24416
24562
|
}
|
|
24417
24563
|
/**
|
|
24418
|
-
*
|
|
24564
|
+
* Issue #240 — produce the EFFECTIVE per-target build inputs by layering
|
|
24565
|
+
* a per-service overlay on top of the global baseline. Per-service
|
|
24566
|
+
* entries override the global per-key (and per-service `targetStage`
|
|
24567
|
+
* overrides the global `targetStage`).
|
|
24568
|
+
*
|
|
24569
|
+
* The returned Maps are fresh copies — the caller may mutate the
|
|
24570
|
+
* resulting {@link ImageOverrideEntry}'s Maps without bleeding back
|
|
24571
|
+
* into the shared global Maps inside {@link RawImageOverrideFlags}.
|
|
24572
|
+
* Per-service entries are merged AFTER globals so a key shared between
|
|
24573
|
+
* the two ends up with the per-service value (Map's `set` overwrites
|
|
24574
|
+
* on duplicate key).
|
|
24575
|
+
*/
|
|
24576
|
+
function mergeForService(serviceTarget, globals, perService) {
|
|
24577
|
+
const buildArgs = new Map(globals.buildArgs);
|
|
24578
|
+
const buildSecrets = new Map(globals.buildSecrets);
|
|
24579
|
+
let targetStage = globals.targetStage;
|
|
24580
|
+
const overlay = perService.get(serviceTarget);
|
|
24581
|
+
if (overlay) {
|
|
24582
|
+
for (const [k, v] of overlay.buildArgs.entries()) buildArgs.set(k, v);
|
|
24583
|
+
for (const [k, v] of overlay.buildSecrets.entries()) buildSecrets.set(k, v);
|
|
24584
|
+
if (overlay.targetStage !== void 0) targetStage = overlay.targetStage;
|
|
24585
|
+
}
|
|
24586
|
+
return {
|
|
24587
|
+
buildArgs,
|
|
24588
|
+
buildSecrets,
|
|
24589
|
+
...targetStage !== void 0 && { targetStage }
|
|
24590
|
+
};
|
|
24591
|
+
}
|
|
24592
|
+
/**
|
|
24593
|
+
* Promote a per-target Dockerfile path + raw flags into a full
|
|
24419
24594
|
* {@link ImageOverrideEntry}. Resolves the path against `cwd`, asserts
|
|
24420
24595
|
* the Dockerfile exists and is a regular file (so the user sees
|
|
24421
24596
|
* "file not found" up-front rather than mid-`docker build`), and
|
|
24422
24597
|
* derives the build context as the Dockerfile's parent directory
|
|
24423
24598
|
* (the v1 simplification — custom contexts are tracked separately).
|
|
24599
|
+
*
|
|
24600
|
+
* Issue #240 — `serviceTarget` is now part of the entry-construction
|
|
24601
|
+
* input so the global + per-service merge runs per target.
|
|
24424
24602
|
*/
|
|
24425
|
-
function makeEntryFromPath(raw,
|
|
24603
|
+
function makeEntryFromPath(raw, serviceTarget, rawFlags, cwd) {
|
|
24426
24604
|
const abs = isAbsolute(raw) ? raw : resolve(cwd, raw);
|
|
24427
24605
|
if (!existsSync(abs)) throw new ImageOverrideError(`--image-override: Dockerfile '${raw}' does not exist (resolved to '${abs}').`);
|
|
24428
24606
|
if (!statSync(abs).isFile()) throw new ImageOverrideError(`--image-override: '${raw}' is not a regular file (resolved to '${abs}').`);
|
|
24607
|
+
const merged = mergeForService(serviceTarget, rawFlags.globals, rawFlags.perService);
|
|
24429
24608
|
return {
|
|
24430
24609
|
dockerfile: abs,
|
|
24431
24610
|
contextDir: dirname(abs),
|
|
24432
|
-
buildArgs:
|
|
24433
|
-
buildSecrets:
|
|
24434
|
-
...
|
|
24611
|
+
buildArgs: merged.buildArgs,
|
|
24612
|
+
buildSecrets: merged.buildSecrets,
|
|
24613
|
+
...merged.targetStage !== void 0 && { targetStage: merged.targetStage }
|
|
24435
24614
|
};
|
|
24436
24615
|
}
|
|
24437
24616
|
/**
|
|
@@ -24493,10 +24672,26 @@ function buildImageOverrideTag(serviceTarget, entry) {
|
|
|
24493
24672
|
* order maximizes cache reuse). On any failure the function rejects
|
|
24494
24673
|
* with {@link ImageOverrideError}; the emulator surfaces this before
|
|
24495
24674
|
* any container is started.
|
|
24675
|
+
*
|
|
24676
|
+
* Partial-failure rollback (issue #242 / N3): when build N (1-indexed)
|
|
24677
|
+
* fails, the 1..(N-1) successfully built local-only tags from THIS run
|
|
24678
|
+
* are best-effort `docker image rm`'d before re-throwing. Not a leak
|
|
24679
|
+
* (the tags are deterministic per
|
|
24680
|
+
* {@link buildImageOverrideTag} so a re-run collides safely with any
|
|
24681
|
+
* leftover), but the cleanup keeps the local Docker daemon tidy after
|
|
24682
|
+
* a failed boot — disk-pressure scenarios that occasionally surface
|
|
24683
|
+
* on dev laptops never accumulate orphan override images across a
|
|
24684
|
+
* day of iterations. Cleanup failures are logged at `debug` (another
|
|
24685
|
+
* container could be using the image, the image could already have
|
|
24686
|
+
* been removed by a parallel `docker system prune`, etc.) and the
|
|
24687
|
+
* loop continues so one un-removable tag doesn't shadow the others.
|
|
24688
|
+
* The originating {@link ImageOverrideError} is always the thrown
|
|
24689
|
+
* value — a cleanup-step error never replaces it.
|
|
24496
24690
|
*/
|
|
24497
24691
|
async function runImageOverrideBuilds(overrides) {
|
|
24498
24692
|
const logger = getLogger();
|
|
24499
24693
|
const out = /* @__PURE__ */ new Map();
|
|
24694
|
+
const builtTags = [];
|
|
24500
24695
|
for (const [target, entry] of overrides.entries()) {
|
|
24501
24696
|
const tag = buildImageOverrideTag(target, entry);
|
|
24502
24697
|
const args = [
|
|
@@ -24518,12 +24713,69 @@ async function runImageOverrideBuilds(overrides) {
|
|
|
24518
24713
|
});
|
|
24519
24714
|
} catch (err) {
|
|
24520
24715
|
const e = err;
|
|
24521
|
-
|
|
24716
|
+
const wrapped = new ImageOverrideError(`docker build failed for --image-override '${target}' (Dockerfile=${entry.dockerfile}): ` + (e.stderr?.trim() || e.message || String(err)));
|
|
24717
|
+
for (const priorTag of builtTags) try {
|
|
24718
|
+
await runDockerStreaming([
|
|
24719
|
+
"image",
|
|
24720
|
+
"rm",
|
|
24721
|
+
priorTag
|
|
24722
|
+
], {});
|
|
24723
|
+
} catch (rmErr) {
|
|
24724
|
+
logger.debug(`Image-override rollback: \`docker image rm ${priorTag}\` failed: ${rmErr instanceof Error ? rmErr.message : String(rmErr)}.`);
|
|
24725
|
+
}
|
|
24726
|
+
throw wrapped;
|
|
24522
24727
|
}
|
|
24523
24728
|
out.set(target, tag);
|
|
24729
|
+
builtTags.push(tag);
|
|
24524
24730
|
}
|
|
24525
24731
|
return out;
|
|
24526
24732
|
}
|
|
24733
|
+
/**
|
|
24734
|
+
* Issue #240 — orphan validation. A per-service build-input flag
|
|
24735
|
+
* (`--image-build-arg <svc>:KEY=VAL`, `--image-build-secret
|
|
24736
|
+
* <svc>:id=src`, or `--image-target <svc>=stage`) names a service that
|
|
24737
|
+
* MUST appear in the resolved override map — otherwise the per-service
|
|
24738
|
+
* value silently gets discarded because no `docker build` runs for
|
|
24739
|
+
* that service.
|
|
24740
|
+
*
|
|
24741
|
+
* Called AFTER {@link resolveImageOverrides} (Stage 3 boot prompt
|
|
24742
|
+
* complete) so a service the user added via the prompt counts as
|
|
24743
|
+
* covered. A still-orphan per-service flag at this point means the
|
|
24744
|
+
* user typo'd the service name OR forgot a corresponding
|
|
24745
|
+
* `--image-override <svc>=<dockerfile>` mapping (and either chose `N`
|
|
24746
|
+
* in the boot prompt or ran with `--no-interactive-overrides`).
|
|
24747
|
+
*
|
|
24748
|
+
* Throws {@link LocalStartServiceError} naming every offending
|
|
24749
|
+
* `<flag>` + `<service>` pair so the user can fix all in one go
|
|
24750
|
+
* (matches the `enforceStrictOverrides` pattern). Mutates nothing.
|
|
24751
|
+
*
|
|
24752
|
+
* Host-side use case: cdkd and other shim hosts that wrap the engine
|
|
24753
|
+
* re-export this via `cdk-local/internal` and call it right after
|
|
24754
|
+
* their own `resolveImageOverrides` invocation to inherit the same
|
|
24755
|
+
* orphan-detection semantics.
|
|
24756
|
+
*
|
|
24757
|
+
* @param rawFlags Output of {@link parseImageOverrideFlags}.
|
|
24758
|
+
* @param resolvedOverrides Output of {@link resolveImageOverrides}
|
|
24759
|
+
* (after the boot prompt has run).
|
|
24760
|
+
*/
|
|
24761
|
+
function enforceImageOverrideOrphans(rawFlags, resolvedOverrides) {
|
|
24762
|
+
if (rawFlags.perService.size === 0) return;
|
|
24763
|
+
const offenders = [];
|
|
24764
|
+
for (const [svc, overlay] of rawFlags.perService.entries()) {
|
|
24765
|
+
if (resolvedOverrides.has(svc)) continue;
|
|
24766
|
+
if (overlay.buildArgs.size > 0) {
|
|
24767
|
+
const keys = Array.from(overlay.buildArgs.keys()).join(", ");
|
|
24768
|
+
offenders.push(`--image-build-arg ${svc}:${keys} references a service with no --image-override mapping.`);
|
|
24769
|
+
}
|
|
24770
|
+
if (overlay.buildSecrets.size > 0) {
|
|
24771
|
+
const ids = Array.from(overlay.buildSecrets.keys()).join(", ");
|
|
24772
|
+
offenders.push(`--image-build-secret ${svc}:${ids} references a service with no --image-override mapping.`);
|
|
24773
|
+
}
|
|
24774
|
+
if (overlay.targetStage !== void 0) offenders.push(`--image-target ${svc}=${overlay.targetStage} references a service with no --image-override mapping.`);
|
|
24775
|
+
}
|
|
24776
|
+
if (offenders.length === 0) return;
|
|
24777
|
+
throw new LocalStartServiceError(`Per-service image-override flag(s) target a service with no --image-override coverage. Add the matching --image-override <service>=<dockerfile>, drop the per-service flag, or fix the service name:\n ${offenders.join("\n ")}`);
|
|
24778
|
+
}
|
|
24527
24779
|
|
|
24528
24780
|
//#endregion
|
|
24529
24781
|
//#region src/cli/commands/ecs-service-emulator.ts
|
|
@@ -24676,15 +24928,15 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
|
|
|
24676
24928
|
logEndpointsBanner(perTarget, frontDoorServers, logger);
|
|
24677
24929
|
logger.info("Press ^C to shut down.");
|
|
24678
24930
|
const uncoveredPinnedTargets = [];
|
|
24679
|
-
|
|
24680
|
-
|
|
24681
|
-
|
|
24682
|
-
|
|
24683
|
-
|
|
24684
|
-
|
|
24685
|
-
const uriDisplay =
|
|
24686
|
-
logger.warn(`'${
|
|
24687
|
-
uncoveredPinnedTargets.push(
|
|
24931
|
+
const pinnedEntries = listPinnedTargets(perTarget.filter((pt) => pt.controller?.service).map((pt) => ({
|
|
24932
|
+
target: pt.boot.target,
|
|
24933
|
+
service: pt.controller.service
|
|
24934
|
+
})));
|
|
24935
|
+
for (const entry of pinnedEntries) {
|
|
24936
|
+
if (imageOverrideTags.has(entry.target)) continue;
|
|
24937
|
+
const uriDisplay = entry.label ? `\`${entry.label}\`` : "a deployed registry";
|
|
24938
|
+
logger.warn(`'${entry.target}': running image is pinned to a deployed registry (${uriDisplay}). To iterate on local source, either pass \`--image-override <dockerfile>\` (or \`<service>=<dockerfile>\`) to substitute a local build, or drop \`--from-cfn-stack\` and switch the CDK app to \`ContainerImage.fromAsset(...)\`.`);
|
|
24939
|
+
uncoveredPinnedTargets.push(entry.target);
|
|
24688
24940
|
}
|
|
24689
24941
|
enforceStrictOverrides(options.strictOverrides === true, uncoveredPinnedTargets);
|
|
24690
24942
|
if (options.watch === true && strategy.supportsWatch === true) {
|
|
@@ -25185,11 +25437,11 @@ async function buildFrontDoor(plan, options, logger) {
|
|
|
25185
25437
|
keyPath: options.tlsKey
|
|
25186
25438
|
}) : void 0;
|
|
25187
25439
|
const jwksCache = createJwksCache();
|
|
25188
|
-
const
|
|
25440
|
+
const sharedWarnedAt = /* @__PURE__ */ new Map();
|
|
25189
25441
|
const authForGuard = (guard) => buildAuthCheck(guard, jwksCache, {
|
|
25190
25442
|
...options.verifyAuth === false && { noVerifyAuth: true },
|
|
25191
25443
|
...options.bearerToken !== void 0 && { bearerToken: options.bearerToken },
|
|
25192
|
-
|
|
25444
|
+
warnedAt: sharedWarnedAt
|
|
25193
25445
|
});
|
|
25194
25446
|
const attachAuth = (action, guard) => guard ? {
|
|
25195
25447
|
...action,
|
|
@@ -25554,8 +25806,7 @@ async function resolveSharedSidecarCredentials(options) {
|
|
|
25554
25806
|
*/
|
|
25555
25807
|
async function resolveAndBuildImageOverrides(args) {
|
|
25556
25808
|
const { perTarget, stacks, options, extraStateProviders, logger } = args;
|
|
25557
|
-
const
|
|
25558
|
-
const pinnedLabels = /* @__PURE__ */ new Map();
|
|
25809
|
+
const resolvedForPeek = [];
|
|
25559
25810
|
for (const target of perTarget) {
|
|
25560
25811
|
const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
|
|
25561
25812
|
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
@@ -25571,18 +25822,22 @@ async function resolveAndBuildImageOverrides(args) {
|
|
|
25571
25822
|
if (stateProvider) stateProvider.dispose();
|
|
25572
25823
|
}
|
|
25573
25824
|
if (!service) continue;
|
|
25574
|
-
|
|
25575
|
-
|
|
25576
|
-
|
|
25577
|
-
|
|
25825
|
+
resolvedForPeek.push({
|
|
25826
|
+
target,
|
|
25827
|
+
service
|
|
25828
|
+
});
|
|
25578
25829
|
}
|
|
25830
|
+
const pinnedEntries = listPinnedTargets(resolvedForPeek);
|
|
25831
|
+
const pinnedTargets = pinnedEntries.map((e) => e.target);
|
|
25832
|
+
const pinnedLabels = /* @__PURE__ */ new Map();
|
|
25833
|
+
for (const entry of pinnedEntries) if (entry.label !== void 0) pinnedLabels.set(entry.target, entry.label);
|
|
25579
25834
|
const rawFlags = parseImageOverrideFlags({
|
|
25580
25835
|
...options.imageOverride && { imageOverride: options.imageOverride },
|
|
25581
25836
|
...options.imageBuildArg && { imageBuildArg: options.imageBuildArg },
|
|
25582
25837
|
...options.imageBuildSecret && { imageBuildSecret: options.imageBuildSecret },
|
|
25583
25838
|
...options.imageTarget && { imageTarget: options.imageTarget }
|
|
25584
25839
|
});
|
|
25585
|
-
if (pinnedTargets.length === 0 && rawFlags.explicit.size === 0 && rawFlags.pickerPaths.length === 0) return /* @__PURE__ */ new Map();
|
|
25840
|
+
if (pinnedTargets.length === 0 && rawFlags.explicit.size === 0 && rawFlags.pickerPaths.length === 0 && rawFlags.perService.size === 0) return /* @__PURE__ */ new Map();
|
|
25586
25841
|
const overrides = await resolveImageOverrides({
|
|
25587
25842
|
rawFlags,
|
|
25588
25843
|
pinnedTargets,
|
|
@@ -25590,6 +25845,7 @@ async function resolveAndBuildImageOverrides(args) {
|
|
|
25590
25845
|
interactiveBootPrompt: true,
|
|
25591
25846
|
noInteractive: options.interactiveOverrides === false
|
|
25592
25847
|
});
|
|
25848
|
+
enforceImageOverrideOrphans(rawFlags, overrides);
|
|
25593
25849
|
if (overrides.size === 0) return /* @__PURE__ */ new Map();
|
|
25594
25850
|
return runImageOverrideBuilds(overrides);
|
|
25595
25851
|
}
|
|
@@ -25613,7 +25869,7 @@ function enforceStrictOverrides(strict, uncoveredPinnedTargets) {
|
|
|
25613
25869
|
* factory.
|
|
25614
25870
|
*/
|
|
25615
25871
|
function addCommonEcsServiceOptions(cmd) {
|
|
25616
|
-
cmd.addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default(getEmbedConfig().resourceNamePrefix)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed task role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--max-tasks <n>", `Hard cap on local replica count. Caps the template DesiredCount so local dev machines don't run an unbounded number of containers. Cannot exceed ${83} due to the per-replica link-local /24 subnet allocator's range.`).default(3).argParser(parseMaxTasks)).addOption(new Option("--restart-policy <policy>", "How to react when an essential container exits. 'on-failure' (default) restarts only on non-zero exit; 'always' restarts on every exit; 'none' shuts the replica down and runs the service degraded.").default("on-failure").argParser(parseRestartPolicy)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", `Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (\`cdk deploy\`). Bare form uses the ${getEmbedConfig().binaryName} stack name; pass an explicit value when the CFn stack name differs. Fn::GetAtt is warn-and-dropped
|
|
25872
|
+
cmd.addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default(getEmbedConfig().resourceNamePrefix)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed task role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--max-tasks <n>", `Hard cap on local replica count. Caps the template DesiredCount so local dev machines don't run an unbounded number of containers. Cannot exceed ${83} due to the per-replica link-local /24 subnet allocator's range.`).default(3).argParser(parseMaxTasks)).addOption(new Option("--restart-policy <policy>", "How to react when an essential container exits. 'on-failure' (default) restarts only on non-zero exit; 'always' restarts on every exit; 'none' shuts the replica down and runs the service degraded.").default("on-failure").argParser(parseRestartPolicy)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", `Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (\`cdk deploy\`). Bare form uses the ${getEmbedConfig().binaryName} stack name; pass an explicit value when the CFn stack name differs. Fn::GetAtt in container Environment[].Value is warn-and-dropped: CFn ListStackResources does not return per-attribute values, and unlike Lambda (where \`cdkl invoke --from-cfn-stack\` recovers Fn::GetAtt from the deployed function via lambda:GetFunctionConfiguration), no ECS-side equivalent resolves attributes off a deployed task / service.`)).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).addOption(new Option("--no-logs", "Disable foreground streaming of each replica container stdout/stderr. By default every booted replica streams its docker logs to the host terminal with a [svc=<service> r=<replica-index> c=<container>] prefix (parity with `run-task`). Pass --no-logs for multi-replica / multi-service runs whose interleaved log volume is unreadable; `docker logs -f <id>` in a separate terminal stays available."));
|
|
25617
25873
|
[
|
|
25618
25874
|
...commonOptions(),
|
|
25619
25875
|
...appOptions(),
|
|
@@ -25639,7 +25895,7 @@ function addCommonEcsServiceOptions(cmd) {
|
|
|
25639
25895
|
* factories inherit the flag set without duplication.
|
|
25640
25896
|
*/
|
|
25641
25897
|
function addImageOverrideOptions(cmd) {
|
|
25642
|
-
return cmd.addOption(new Option("--image-override <service=dockerfile or dockerfile...>", "Replace a service target's deployed-registry image with a local `docker build` of the supplied Dockerfile (repeatable). Two forms: <service>=<dockerfile> binds the service explicitly; a bare <dockerfile> opens a multi-select picker against the still-uncovered pinned targets (one Dockerfile across N services). Mix freely. Lets `--from-cfn-stack` still reach real AWS state while you iterate locally on the application container.")).addOption(new Option("--image-build-arg <KEY=VAL...>", "Global `docker build --build-arg KEY=VAL` pair applied to every --image-override build (repeatable). Lets a CDK Dockerfile that branches on an ARG (env target, base image tag, package mirror) honor that ARG locally without editing the Dockerfile.")).addOption(new Option("--image-build-secret <id=src...>", "Global `docker build --secret id=<id>,src=<src>` entry applied to every --image-override build (repeatable). Enables `RUN --mount=type=secret,id=<id>` in the Dockerfile — the standard private-registry / npm-token recipe (e.g. `--image-build-secret npmrc=./.npmrc`).")).addOption(new Option("--image-target <stage
|
|
25898
|
+
return cmd.addOption(new Option("--image-override <service=dockerfile or dockerfile...>", "Replace a service target's deployed-registry image with a local `docker build` of the supplied Dockerfile (repeatable). Two forms: <service>=<dockerfile> binds the service explicitly; a bare <dockerfile> opens a multi-select picker against the still-uncovered pinned targets (one Dockerfile across N services). Mix freely. Lets `--from-cfn-stack` still reach real AWS state while you iterate locally on the application container.")).addOption(new Option("--image-build-arg <KEY=VAL...>", "Global `docker build --build-arg KEY=VAL` pair applied to every --image-override build (repeatable). Lets a CDK Dockerfile that branches on an ARG (env target, base image tag, package mirror) honor that ARG locally without editing the Dockerfile.")).addOption(new Option("--image-build-secret <id=src...>", "Global `docker build --secret id=<id>,src=<src>` entry applied to every --image-override build (repeatable). Enables `RUN --mount=type=secret,id=<id>` in the Dockerfile — the standard private-registry / npm-token recipe (e.g. `--image-build-secret npmrc=./.npmrc`).")).addOption(new Option("--image-target <stage or svc=stage...>", "Repeatable. Bare `<stage>` is a global --target for every --image-override build; `<service>=<stage>` (issue #240) scopes the --target to one overridden target so a monorepo with different multi-stage Dockerfiles per service can stop each at its own intermediate stage. Per-service form overrides the global on the named target.")).addOption(new Option("--no-interactive-overrides", "Suppress the interactive boot prompt that walks each pinned target asking for a Dockerfile path, and the multi-select prompt fired by `--image-override <dockerfile>` (picker form). Useful for CI / scripted invocations.")).addOption(new Option("--strict-overrides", "Fail fast at boot when any pinned target remains uncovered after `--image-override` + the boot prompt resolve. Off by default; the boot WARN per uncovered pinned target still fires regardless.").default(false));
|
|
25643
25899
|
}
|
|
25644
25900
|
|
|
25645
25901
|
//#endregion
|
|
@@ -26662,5 +26918,5 @@ function addListSpecificOptions(cmd) {
|
|
|
26662
26918
|
}
|
|
26663
26919
|
|
|
26664
26920
|
//#endregion
|
|
26665
|
-
export {
|
|
26666
|
-
//# sourceMappingURL=local-list-
|
|
26921
|
+
export { groupRoutesByServer as $, tryResolveImageFnJoin as $n, AGENTCORE_SESSION_ID_HEADER as $t, isLocalCdkAssetImage as A, collectSsmParameterRefs as An, buildCognitoJwksUrl as At, addInvokeAgentCoreSpecificOptions as B, pickRefLogicalId as Bn, isFunctionUrlOacFronted as Bt, buildImageOverrideTag as C, createLocalStateProvider as Cn, ConnectionRegistry as Ct, resolveImageOverrides as D, resolveCfnRegion as Dn, buildConnectEvent as Dt, parseImageOverrideFlags as E, resolveCfnFallbackRegion as En, parseConnectionsPath as Et, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as F, listTargets as Fn, verifyJwtViaDiscovery as Ft, resolveApiTargetSubset as G, AGENTCORE_MCP_PROTOCOL as Gn, a2aInvokeOnce as Gt, addStartApiSpecificOptions as H, AGENTCORE_A2A_PROTOCOL as Hn, invokeAgentCoreWs as Ht, getContainerNetworkIp as I, discoverWebSocketApis as In, attachAuthorizers as It, attachStageContext as J, pickAgentCoreCandidateStack as Jn, MCP_PROTOCOL_VERSION as Jt, createAuthorizerCache as K, AGENTCORE_RUNTIME_TYPE as Kn, MCP_CONTAINER_PORT as Kt, addRunTaskSpecificOptions as L, discoverWebSocketApisOrThrow as Ln, applyCorsResponseHeaders as Lt, buildCloudMapIndex as M, resolveWatchConfig as Mn, createJwksCache as Mt, CloudMapRegistry as N, resolveSingleTarget as Nn, verifyCognitoJwt as Nt, runImageOverrideBuilds as O, resolveCfnStackName as On, buildDisconnectEvent as Ot, classifySourceChange as P, countTargets as Pn, verifyJwtAuthorizer as Pt, filterRoutesByApiIdentifiers as Q, substituteImagePlaceholders as Qn, signAgentCoreInvocation as Qt, createLocalRunTaskCommand as R, parseSelectionExpressionPath as Rn, buildCorsConfigByApiId as Rt, ImageOverrideError as S, LocalStateSourceError as Sn, bufferToBody as St, mergeForService as T, rejectExplicitCfnStackWithMultipleStacks as Tn, handleConnectionsRequest as Tt, createLocalStartApiCommand as U, AGENTCORE_AGUI_PROTOCOL as Un, A2A_CONTAINER_PORT as Ut, createLocalInvokeAgentCoreCommand as V, resolveLambdaArnIntrinsic as Vn, matchPreflight as Vt, createWatchPredicates as W, AGENTCORE_HTTP_PROTOCOL as Wn, A2A_PATH as Wt, availableApiIdentifiers as X, derivePseudoParametersFromRegion as Xn, parseSseForJsonRpc as Xt, buildStageMap as Y, resolveAgentCoreTarget as Yn, mcpInvokeOnce as Yt, filterRoutesByApiIdentifier as Z, formatStateRemedy as Zn, AGENTCORE_SIGV4_SERVICE as Zt, buildEcsImageResolutionContext as _, substituteAgainstStateAsync as _n, selectIntegrationResponse as _t, albStrategy as a, computeCodeImageTag as an, buildMethodArn as at, resolveSharedSidecarCredentials as b, resolveEnvVars as bn, HOST_GATEWAY_MIN_VERSION as bt, resolveAlbTarget as c, addInvokeSpecificOptions as cn, invokeRequestAuthorizer as ct, addStartServiceSpecificOptions as d, buildContainerImage as dn, translateLambdaResponse as dt, invokeAgentCore as en, LocalInvokeBuildError as er, readMtlsMaterialsFromDisk as et, createLocalStartServiceCommand as f, resolveRuntimeCodeMountPath as fn, applyAuthorizerOverlay as ft, addImageOverrideOptions as g, substituteAgainstState as gn, pickResponseTemplate as gt, addCommonEcsServiceOptions as h, EcsTaskResolutionError as hn, evaluateResponseParameters as ht, addAlbSpecificOptions as i, buildAgentCoreCodeImage as in, defaultCredentialsLoader as it, listPinnedTargets as j, resolveSsmParameters as jn, buildJwksUrlFromIssuer as jt, describePinnedImageUri as k, CfnLocalStateProvider as kn, buildMessageEvent as kt, isApplicationLoadBalancer as l, createLocalInvokeCommand as ln, invokeTokenAuthorizer as lt, MAX_TASKS_SUBNET_RANGE_CAP as m, resolveRuntimeImage as mn, buildRestV1Event as mt, createLocalListCommand as n, downloadAndExtractS3Bundle as nn, resolveSelectionExpression as nt, createLocalStartAlbCommand as o, renderCodeDockerfile as on, computeRequestIdentityHash as ot, serviceStrategy as p, resolveRuntimeFileExtension as pn, buildHttpApiV2Event as pt, createFileWatcher as q, AgentCoreResolutionError as qn, MCP_PATH as qt, formatTargetListing as r, SUPPORTED_CODE_RUNTIMES as rn, resolveServiceIntegrationParameters as rt, parseLbPortOverrides as s, toCmdArgv as sn, evaluateCachedLambdaPolicy as st, addListSpecificOptions as t, waitForAgentCorePing as tn, startApiServer as tt, resolveAlbFrontDoor as u, architectureToPlatform as un, matchRoute as ut, parseMaxTasks as v, substituteEnvVarsFromState as vn, tryParseStatus as vt, enforceImageOverrideOrphans as w, isCfnFlagPresent as wn, buildMgmtEndpointEnvUrl as wt, runEcsServiceEmulator as x, materializeLayerFromArn as xn, probeHostGatewaySupport as xt, parseRestartPolicy as y, substituteEnvVarsFromStateAsync as yn, VtlEvaluationError as yt, attachContainerLogStreamer as z, discoverRoutes as zn, buildCorsConfigFromCloudFrontChain as zt };
|
|
26922
|
+
//# sourceMappingURL=local-list-CE7RB46x.js.map
|