cdk-local 0.73.6 → 0.74.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.
@@ -1,4 +1,4 @@
1
- import { a as runDockerStreaming, c as getEmbedConfig, i as runDockerForeground, n as formatDockerLoginError, o as spawnStreaming, r as getDockerCmd, s as getLogger, u as setEmbedConfig } from "./docker-cmd-voNPrcRh.js";
1
+ import { a as runDockerStreaming, c as getEmbedConfig, i as runDockerForeground, n as formatDockerLoginError, o as spawnStreaming, r as getDockerCmd, s as getLogger, u as setEmbedConfig } from "./docker-cmd-Dqx2YENO.js";
2
2
  import { cpSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { homedir, tmpdir } from "node:os";
4
4
  import * as path$1 from "node:path";
@@ -6498,7 +6498,10 @@ async function pullImage(image, skipPull) {
6498
6498
  }
6499
6499
  logger.debug(`Pulling ${image} (silent — pass --verbose to stream progress)`);
6500
6500
  try {
6501
- await runDockerStreaming(["pull", image], { streamLive: false });
6501
+ await runDockerStreaming(["pull", image], {
6502
+ streamLive: false,
6503
+ progressLabel: `Pulling ${image}`
6504
+ });
6502
6505
  } catch (err) {
6503
6506
  const e = err;
6504
6507
  if (e.exitCode === void 0 || e.exitCode === null) throw new DockerRunnerError(`docker pull ${image} failed: ${e.message ?? String(err)}`);
@@ -6773,7 +6776,8 @@ async function buildDockerImage(asset, cdkOutDir, options) {
6773
6776
  try {
6774
6777
  await runDockerStreaming(buildArgs, {
6775
6778
  cwd: contextDir,
6776
- env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" }
6779
+ env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" },
6780
+ ...options.progressLabel !== void 0 && { progressLabel: options.progressLabel }
6777
6781
  });
6778
6782
  } catch (err) {
6779
6783
  const e = err;
@@ -7080,7 +7084,8 @@ async function buildContainerImage(asset, cdkOutDir, options) {
7080
7084
  const actualTag = await buildDockerImage(asset, cdkOutDir, {
7081
7085
  tag,
7082
7086
  platform,
7083
- wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`)
7087
+ wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`),
7088
+ progressLabel: `Building container image (platform=${platform})`
7084
7089
  });
7085
7090
  if (actualTag !== tag) {
7086
7091
  logger.debug(`Re-tagging executable-built image '${actualTag}' → '${tag}'`);
@@ -15269,7 +15274,8 @@ async function localStartApiCommand(targets, options, extraStateProviders) {
15269
15274
  const appCmd = resolveApp(options.app);
15270
15275
  if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
15271
15276
  const overrides = readEnvOverridesFile$4(options.envVars);
15272
- const debugPortBase = options.debugPortBase ? parseDebugPort(options.debugPortBase) : void 0;
15277
+ const debugPortRaw = options.debugPortBase ?? options.debugPort;
15278
+ const debugPortBase = debugPortRaw ? parseDebugPort(debugPortRaw) : void 0;
15273
15279
  const perLambdaConcurrency = parsePerLambdaConcurrency(options.perLambdaConcurrency);
15274
15280
  const inlineTmpDirs = /* @__PURE__ */ new Set();
15275
15281
  const layerTmpDirs = /* @__PURE__ */ new Set();
@@ -15378,7 +15384,9 @@ async function localStartApiCommand(targets, options, extraStateProviders) {
15378
15384
  layerTmpDirs,
15379
15385
  stateByStack,
15380
15386
  skipPull: options.pull === false,
15387
+ skipBuild: options.build === false,
15381
15388
  ...options.profile !== void 0 && { profile: options.profile },
15389
+ ...options.ecrRoleArn !== void 0 && { ecrRoleArn: options.ecrRoleArn },
15382
15390
  ...options.layerRoleArn !== void 0 && { layerRoleArn: options.layerRoleArn },
15383
15391
  ...profileCredentials && { profileCredentials },
15384
15392
  ...profileCredsFile && { profileCredsFile },
@@ -16050,7 +16058,11 @@ async function buildContainerSpec(args) {
16050
16058
  codeDir = lambda.codePath ?? materializeInlineCode$2(lambda.handler, lambda.inlineCode ?? "", resolveRuntimeFileExtension(lambda.runtime), inlineTmpDirs);
16051
16059
  optDir = await materializeLambdaLayers$1(lambda.layers, layerTmpDirs, layerRoleArn);
16052
16060
  } else {
16053
- imageRef = (await resolveContainerImageForStartApi(lambda, skipPull, args.profile)).imageRef;
16061
+ imageRef = (await resolveContainerImageForStartApi(lambda, skipPull, args.profile, {
16062
+ ...args.skipBuild !== void 0 && { noBuild: args.skipBuild },
16063
+ ...args.ecrRoleArn !== void 0 && { ecrRoleArn: args.ecrRoleArn },
16064
+ ...args.stsRegion !== void 0 && { region: args.stsRegion }
16065
+ })).imageRef;
16054
16066
  platform = architectureToPlatform(lambda.architecture);
16055
16067
  }
16056
16068
  let templateEnv = getTemplateEnv$1(lambda.resource);
@@ -16171,23 +16183,30 @@ async function buildContainerSpec(args) {
16171
16183
  * Resolve a container Lambda's local docker image — local build from
16172
16184
  * `cdk.out` asset manifest first, ECR-pull fallback when the asset
16173
16185
  * manifest has no matching entry. Mirrors `cdkl invoke`'s
16174
- * `resolveContainerImagePlan` shape; the start-api server doesn't
16175
- * need the no-build flag (deterministic-tag cache reuse is automatic
16176
- * across reloads because the per-Lambda tag is content-addressed).
16186
+ * `resolveContainerImagePlan` shape.
16177
16187
  *
16178
- * Same-account / same-region only on the ECR-pull path (matches the
16179
- * `cdkl invoke` PR 5 of #224 boundary). Cross-account /
16180
- * cross-region ECR pull is the W2-1 deferred follow-up.
16188
+ * Issue #249 / C7 + C8 `noBuild` and `ecrRoleArn` are accepted for
16189
+ * parity with `cdkl invoke` / `cdkl run-task`. `noBuild` short-circuits
16190
+ * the local-asset build path (the deterministic content-addressed tag
16191
+ * must already be in the local registry). `ecrRoleArn` lets the
16192
+ * ECR-pull fallback authenticate against cross-account / centralized
16193
+ * registries via STS AssumeRole — same-account / same-region pulls do
16194
+ * not need it.
16181
16195
  */
16182
- async function resolveContainerImageForStartApi(lambda, skipPull, profile) {
16196
+ async function resolveContainerImageForStartApi(lambda, skipPull, profile, options) {
16183
16197
  const logger = getLogger();
16184
16198
  const localBuild = await resolveLocalBuildPlan$1(lambda);
16185
- if (localBuild) return { imageRef: await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, { architecture: lambda.architecture }) };
16199
+ if (localBuild) return { imageRef: await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, {
16200
+ architecture: lambda.architecture,
16201
+ ...options?.noBuild !== void 0 && { noBuild: options.noBuild }
16202
+ }) };
16186
16203
  if (!parseEcrUri(lambda.imageUri)) throw new Error(`Container Lambda '${lambda.logicalId}' has no matching asset in cdk.out, and Code.ImageUri '${lambda.imageUri}' is not an ECR URI ${getEmbedConfig().binaryName} can authenticate against. Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.`);
16187
- logger.info(`No matching cdk.out asset for ${lambda.imageUri}; falling back to ECR pull (same-acct/region only)...`);
16204
+ logger.info(`No matching cdk.out asset for ${lambda.imageUri}; falling back to ECR pull...`);
16188
16205
  return { imageRef: await pullEcrImage(lambda.imageUri, {
16189
16206
  skipPull,
16190
- ...profile !== void 0 && { profile }
16207
+ ...profile !== void 0 && { profile },
16208
+ ...options?.ecrRoleArn !== void 0 && { ecrRoleArn: options.ecrRoleArn },
16209
+ ...options?.region !== void 0 && { region: options.region }
16191
16210
  }) };
16192
16211
  }
16193
16212
  /**
@@ -16925,7 +16944,7 @@ function createLocalStartApiCommand(opts = {}) {
16925
16944
  * `--help` clusters. Chainable: returns `cmd`.
16926
16945
  */
16927
16946
  function addStartApiSpecificOptions(cmd) {
16928
- return cmd.addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--all-stacks", "Serve every stack's API in a multi-stack app (each API on its own port) instead of erroring out. Mutually exclusive with a positional target subset, --stack, and an explicit --from-cfn-stack <name>; the bare --from-cfn-stack flag stays compatible (binds each routed stack to its own CFn stack).").default(false)).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when the CDK app's source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <targets...> argument instead. Accepts a SINGLE identifier; for a subset pass multiple positional targets. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). 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("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in Lambda 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 per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. 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.")).addOption(new Option("--mtls-truststore <path>", `PEM-encoded CA bundle for client-certificate verification (mutual TLS). When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced on the Lambda event under requestContext.identity.clientCert (REST v1) / requestContext.authentication.clientCert (HTTP API v2). Must be set together with --mtls-cert + --mtls-key; partial flag sets are rejected. Generate a CA + server + client cert for local dev: openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj "/CN=${getEmbedConfig().resourceNamePrefix}-ca" -days 365; openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj "/CN=localhost"; openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj "/CN=client"; openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...`)).addOption(new Option("--mtls-cert <path>", "PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. Must be set together with --mtls-truststore + --mtls-key.")).addOption(new Option("--mtls-key <path>", "PEM-encoded server private key matching --mtls-cert. Must be set together with --mtls-truststore + --mtls-cert.")).addOption(new Option("--strict-sigv4", "Opt-in: DENY AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id — e.g. a federated / Cognito Identity Pool / cross-account signer — OR no local AWS credentials configured) instead of the default warn-and-pass. DEFAULT off: cdk-local warn-and-passes unverifiable IAM requests with a placeholder principalId so local dev exercises app logic without reproducing an auth boundary it cannot fully emulate. OAC-fronted Function URLs always warn-and-pass regardless.").default(false));
16947
+ return cmd.addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--all-stacks", "Serve every stack's API in a multi-stack app (each API on its own port) instead of erroring out. Mutually exclusive with a positional target subset, --stack, and an explicit --from-cfn-stack <name>; the bare --from-cfn-stack flag stays compatible (binds each routed stack to its own CFn stack).").default(false)).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--no-build", "Skip docker build on every container Lambda local-asset build (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("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--debug-port <port>", "Alias of --debug-port-base for parity with `cdkl invoke --debug-port`. Reserves a contiguous --debug-port range (one per Lambda) starting at <port>.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries on the container-Lambda ECR-pull fallback. 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("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when the CDK app's source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <targets...> argument instead. Accepts a SINGLE identifier; for a subset pass multiple positional targets. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). 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("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in Lambda 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 per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. 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.")).addOption(new Option("--mtls-truststore <path>", `PEM-encoded CA bundle for client-certificate verification (mutual TLS). When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced on the Lambda event under requestContext.identity.clientCert (REST v1) / requestContext.authentication.clientCert (HTTP API v2). Must be set together with --mtls-cert + --mtls-key; partial flag sets are rejected. Generate a CA + server + client cert for local dev: openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj "/CN=${getEmbedConfig().resourceNamePrefix}-ca" -days 365; openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj "/CN=localhost"; openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj "/CN=client"; openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...`)).addOption(new Option("--mtls-cert <path>", "PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. Must be set together with --mtls-truststore + --mtls-key.")).addOption(new Option("--mtls-key <path>", "PEM-encoded server private key matching --mtls-cert. Must be set together with --mtls-truststore + --mtls-cert.")).addOption(new Option("--strict-sigv4", "Opt-in: DENY AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id — e.g. a federated / Cognito Identity Pool / cross-account signer — OR no local AWS credentials configured) instead of the default warn-and-pass. DEFAULT off: cdk-local warn-and-passes unverifiable IAM requests with a placeholder principalId so local dev exercises app logic without reproducing an auth boundary it cannot fully emulate. OAC-fronted Function URLs always warn-and-pass regardless.").default(false));
16929
16948
  }
16930
16949
 
16931
16950
  //#endregion
@@ -17554,7 +17573,7 @@ function createLocalInvokeCommand(opts = {}) {
17554
17573
  * `--help` clusters. Chainable: returns `cmd`.
17555
17574
  */
17556
17575
  function addInvokeSpecificOptions(cmd) {
17557
- 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."));
17576
+ 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\"}, \"Parameters\": {...}})")).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 IP the host uses to bind the RIE port to. Must be a numeric IP (Docker rejects hostnames here). Defaults to 127.0.0.1.").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."));
17558
17577
  }
17559
17578
 
17560
17579
  //#endregion
@@ -17616,7 +17635,7 @@ async function buildAgentCoreCodeImage(options) {
17616
17635
  "--file",
17617
17636
  dockerfilePath,
17618
17637
  options.sourceDir
17619
- ]);
17638
+ ], { progressLabel: `Building agent image (runtime=${options.runtime})` });
17620
17639
  } catch (err) {
17621
17640
  const stderr = err.stderr?.trim();
17622
17641
  throw new LocalInvokeBuildError(`docker build failed for AgentCore code artifact (${options.sourceDir})${stderr ? `: ${stderr}` : ""}`);
@@ -19219,7 +19238,7 @@ function createLocalInvokeAgentCoreCommand(opts = {}) {
19219
19238
  * in three `--help` clusters. Chainable: returns `cmd`.
19220
19239
  */
19221
19240
  function addInvokeAgentCoreSpecificOptions(cmd) {
19222
- 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("--session-id <id>", "AgentCore runtime session id header value (default: a random UUID)")).addOption(new Option("--ws", "Stream over the HTTP-protocol agent's bidirectional /ws WebSocket endpoint (on 8080) instead of POST /invocations: send --event as the first frame and print every received frame to stdout until the agent closes. Ignored for an MCP runtime.").default(false)).addOption(new Option("--ws-interactive", "REPL mode for --ws: after the initial --event frame, read additional frames from stdin (one frame per line, trailing newline stripped) and send each as a text frame until stdin EOFs (Ctrl-D) or the agent closes. Only meaningful with --ws.").default(false)).addOption(new Option("--bearer-token <jwt>", "Bearer JWT to present when the runtime declares a customJwtAuthorizer. Verified against the runtime OIDC discovery URL (signature / issuer / expiry / audience) before the container starts, then forwarded to /invocations as Authorization: Bearer <jwt>.")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign the /invocations POST with AWS SigV4 (service bedrock-agentcore) using the resolved credentials, matching the cloud default when the runtime declares no customJwtAuthorizer. Opt-in: default unsigned. Mutually exclusive with --bearer-token; ignored on a JWT-protected runtime.").default(false)).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64)").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host to bind the agent port to").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Per-request timeout in milliseconds. Applied to POST /invocations, POST /mcp, and the /ws open-to-close window. Raise this for long-running agent calls that exceed the default.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container so the agent runs with the deployed role. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's RoleArn when it is a literal ARN; (3) `--no-assume-role` opts out. Off by default — the developer's shell credentials are forwarded unchanged.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. 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. Bare form uses the resolved stack name; pass an explicit value when the CFn stack name differs.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
19241
+ 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\"}, \"Parameters\": {...}})")).addOption(new Option("--session-id <id>", "AgentCore runtime session id header value (default: a random UUID)")).addOption(new Option("--ws", "Stream over the HTTP-protocol agent's bidirectional /ws WebSocket endpoint (on 8080) instead of POST /invocations: send --event as the first frame and print every received frame to stdout until the agent closes. Ignored for an MCP runtime.").default(false)).addOption(new Option("--ws-interactive", "REPL mode for --ws: after the initial --event frame, read additional frames from stdin (one frame per line, trailing newline stripped) and send each as a text frame until stdin EOFs (Ctrl-D) or the agent closes. Only meaningful with --ws.").default(false)).addOption(new Option("--bearer-token <jwt>", "Bearer JWT this command SUPPLIES (the supplier role) when the runtime declares a customJwtAuthorizer — `cdkl invoke-agentcore` is the local-dev client making the outbound call, so it always presents this token to its own invocation. Verified against the runtime's OIDC discovery URL (signature / issuer / expiry / audience) before the container starts, then forwarded to /invocations as Authorization: Bearer <jwt>. Contrast with `cdkl start-alb --bearer-token`, where the role is reversed — the ALB front-door RECEIVES inbound requests and injects this token only as a default when an inbound request has none (default-when-missing).")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign the /invocations POST with AWS SigV4 (service bedrock-agentcore) using the resolved credentials, matching the cloud default when the runtime declares no customJwtAuthorizer. Opt-in: default unsigned. Mutually exclusive with --bearer-token; ignored on a JWT-protected runtime.").default(false)).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64). Defaults to linux/arm64 because the cloud AgentCore Runtime requires arm64. Override to linux/amd64 only when iterating against an amd64 dev container locally; note the image will not run on the cloud runtime as-is.").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host IP the host uses to bind the agent port to. Must be a numeric IP (Docker rejects hostnames here). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Per-request timeout in milliseconds. Applied to POST /invocations, POST /mcp, and the /ws open-to-close window. Raise this for long-running agent calls that exceed the default.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container so the agent runs with the deployed role. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's RoleArn when it is a literal ARN; (3) `--no-assume-role` opts out. Off by default — the developer's shell credentials are forwarded unchanged.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. 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. Bare form uses the resolved stack name; pass an explicit value when the CFn stack name differs.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
19223
19242
  }
19224
19243
 
19225
19244
  //#endregion
@@ -20130,10 +20149,19 @@ async function prepareOneImage(task, container, options) {
20130
20149
  else if (entries.length === 1) asset = entries[0][1];
20131
20150
  if (!asset) throw new EcsTaskRunnerError(`Container '${container.name}' references a CDK asset image but no matching entry was found in cdk.out. Re-synthesize the CDK app and retry.`);
20132
20151
  const tag = `${getEmbedConfig().resourceNamePrefix}-run-task-${(image.assetHash ?? "single").slice(0, 16)}`;
20152
+ if (options.skipBuild === true) {
20153
+ const logger = getLogger().child("ecs-runner");
20154
+ if (image.assetHash === void 0) throw new LocalInvokeBuildError(`Cannot honor --no-build for ECS container '${container.name}': the asset has no stable assetHash (synthetic tag '${tag}' may collide with unrelated prior runs). Drop --no-build, or re-synthesize with an explicit assetHash on the ContainerImage.fromAsset call.`);
20155
+ logger.info(`Skipping docker build (--no-build) for container '${container.name}'; verifying ${tag} is in local registry...`);
20156
+ if (!await isImageInLocalCache(tag)) throw new LocalInvokeBuildError(`image '${tag}' not in local registry and --no-build is set (ECS container '${container.name}'); remove --no-build or run \`docker build\` manually first.`);
20157
+ logger.debug(`Local tag ${tag} is cached; skipping build for container '${container.name}'.`);
20158
+ return tag;
20159
+ }
20133
20160
  const actualTag = await buildDockerImage(asset, cdkOutDir, {
20134
20161
  tag,
20135
20162
  ...options.platformOverride !== void 0 && { platform: options.platformOverride },
20136
- wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`)
20163
+ wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`),
20164
+ progressLabel: `Building container image for '${container.name}'`
20137
20165
  });
20138
20166
  if (actualTag !== tag) try {
20139
20167
  await runDockerStreaming([
@@ -20356,419 +20384,85 @@ function shellJoin(parts) {
20356
20384
  }
20357
20385
 
20358
20386
  //#endregion
20359
- //#region src/cli/commands/local-run-task.ts
20360
- /**
20361
- * `cdkl run-task <target>` Phase 1 of the ECS local-execution
20362
- * trilogy. Synthesizes the CDK app, locates the target
20363
- * `AWS::ECS::TaskDefinition`, stands up a per-task docker network with
20364
- * the AWS-published `amazon-ecs-local-container-endpoints` sidecar, and
20365
- * starts every container in `dependsOn` order. The essential
20366
- * container's exit code drives the CLI's exit.
20367
- */
20368
- async function localRunTaskCommand(target, options, extraStateProviders) {
20369
- const logger = getLogger();
20370
- if (options.verbose) logger.setLevel("debug");
20371
- const state = createEcsRunState();
20372
- let sigintHandler;
20373
- let sigintCount = 0;
20374
- let stateProvider;
20375
- let profileCredsFile;
20376
- let cleanupPromise;
20377
- const cleanup = async () => {
20378
- if (!cleanupPromise) cleanupPromise = (async () => {
20379
- try {
20380
- await cleanupEcsRun(state, { keepRunning: options.keepRunning });
20381
- } catch (err) {
20382
- getLogger().debug(`cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
20383
- }
20384
- if (profileCredsFile) try {
20385
- await profileCredsFile.dispose();
20386
- } catch (err) {
20387
- getLogger().debug(`Failed to remove profile credentials tmpdir ${profileCredsFile.hostPath}: ${err instanceof Error ? err.message : String(err)}`);
20388
- }
20389
- })();
20390
- await cleanupPromise;
20391
- };
20392
- try {
20393
- await applyRoleArnIfSet({
20394
- roleArn: options.roleArn,
20395
- region: options.region,
20396
- profile: options.profile
20397
- });
20398
- await ensureDockerAvailable();
20399
- const appCmd = resolveApp(options.app);
20400
- if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
20401
- logger.info("Synthesizing CDK app...");
20402
- const synthesizer = new Synthesizer();
20403
- const context = parseContextOptions(options.context);
20404
- const synthOpts = {
20405
- app: appCmd,
20406
- output: options.output,
20407
- ...options.region && { region: options.region },
20408
- ...options.profile && { profile: options.profile },
20409
- ...Object.keys(context).length > 0 && { context }
20410
- };
20411
- const { stacks } = await synthesizer.synthesize(synthOpts);
20412
- const resolvedTarget = await resolveSingleTarget(target, {
20413
- entries: listTargets(stacks).ecsTaskDefinitions,
20414
- message: "Select an ECS task definition to run",
20415
- noun: "ECS task definitions",
20416
- onMissing: () => new CdkLocalError(`${getEmbedConfig().cliName} run-task requires a <target> (an ECS task definition display path or logical ID). Run \`${getEmbedConfig().cliName} list\` to see them, or run it in a TTY to pick interactively.`, "LOCAL_RUN_TASK_TARGET_REQUIRED")
20417
- });
20418
- const candidate = pickCandidateStack$1(parseEcsTarget(resolvedTarget).stackPattern, stacks);
20419
- stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
20420
- const imageContext = await buildEcsImageResolutionContext$1(candidate, stateProvider, options);
20421
- const task = resolveEcsTaskTarget(resolvedTarget, stacks, imageContext);
20422
- logger.info(`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`);
20423
- const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack);
20424
- if (stateProvider && taskNeeds.needsCrossStackResolver) {
20425
- const consumerRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? task.stack.region ?? "us-east-1";
20426
- const resolver = await stateProvider.buildCrossStackResolver(consumerRegion);
20427
- if (resolver) await applyCrossStackResolverToTask(task, {
20428
- resources: imageContext?.stateResources ?? {},
20429
- ...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
20430
- ...imageContext?.stateParameters && { parameters: imageContext.stateParameters },
20431
- ...imageContext?.stateSensitiveParameters?.length && { sensitiveParameters: new Set(imageContext.stateSensitiveParameters) },
20432
- consumerRegion,
20433
- crossStackResolver: resolver
20434
- });
20435
- } else if (!stateProvider && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against deployed state.");
20436
- sigintHandler = () => {
20437
- sigintCount += 1;
20438
- if (sigintCount >= 2) {
20439
- process.stderr.write("Force-exit on second ^C; container cleanup skipped.\n");
20440
- process.exit(130);
20441
- }
20442
- logger.info("Stopping task...");
20443
- cleanup().then(() => process.exit(130));
20444
- };
20445
- process.on("SIGINT", sigintHandler);
20446
- let assumedCredentials;
20447
- let resolvedRoleArn;
20448
- if (options.assumeTaskRole === true) {
20449
- if (!task.taskRoleArn) throw new Error(`--assume-task-role passed without an ARN but the task definition has no resolvable TaskRoleArn. Either the task definition does not set TaskRoleArn, or it points at a resource ${getEmbedConfig().binaryName} cannot resolve to an IAM Role at synth time. Pass the ARN explicitly: --assume-task-role <arn>`);
20450
- resolvedRoleArn = await resolvePlaceholderAccount$1(task.taskRoleArn, options.region, options.profile);
20451
- assumedCredentials = await assumeTaskRole$1(resolvedRoleArn, options.region, options.profile);
20452
- } else if (typeof options.assumeTaskRole === "string") {
20453
- resolvedRoleArn = options.assumeTaskRole;
20454
- assumedCredentials = await assumeTaskRole$1(resolvedRoleArn, options.region, options.profile);
20455
- }
20456
- const sidecarCredentials = await resolveSidecarCredentials(options, assumedCredentials);
20457
- if (options.profile && sidecarCredentials && !assumedCredentials) profileCredsFile = await writeProfileCredentialsFile(options.profile, sidecarCredentials);
20458
- const envOverrides = readEnvOverridesFile$1(options.envVars);
20459
- const runOpts = {
20460
- cluster: options.cluster,
20461
- containerHost: options.containerHost,
20462
- skipPull: options.pull === false,
20463
- keepRunning: options.keepRunning,
20464
- detach: options.detach
20465
- };
20466
- if (envOverrides) runOpts.envOverrides = envOverrides;
20467
- if (sidecarCredentials) runOpts.taskCredentials = sidecarCredentials;
20468
- if (resolvedRoleArn) runOpts.taskRoleArn = resolvedRoleArn;
20469
- if (options.platform) runOpts.platformOverride = options.platform;
20470
- if (options.region) runOpts.region = options.region;
20471
- if (options.ecrRoleArn) runOpts.ecrRoleArn = options.ecrRoleArn;
20472
- if (options.profile) runOpts.profile = options.profile;
20473
- const hostPortOverrides = parseHostPortOverrides(options.hostPort);
20474
- if (Object.keys(hostPortOverrides).length > 0) runOpts.hostPortOverrides = hostPortOverrides;
20475
- if (profileCredsFile) runOpts.profileCredentialsFile = {
20476
- hostPath: profileCredsFile.hostPath,
20477
- containerPath: profileCredsFile.containerPath,
20478
- profileName: profileCredsFile.profileName
20479
- };
20480
- const result = await runEcsTask(task, runOpts, state);
20481
- if (options.detach) {
20482
- logger.info(`Task containers started in detached mode; ${getEmbedConfig().binaryName} is exiting.`);
20483
- logger.info(`Use 'docker ps --filter network=${result.state.network?.networkName ?? "<network>"}' to inspect; tear down with 'docker rm -f' and 'docker network rm'.`);
20484
- sigintCount = 99;
20485
- return;
20486
- }
20487
- if (result.essentialContainerName) logger.info(`Essential container '${result.essentialContainerName}' exited with code ${result.exitCode}.`);
20488
- if (result.exitCode !== 0) process.exitCode = result.exitCode;
20489
- } finally {
20490
- if (sigintHandler) process.off("SIGINT", sigintHandler);
20491
- if (stateProvider) stateProvider.dispose();
20492
- if (!options.detach) await cleanup();
20493
- }
20494
- }
20495
- /**
20496
- * If `arn` contains the `${AWS::AccountId}` placeholder emitted by the
20497
- * resolver for inline same-stack IAM Roles, substitute the live caller
20498
- * account via STS `GetCallerIdentity`. Otherwise pass through unchanged.
20499
- */
20500
- async function resolvePlaceholderAccount$1(arn, region, profile) {
20501
- if (!arn.includes("${AWS::AccountId}")) return arn;
20502
- const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
20503
- const sts = new STSClient(buildStsClientConfig({
20504
- region,
20505
- profile
20506
- }));
20507
- try {
20508
- const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
20509
- if (!account) throw new Error(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'. Pass the ARN explicitly: --assume-task-role <arn>`);
20510
- return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
20511
- } finally {
20512
- sts.destroy();
20387
+ //#region src/local/ecs-service-resolver.ts
20388
+ function resolveEcsServiceTarget(target, stacks, context, options) {
20389
+ if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
20390
+ const parsed = parseEcsTarget(target);
20391
+ const stack = pickStack$1(parsed, stacks);
20392
+ const resources = stack.template.Resources ?? {};
20393
+ let serviceLogicalId;
20394
+ let serviceResource;
20395
+ if (parsed.isPath) {
20396
+ const index = buildCdkPathIndex(stack.template);
20397
+ const services = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId: l }) => resources[l]?.Type === "AWS::ECS::Service");
20398
+ if (services.length === 0) throw notFoundError(target, stack, resources, parsed);
20399
+ if (services.length > 1) throw new EcsTaskResolutionError(`Target '${target}' matches ${services.length} ECS services in ${stack.stackName}: ` + services.map((s) => s.logicalId).join(", ") + ". Refine the path or use the stack:LogicalId form.");
20400
+ serviceLogicalId = services[0].logicalId;
20401
+ serviceResource = resources[serviceLogicalId];
20402
+ } else {
20403
+ serviceResource = resources[parsed.pathOrId];
20404
+ if (!serviceResource) throw notFoundError(target, stack, resources, parsed);
20405
+ serviceLogicalId = parsed.pathOrId;
20513
20406
  }
20407
+ if (!serviceLogicalId || !serviceResource) throw notFoundError(target, stack, resources, parsed);
20408
+ if (serviceResource.Type === "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource '${serviceLogicalId}' in ${stack.stackName} is an ECS TaskDefinition, not a Service. Use \`${getEmbedConfig().cliName} run-task\` for one-shot tasks; \`${getEmbedConfig().cliName} start-service\` is Service-only.`);
20409
+ if (serviceResource.Type !== "AWS::ECS::Service") throw new EcsTaskResolutionError(`Resource '${serviceLogicalId}' in ${stack.stackName} is ${serviceResource.Type}, not an AWS::ECS::Service.`);
20410
+ return extractServiceProperties(stack, serviceLogicalId, serviceResource, stacks, context, options);
20514
20411
  }
20515
20412
  /**
20516
- * Assume `roleArn` and return temp credentials.
20413
+ * Pure-functional extraction from the synth resource. Exposed for unit
20414
+ * testing the per-field resolution rules (DesiredCount default, missing
20415
+ * TaskDefinition, intrinsic shapes).
20517
20416
  */
20518
- async function assumeTaskRole$1(roleArn, region, profile) {
20519
- const { STSClient, AssumeRoleCommand } = await import("@aws-sdk/client-sts");
20520
- const sts = new STSClient(buildStsClientConfig({
20521
- region,
20522
- profile
20523
- }));
20524
- try {
20525
- const creds = (await sts.send(new AssumeRoleCommand({
20526
- RoleArn: roleArn,
20527
- RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-run-task-${Date.now()}`,
20528
- DurationSeconds: 3600
20529
- }))).Credentials;
20530
- if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) throw new Error(`AssumeRole(${roleArn}) returned no usable credentials.`);
20531
- return {
20532
- accessKeyId: creds.AccessKeyId,
20533
- secretAccessKey: creds.SecretAccessKey,
20534
- sessionToken: creds.SessionToken
20535
- };
20536
- } finally {
20537
- sts.destroy();
20417
+ function extractServiceProperties(stack, serviceLogicalId, resource, stacks, context, options) {
20418
+ const props = resource.Properties ?? {};
20419
+ const warnings = [];
20420
+ const taskDefRef = props["TaskDefinition"];
20421
+ if (taskDefRef === void 0 || taskDefRef === null) throw new EcsTaskResolutionError(`ECS Service '${serviceLogicalId}' in ${stack.stackName} has no TaskDefinition property.`);
20422
+ const taskDefLogicalId = resolveTaskDefinitionReference(taskDefRef, stack, serviceLogicalId);
20423
+ const task = resolveEcsTaskTarget(`${stack.stackName}:${taskDefLogicalId}`, stacks, context);
20424
+ const desiredCount = parseDesiredCount(props["DesiredCount"], serviceLogicalId);
20425
+ const healthCheckGracePeriodSeconds = parseHealthCheckGrace(props["HealthCheckGracePeriodSeconds"], serviceLogicalId);
20426
+ const serviceName = parseServiceName(props["ServiceName"], serviceLogicalId);
20427
+ const serviceDisplayName = deriveServiceDisplayName(props["ServiceName"], serviceLogicalId, resource.Metadata);
20428
+ if (!options?.suppressLoadBalancerWarning && Array.isArray(props["LoadBalancers"]) && props["LoadBalancers"].length > 0) {
20429
+ const { cliName } = getEmbedConfig();
20430
+ warnings.push(`ECS Service '${serviceLogicalId}' declares LoadBalancers, but \`${cliName} start-service\` runs the replicas only; no local listener fronts them. Reach the containers via their published ports, or run \`${cliName} start-alb <Stack>/<Alb>\` to boot the same replicas behind a local front-door that round-robins the listener rules.`);
20538
20431
  }
20432
+ const serviceConnect = extractServiceConnect(props["ServiceConnectConfiguration"], task);
20433
+ const out = {
20434
+ stack,
20435
+ serviceLogicalId,
20436
+ resource,
20437
+ serviceName,
20438
+ serviceDisplayName,
20439
+ desiredCount,
20440
+ healthCheckGracePeriodSeconds,
20441
+ task,
20442
+ serviceRegistries: extractServiceRegistries(props["ServiceRegistries"], serviceLogicalId, warnings),
20443
+ warnings
20444
+ };
20445
+ if (serviceConnect) out.serviceConnect = serviceConnect;
20446
+ return out;
20539
20447
  }
20540
20448
  /**
20541
- * Build the substitution context the ECS task resolver consumes.
20542
- * Returns `undefined` when no container's `Image` field needs
20543
- * substitution — the resolver behaves as before in that case.
20544
- */
20545
- async function buildEcsImageResolutionContext$1(candidate, stateProvider, options) {
20546
- const logger = getLogger();
20547
- if (!candidate) return void 0;
20548
- const needs = detectEcsImageResolutionNeeds(candidate);
20549
- if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution) return;
20550
- const ctx = {};
20551
- const wantsPseudoForEnvOrSecret = !!stateProvider && needs.needsEnvOrSecretSubstitution;
20552
- if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
20553
- const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
20554
- if (!region) logger.warn(`Resolver references \${AWS::Region} but ${getEmbedConfig().binaryName} could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.`);
20555
- let accountId;
20556
- try {
20557
- accountId = await resolveCallerAccountId$1(region, options.profile);
20558
- } catch (err) {
20559
- logger.warn(`Resolver needs \${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. Substitution will be skipped; affected env / secret entries will be dropped with per-key warnings.`);
20560
- }
20561
- const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
20562
- ctx.pseudoParameters = {
20563
- ...accountId !== void 0 && { accountId },
20564
- ...region !== void 0 && { region },
20565
- ...partitionAndSuffix && {
20566
- partition: partitionAndSuffix.partition,
20567
- urlSuffix: partitionAndSuffix.urlSuffix
20568
- }
20569
- };
20570
- }
20571
- const wantsState = needs.needsStateResources || needs.needsEnvOrSecretSubstitution;
20572
- if (stateProvider && wantsState) {
20573
- const loaded = await stateProvider.load(candidate.stackName, candidate.region);
20574
- if (loaded) ctx.stateResources = loaded.resources;
20575
- else {
20576
- const loadError = stateProvider.getLastLoadError?.();
20577
- if (loadError) ctx.stateLoadFailureMessage = loadError;
20578
- }
20579
- if (needs.needsEnvOrSecretSubstitution && stateProvider.resolveTemplateSsmParameters) {
20580
- const ssmParameters = await stateProvider.resolveTemplateSsmParameters(candidate.template);
20581
- if (Object.keys(ssmParameters.values).length > 0) ctx.stateParameters = ssmParameters.values;
20582
- if (ssmParameters.secureStringLogicalIds.length > 0) ctx.stateSensitiveParameters = ssmParameters.secureStringLogicalIds;
20583
- }
20584
- } else if (!stateProvider && needs.needsStateResources) logger.warn("Container Image references a same-stack AWS::ECR::Repository. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute the deployed repository URI. Otherwise the resolver will surface its existing error.");
20585
- else if (!stateProvider && needs.needsEnvOrSecretSubstitution) logger.warn("Container Environment / Secrets entries contain CloudFormation intrinsics (Ref / Fn::GetAtt / Fn::Sub / Fn::Join). Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against deployed state. Without a state source these entries are dropped (per-key warnings will follow).");
20586
- return ctx;
20587
- }
20588
- function pickCandidateStack$1(stackPattern, stacks) {
20589
- if (stackPattern === null) {
20590
- if (stacks.length === 1) return stacks[0];
20591
- return;
20592
- }
20593
- const matched = matchStacks(stacks, [stackPattern]);
20594
- if (matched.length === 1) return matched[0];
20595
- }
20596
- async function resolveCallerAccountId$1(region, profile) {
20597
- const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
20598
- const sts = new STSClient(buildStsClientConfig({
20599
- region,
20600
- profile
20601
- }));
20602
- try {
20603
- return (await sts.send(new GetCallerIdentityCommand({}))).Account;
20604
- } finally {
20605
- sts.destroy();
20606
- }
20607
- }
20608
- /**
20609
- * Read the `--env-vars` JSON file using the same SAM-style shape as
20610
- * `cdkl invoke --env-vars`: top-level keys are container names, with
20611
- * `Parameters` reserved for global entries.
20612
- */
20613
- function readEnvOverridesFile$1(filePath) {
20614
- if (!filePath) return void 0;
20615
- let raw;
20616
- try {
20617
- raw = readFileSync(filePath, "utf-8");
20618
- } catch (err) {
20619
- throw new Error(`Failed to read --env-vars file '${filePath}': ${err instanceof Error ? err.message : String(err)}`);
20620
- }
20621
- let parsed;
20622
- try {
20623
- parsed = JSON.parse(raw);
20624
- } catch (err) {
20625
- throw new Error(`Failed to parse --env-vars file '${filePath}' as JSON: ${err instanceof Error ? err.message : String(err)}`);
20626
- }
20627
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`--env-vars file '${filePath}' must contain a JSON object at the top level.`);
20628
- return parsed;
20629
- }
20630
- /**
20631
- * Pick the credentials forwarded to the AWS-published
20632
- * `amazon-ecs-local-container-endpoints` sidecar. Precedence:
20633
- * 1. `--assume-task-role <arn>` (or bare `--assume-task-role` against
20634
- * a resolvable `TaskRoleArn`) → STS-assumed temp creds. Highest
20635
- * priority — when the user opted in to IAM emulation, those creds
20636
- * drive the sidecar regardless of `--profile`.
20637
- * 2. `--profile <p>` → resolved via {@link resolveProfileCredentials}
20638
- * (the SDK's default credential provider chain — SSO / IAM
20639
- * Identity Center / fromIni / role-assumption). NEW in this PR.
20640
- * 3. Neither set → `undefined`; the sidecar runs with its own
20641
- * default credential chain (typically empty inside a fresh
20642
- * container — user containers will get 4xx from the credentials
20643
- * endpoint, mimicking IAM-misconfigured prod).
20644
- *
20645
- * Extracted as an exported helper so a unit test can exercise every
20646
- * branch without having to mock the full Synth + Docker + AWS pipeline
20647
- * (the strategy used for the Lambda container path).
20648
- */
20649
- async function resolveSidecarCredentials(options, assumedCredentials) {
20650
- if (assumedCredentials) return assumedCredentials;
20651
- if (options.profile) return resolveProfileCredentials(options.profile);
20652
- }
20653
- function createLocalRunTaskCommand(opts = {}) {
20654
- setEmbedConfig(opts.embedConfig);
20655
- const cmd = new Command("run-task").description("Run an AWS::ECS::TaskDefinition locally — pulls/builds images, sets up a per-task docker network with the AWS-published metadata-endpoints sidecar, and starts every container in dependsOn order. Target accepts a CDK display path (MyStack/MyService/TaskDef) or stack-qualified logical ID (MyStack:MyServiceTaskDefXYZ1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick the task definition from a list.").argument("[target]", "CDK display path or stack-qualified logical ID of the AWS::ECS::TaskDefinition to run (omit to pick interactively in a TTY)").action(withErrorHandling(async (target, options) => {
20656
- await localRunTaskCommand(target, options, opts.extraStateProviders);
20657
- }));
20658
- addRunTaskSpecificOptions(cmd);
20659
- [
20660
- ...commonOptions(),
20661
- ...appOptions(),
20662
- ...contextOptions
20663
- ].forEach((opt) => cmd.addOption(opt));
20664
- cmd.addOption(regionOption);
20665
- return cmd;
20666
- }
20667
- /**
20668
- * Register the option block that `cdkl run-task` adds on top of the shared
20669
- * common / app / context option helpers. Shared between `cdkl run-task` and
20670
- * any host CLI (e.g. cdkd's `local run-task`) that wraps the single-task
20671
- * ECS local runner, so adding or renaming a `run-task`-only flag here
20672
- * propagates to every embedder without duplicate `.addOption(...)` blocks.
20673
- *
20674
- * Calling order only affects `--help` presentation (Commander parses
20675
- * insertion-order-independent). The host-CLI convention is host-specific
20676
- * options first, then this helper, then the shared common / app / context
20677
- * options — host flags / run-task flags / common flags grouped in three
20678
- * `--help` clusters. Chainable: returns `cmd`.
20679
- *
20680
- * NOTE: `run-task` does NOT compose with {@link addCommonEcsServiceOptions}
20681
- * even though many flags overlap. The two ECS surfaces (single-task vs
20682
- * multi-replica service) have intentionally divergent defaults
20683
- * (`run-task` has no `--max-tasks` / `--restart-policy`; `start-service`
20684
- * / `start-alb` have no `--host-port` / `--keep-running` / `--detach`),
20685
- * and folding `run-task` into the service common block would mutate the
20686
- * surface non-trivially. Each command keeps its own helper.
20687
- */
20688
- function addRunTaskSpecificOptions(cmd) {
20689
- 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."));
20690
- }
20691
-
20692
- //#endregion
20693
- //#region src/local/ecs-service-resolver.ts
20694
- function resolveEcsServiceTarget(target, stacks, context, options) {
20695
- if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
20696
- const parsed = parseEcsTarget(target);
20697
- const stack = pickStack$1(parsed, stacks);
20698
- const resources = stack.template.Resources ?? {};
20699
- let serviceLogicalId;
20700
- let serviceResource;
20701
- if (parsed.isPath) {
20702
- const index = buildCdkPathIndex(stack.template);
20703
- const services = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId: l }) => resources[l]?.Type === "AWS::ECS::Service");
20704
- if (services.length === 0) throw notFoundError(target, stack, resources, parsed);
20705
- if (services.length > 1) throw new EcsTaskResolutionError(`Target '${target}' matches ${services.length} ECS services in ${stack.stackName}: ` + services.map((s) => s.logicalId).join(", ") + ". Refine the path or use the stack:LogicalId form.");
20706
- serviceLogicalId = services[0].logicalId;
20707
- serviceResource = resources[serviceLogicalId];
20708
- } else {
20709
- serviceResource = resources[parsed.pathOrId];
20710
- if (!serviceResource) throw notFoundError(target, stack, resources, parsed);
20711
- serviceLogicalId = parsed.pathOrId;
20712
- }
20713
- if (!serviceLogicalId || !serviceResource) throw notFoundError(target, stack, resources, parsed);
20714
- if (serviceResource.Type === "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource '${serviceLogicalId}' in ${stack.stackName} is an ECS TaskDefinition, not a Service. Use \`${getEmbedConfig().cliName} run-task\` for one-shot tasks; \`${getEmbedConfig().cliName} start-service\` is Service-only.`);
20715
- if (serviceResource.Type !== "AWS::ECS::Service") throw new EcsTaskResolutionError(`Resource '${serviceLogicalId}' in ${stack.stackName} is ${serviceResource.Type}, not an AWS::ECS::Service.`);
20716
- return extractServiceProperties(stack, serviceLogicalId, serviceResource, stacks, context, options);
20717
- }
20718
- /**
20719
- * Pure-functional extraction from the synth resource. Exposed for unit
20720
- * testing the per-field resolution rules (DesiredCount default, missing
20721
- * TaskDefinition, intrinsic shapes).
20722
- */
20723
- function extractServiceProperties(stack, serviceLogicalId, resource, stacks, context, options) {
20724
- const props = resource.Properties ?? {};
20725
- const warnings = [];
20726
- const taskDefRef = props["TaskDefinition"];
20727
- if (taskDefRef === void 0 || taskDefRef === null) throw new EcsTaskResolutionError(`ECS Service '${serviceLogicalId}' in ${stack.stackName} has no TaskDefinition property.`);
20728
- const taskDefLogicalId = resolveTaskDefinitionReference(taskDefRef, stack, serviceLogicalId);
20729
- const task = resolveEcsTaskTarget(`${stack.stackName}:${taskDefLogicalId}`, stacks, context);
20730
- const desiredCount = parseDesiredCount(props["DesiredCount"], serviceLogicalId);
20731
- const healthCheckGracePeriodSeconds = parseHealthCheckGrace(props["HealthCheckGracePeriodSeconds"], serviceLogicalId);
20732
- const serviceName = parseServiceName(props["ServiceName"], serviceLogicalId);
20733
- const serviceDisplayName = deriveServiceDisplayName(props["ServiceName"], serviceLogicalId, resource.Metadata);
20734
- if (!options?.suppressLoadBalancerWarning && Array.isArray(props["LoadBalancers"]) && props["LoadBalancers"].length > 0) {
20735
- const { cliName } = getEmbedConfig();
20736
- warnings.push(`ECS Service '${serviceLogicalId}' declares LoadBalancers, but \`${cliName} start-service\` runs the replicas only; no local listener fronts them. Reach the containers via their published ports, or run \`${cliName} start-alb <Stack>/<Alb>\` to boot the same replicas behind a local front-door that round-robins the listener rules.`);
20737
- }
20738
- const serviceConnect = extractServiceConnect(props["ServiceConnectConfiguration"], task);
20739
- const out = {
20740
- stack,
20741
- serviceLogicalId,
20742
- resource,
20743
- serviceName,
20744
- serviceDisplayName,
20745
- desiredCount,
20746
- healthCheckGracePeriodSeconds,
20747
- task,
20748
- serviceRegistries: extractServiceRegistries(props["ServiceRegistries"], serviceLogicalId, warnings),
20749
- warnings
20750
- };
20751
- if (serviceConnect) out.serviceConnect = serviceConnect;
20752
- return out;
20753
- }
20754
- /**
20755
- * Parse `ServiceConnectConfiguration` against the producer TaskDef.
20756
- * Returns `undefined` when the block is missing OR `Enabled: false`.
20757
- *
20758
- * Reject conditions (surface as resolver-time errors so the user sees
20759
- * them BEFORE the docker network is created):
20760
- * - `Namespace` is not a literal string. CDK 2.x always emits a
20761
- * literal string here (verified 2026-05-22); cross-stack /
20762
- * intrinsic shapes are out of scope.
20763
- * - `Services[].PortName` doesn't match any of the TaskDef's
20764
- * `ContainerDefinitions[].PortMappings[].Name` entries.
20765
- *
20766
- * Note on `clientAliases[]` shape: each ClientAlias can declare a
20767
- * `DnsName` (the bare short-name peers connect to, e.g. `orders`) AND
20768
- * a `Port` (the listening port the alias maps to inside the consumer).
20769
- * cdk-local surfaces both verbatim; the registry / `--add-host` overlay
20770
- * publishes each `DnsName` as a bare alias pointing at the same IP as
20771
- * the canonical fqdn.
20449
+ * Parse `ServiceConnectConfiguration` against the producer TaskDef.
20450
+ * Returns `undefined` when the block is missing OR `Enabled: false`.
20451
+ *
20452
+ * Reject conditions (surface as resolver-time errors so the user sees
20453
+ * them BEFORE the docker network is created):
20454
+ * - `Namespace` is not a literal string. CDK 2.x always emits a
20455
+ * literal string here (verified 2026-05-22); cross-stack /
20456
+ * intrinsic shapes are out of scope.
20457
+ * - `Services[].PortName` doesn't match any of the TaskDef's
20458
+ * `ContainerDefinitions[].PortMappings[].Name` entries.
20459
+ *
20460
+ * Note on `clientAliases[]` shape: each ClientAlias can declare a
20461
+ * `DnsName` (the bare short-name peers connect to, e.g. `orders`) AND
20462
+ * a `Port` (the listening port the alias maps to inside the consumer).
20463
+ * cdk-local surfaces both verbatim; the registry / `--add-host` overlay
20464
+ * publishes each `DnsName` as a bare alias pointing at the same IP as
20465
+ * the canonical fqdn.
20772
20466
  */
20773
20467
  function extractServiceConnect(raw, task) {
20774
20468
  if (!raw || typeof raw !== "object") return void 0;
@@ -21846,7 +21540,7 @@ async function softReloadReplica(args) {
21846
21540
  const defaultDockerInspectWorkdirImpl = async (containerId) => {
21847
21541
  const { execFile } = await import("node:child_process");
21848
21542
  const { promisify } = await import("node:util");
21849
- const { getDockerCmd } = await import("./docker-cmd-voNPrcRh.js").then((n) => n.t);
21543
+ const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
21850
21544
  const { stdout } = await promisify(execFile)(getDockerCmd(), [
21851
21545
  "inspect",
21852
21546
  "--format",
@@ -21865,7 +21559,7 @@ let dockerInspectWorkdirImpl = defaultDockerInspectWorkdirImpl;
21865
21559
  const defaultDockerCpImpl = async (src, dst) => {
21866
21560
  const { execFile } = await import("node:child_process");
21867
21561
  const { promisify } = await import("node:util");
21868
- const { getDockerCmd } = await import("./docker-cmd-voNPrcRh.js").then((n) => n.t);
21562
+ const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
21869
21563
  await promisify(execFile)(getDockerCmd(), [
21870
21564
  "cp",
21871
21565
  src,
@@ -21880,7 +21574,7 @@ let dockerCpImpl = defaultDockerCpImpl;
21880
21574
  const defaultDockerRestartImpl = async (containerId) => {
21881
21575
  const { execFile } = await import("node:child_process");
21882
21576
  const { promisify } = await import("node:util");
21883
- const { getDockerCmd } = await import("./docker-cmd-voNPrcRh.js").then((n) => n.t);
21577
+ const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
21884
21578
  await promisify(execFile)(getDockerCmd(), ["restart", containerId]);
21885
21579
  };
21886
21580
  let dockerRestartImpl = defaultDockerRestartImpl;
@@ -21920,7 +21614,7 @@ async function disconnectOldFromSharedNetwork(oldInstance) {
21920
21614
  const defaultDockerNetworkDisconnectImpl = async (networkName, containerId) => {
21921
21615
  const { execFile } = await import("node:child_process");
21922
21616
  const { promisify } = await import("node:util");
21923
- const { getDockerCmd } = await import("./docker-cmd-voNPrcRh.js").then((n) => n.t);
21617
+ const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
21924
21618
  await promisify(execFile)(getDockerCmd(), [
21925
21619
  "network",
21926
21620
  "disconnect",
@@ -22097,7 +21791,7 @@ function pickEssentialContainerId(instance, service) {
22097
21791
  const defaultWaitForExitImpl = async (containerId) => {
22098
21792
  const { execFile } = await import("node:child_process");
22099
21793
  const { promisify } = await import("node:util");
22100
- const { getDockerCmd } = await import("./docker-cmd-voNPrcRh.js").then((n) => n.t);
21794
+ const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
22101
21795
  const { stdout } = await promisify(execFile)(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
22102
21796
  const code = parseInt(stdout.trim(), 10);
22103
21797
  return Number.isFinite(code) ? code : -1;
@@ -22117,7 +21811,7 @@ const EXIT_LOG_TAIL_LINES = 50;
22117
21811
  const defaultReadContainerLogsImpl = async (containerId) => {
22118
21812
  const { execFile } = await import("node:child_process");
22119
21813
  const { promisify } = await import("node:util");
22120
- const { getDockerCmd } = await import("./docker-cmd-voNPrcRh.js").then((n) => n.t);
21814
+ const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
22121
21815
  const { stdout, stderr } = await promisify(execFile)(getDockerCmd(), [
22122
21816
  "logs",
22123
21817
  "--tail",
@@ -24965,7 +24659,8 @@ async function runImageOverrideBuilds(overrides) {
24965
24659
  try {
24966
24660
  await runDockerStreaming(args, {
24967
24661
  cwd: entry.contextDir,
24968
- env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" }
24662
+ env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" },
24663
+ progressLabel: `Building override image for '${target}'`
24969
24664
  });
24970
24665
  } catch (err) {
24971
24666
  const e = err;
@@ -25401,7 +25096,7 @@ async function reloadAllServices(args) {
25401
25096
  async function loadAssetContextForTarget(args) {
25402
25097
  const { target, controller, stacks, cdkOutDir, assetLoader, logger } = args;
25403
25098
  const parsed = parseEcsTarget(target);
25404
- const candidate = pickCandidateStack(parsed.stackPattern, stacks);
25099
+ const candidate = pickCandidateStack$1(parsed.stackPattern, stacks);
25405
25100
  if (!candidate) {
25406
25101
  logger.debug(`loadAssetContext: stack pattern '${parsed.stackPattern}' from target '${target}' not in the assembly. Classifier will see no asset context (rebuild).`);
25407
25102
  return;
@@ -25465,7 +25160,7 @@ async function loadAssetContextForTarget(args) {
25465
25160
  */
25466
25161
  async function rollOneTarget(args) {
25467
25162
  const { controller, newBoot, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, verdict, imageOverrideTag, logger } = args;
25468
- const candidate = pickCandidateStack(parseEcsTarget(newBoot.target).stackPattern, stacks);
25163
+ const candidate = pickCandidateStack$1(parseEcsTarget(newBoot.target).stackPattern, stacks);
25469
25164
  const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
25470
25165
  try {
25471
25166
  let resolved;
@@ -25517,7 +25212,7 @@ async function rollOneTarget(args) {
25517
25212
  }
25518
25213
  }
25519
25214
  async function bootOneTarget(boot, runState, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag) {
25520
- const candidate = pickCandidateStack(parseEcsTarget(boot.target).stackPattern, stacks);
25215
+ const candidate = pickCandidateStack$1(parseEcsTarget(boot.target).stackPattern, stacks);
25521
25216
  const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
25522
25217
  try {
25523
25218
  return await runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag);
@@ -25556,7 +25251,7 @@ async function resolveServiceAndRunnerOpts(boot, stacks, options, discovery, ski
25556
25251
  const logger = getLogger();
25557
25252
  const target = boot.target;
25558
25253
  const quiet = opts.quiet === true;
25559
- const imageContext = await buildEcsImageResolutionContext(target, stacks, options, stateProvider);
25254
+ const imageContext = await buildEcsImageResolutionContext$1(target, stacks, options, stateProvider);
25560
25255
  const service = resolveEcsServiceTarget(target, stacks, imageContext, { suppressLoadBalancerWarning });
25561
25256
  if (!quiet) logger.info(`Target: ${service.stack.stackName}/${service.serviceLogicalId} (service=${service.serviceName}, desiredCount=${service.desiredCount}, task=${service.task.taskDefinitionLogicalId})`);
25562
25257
  if (!quiet && service.serviceConnect) logger.info(`Service Connect: namespace='${service.serviceConnect.namespaceName}', ${service.serviceConnect.services.length} service(s) registered for peer discovery.`);
@@ -25577,21 +25272,23 @@ async function resolveServiceAndRunnerOpts(boot, stacks, options, discovery, ski
25577
25272
  await applyCrossStackResolverToTask(service.task, subContext);
25578
25273
  }
25579
25274
  } else if (!stateProvider && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against deployed state.");
25275
+ const effectiveAssumeRole = resolveEcsAssumeRoleOption(options);
25580
25276
  let assumedCredentials;
25581
25277
  let resolvedRoleArn;
25582
- if (options.assumeTaskRole === true) {
25583
- if (!service.task.taskRoleArn) throw new LocalStartServiceError(`--assume-task-role passed without an ARN but service '${service.serviceLogicalId}' has no resolvable TaskRoleArn. Pass the ARN explicitly: --assume-task-role <arn>`);
25584
- resolvedRoleArn = await resolvePlaceholderAccount(service.task.taskRoleArn, options.region, options.profile);
25585
- assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region, options.profile);
25586
- } else if (typeof options.assumeTaskRole === "string") {
25587
- resolvedRoleArn = options.assumeTaskRole;
25588
- assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region, options.profile);
25589
- }
25590
- const envOverrides = readEnvOverridesFile(options.envVars);
25278
+ if (effectiveAssumeRole === true) {
25279
+ if (!service.task.taskRoleArn) throw new LocalStartServiceError(`--assume-role passed without an ARN but service '${service.serviceLogicalId}' has no resolvable TaskRoleArn. Pass the ARN explicitly: --assume-role <arn>`);
25280
+ resolvedRoleArn = await resolvePlaceholderAccount$1(service.task.taskRoleArn, options.region, options.profile);
25281
+ assumedCredentials = await assumeTaskRole$1(resolvedRoleArn, options.region, options.profile);
25282
+ } else if (typeof effectiveAssumeRole === "string") {
25283
+ resolvedRoleArn = effectiveAssumeRole;
25284
+ assumedCredentials = await assumeTaskRole$1(resolvedRoleArn, options.region, options.profile);
25285
+ }
25286
+ const envOverrides = readEnvOverridesFile$1(options.envVars);
25591
25287
  const taskOpts = {
25592
25288
  cluster: options.cluster,
25593
25289
  containerHost: options.containerHost,
25594
25290
  skipPull,
25291
+ skipBuild: options.build === false,
25595
25292
  keepRunning: false,
25596
25293
  detach: true
25597
25294
  };
@@ -25882,7 +25579,7 @@ function describeRuleActionForSummary(action) {
25882
25579
  function describeForwardTargetForSummary(t) {
25883
25580
  return t.kind === "lambda" ? `<Lambda: ${t.lambda.logicalId}>` : `<ECS: ${t.serviceTarget}>`;
25884
25581
  }
25885
- async function resolvePlaceholderAccount(arn, region, profile) {
25582
+ async function resolvePlaceholderAccount$1(arn, region, profile) {
25886
25583
  if (!arn.includes("${AWS::AccountId}")) return arn;
25887
25584
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
25888
25585
  const sts = new STSClient(buildStsClientConfig({
@@ -25897,7 +25594,7 @@ async function resolvePlaceholderAccount(arn, region, profile) {
25897
25594
  sts.destroy();
25898
25595
  }
25899
25596
  }
25900
- async function assumeTaskRole(roleArn, region, profile) {
25597
+ async function assumeTaskRole$1(roleArn, region, profile) {
25901
25598
  const { STSClient, AssumeRoleCommand } = await import("@aws-sdk/client-sts");
25902
25599
  const sts = new STSClient(buildStsClientConfig({
25903
25600
  region,
@@ -25924,9 +25621,9 @@ async function assumeTaskRole(roleArn, region, profile) {
25924
25621
  * site-level binding test that locks the `--from-cfn-stack` SSM-parameter
25925
25622
  * resolution call (issue #94).
25926
25623
  */
25927
- async function buildEcsImageResolutionContext(target, stacks, options, stateProvider) {
25624
+ async function buildEcsImageResolutionContext$1(target, stacks, options, stateProvider) {
25928
25625
  const logger = getLogger();
25929
- const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
25626
+ const candidate = pickCandidateStack$1(parseEcsTarget(target).stackPattern, stacks);
25930
25627
  if (!candidate) return void 0;
25931
25628
  const needs = detectEcsImageResolutionNeeds(candidate);
25932
25629
  if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution) return;
@@ -25937,7 +25634,7 @@ async function buildEcsImageResolutionContext(target, stacks, options, stateProv
25937
25634
  if (!region) logger.warn(`Resolver references \${AWS::Region} but ${getEmbedConfig().binaryName} could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.`);
25938
25635
  let accountId;
25939
25636
  try {
25940
- accountId = await resolveCallerAccountId(region, options.profile);
25637
+ accountId = await resolveCallerAccountId$1(region, options.profile);
25941
25638
  } catch (err) {
25942
25639
  logger.warn(`Resolver needs \${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. Substitution will be skipped; affected env / secret entries will be dropped with per-key warnings.`);
25943
25640
  }
@@ -25968,7 +25665,7 @@ async function buildEcsImageResolutionContext(target, stacks, options, stateProv
25968
25665
  else if (!stateProvider && needs.needsEnvOrSecretSubstitution) logger.warn("Container Environment / Secrets entries contain CloudFormation intrinsics. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against the deployed state.");
25969
25666
  return ctx;
25970
25667
  }
25971
- function pickCandidateStack(stackPattern, stacks) {
25668
+ function pickCandidateStack$1(stackPattern, stacks) {
25972
25669
  if (stackPattern === null) {
25973
25670
  if (stacks.length === 1) return stacks[0];
25974
25671
  return;
@@ -25976,7 +25673,7 @@ function pickCandidateStack(stackPattern, stacks) {
25976
25673
  const matched = matchStacks(stacks, [stackPattern]);
25977
25674
  if (matched.length === 1) return matched[0];
25978
25675
  }
25979
- async function resolveCallerAccountId(region, profile) {
25676
+ async function resolveCallerAccountId$1(region, profile) {
25980
25677
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
25981
25678
  const sts = new STSClient(buildStsClientConfig({
25982
25679
  region,
@@ -25988,7 +25685,7 @@ async function resolveCallerAccountId(region, profile) {
25988
25685
  sts.destroy();
25989
25686
  }
25990
25687
  }
25991
- function readEnvOverridesFile(filePath) {
25688
+ function readEnvOverridesFile$1(filePath) {
25992
25689
  if (!filePath) return void 0;
25993
25690
  let raw;
25994
25691
  try {
@@ -26133,12 +25830,12 @@ async function resolveAndBuildImageOverrides(args) {
26133
25830
  const { perTarget, stacks, options, extraStateProviders, logger } = args;
26134
25831
  const resolvedForPeek = [];
26135
25832
  for (const target of perTarget) {
26136
- const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
25833
+ const candidate = pickCandidateStack$1(parseEcsTarget(target).stackPattern, stacks);
26137
25834
  const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
26138
25835
  let imageContext;
26139
25836
  let service;
26140
25837
  try {
26141
- imageContext = await buildEcsImageResolutionContext(target, stacks, options, stateProvider);
25838
+ imageContext = await buildEcsImageResolutionContext$1(target, stacks, options, stateProvider);
26142
25839
  service = resolveEcsServiceTarget(target, stacks, imageContext, { suppressLoadBalancerWarning: true });
26143
25840
  } catch (err) {
26144
25841
  logger.debug(`--image-override peek failed for '${target}': ${err instanceof Error ? err.message : String(err)}.`);
@@ -26197,13 +25894,74 @@ function enforceStrictOverrides(strict, uncoveredPinnedTargets) {
26197
25894
  throw new LocalStartServiceError(`--strict-overrides set, but ${uncoveredPinnedTargets.length} pinned target(s) remain uncovered: ${uncoveredPinnedTargets.join(", ")}. Pass --image-override <service>=<dockerfile> for each, drop --strict-overrides, or drop --from-cfn-stack to iterate on local CDK assets.`);
26198
25895
  }
26199
25896
  /**
25897
+ * Build the canonical `--cluster <name>` Option for every ECS surface.
25898
+ *
25899
+ * Shared between `cdkl run-task` and the `start-service` / `start-alb`
25900
+ * pair so the default (the active embed config's `resourceNamePrefix`)
25901
+ * is defined exactly once — issue #249 / C12. Building it as a fresh
25902
+ * Option per call (not a module-level const) is load-bearing: the
25903
+ * embed config is host-installed at command-factory time, and a
25904
+ * module-level Option would freeze the host-default prefix at
25905
+ * import time.
25906
+ */
25907
+ function ecsClusterOption() {
25908
+ return new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default(getEmbedConfig().resourceNamePrefix);
25909
+ }
25910
+ /**
25911
+ * Add the `--assume-task-role` flag plus its `--assume-role` alias to
25912
+ * an ECS command. Issue #249 / C6 — every non-ECS command calls the
25913
+ * same concept `--assume-role`, so the ECS surface accepts the
25914
+ * cross-command name as a NON-BREAKING alias. Precedence when both
25915
+ * are passed: `--assume-role` (new) wins. {@link resolveEcsAssumeRoleOption}
25916
+ * computes the effective value and fires a one-time deprecation warn
25917
+ * when only the legacy `--assume-task-role` is set.
25918
+ *
25919
+ * Breaking unification of all three parsers (`invoke`, `start-api`,
25920
+ * ECS) is tracked separately under issue #256.
25921
+ */
25922
+ function addEcsAssumeRoleOptions(cmd) {
25923
+ return cmd.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. DEPRECATED alias of `--assume-role`; both forms work, but `--assume-role` is the cross-command name and will become the only form in a future release.")).addOption(new Option("--assume-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. Cross-command alias matching the same flag on `invoke` / `invoke-agentcore` / `start-api`; supersedes the older `--assume-task-role` (which still works)."));
25924
+ }
25925
+ /**
25926
+ * Module-level latch for the `--assume-task-role` deprecation warn.
25927
+ * Per-process single-fire, so a `--watch` reload that re-enters the
25928
+ * resolver on every source-change firing does not spam the user.
25929
+ *
25930
+ * Test-only `__resetAssumeTaskRoleDeprecationLatch` lets the unit suite
25931
+ * drive the latch through repeated invocations.
25932
+ */
25933
+ let assumeTaskRoleDeprecationWarned = false;
25934
+ /**
25935
+ * Collapse `--assume-task-role` (legacy) and `--assume-role` (new) into
25936
+ * a single effective value. When BOTH are set, `--assume-role` wins
25937
+ * (matches Commander's "later wins" default for repeatable flags, and
25938
+ * gives the cross-command name priority). When ONLY `--assume-task-role`
25939
+ * is set, fires a **per-process one-time** deprecation warn naming the
25940
+ * replacement — the latch above prevents `--watch` reloads from
25941
+ * re-emitting the warn on every source-change firing.
25942
+ *
25943
+ * Issue #249 / C6.
25944
+ */
25945
+ function resolveEcsAssumeRoleOption(options) {
25946
+ if (options.assumeRole !== void 0) return options.assumeRole;
25947
+ if (options.assumeTaskRole !== void 0) {
25948
+ if (!assumeTaskRoleDeprecationWarned) {
25949
+ assumeTaskRoleDeprecationWarned = true;
25950
+ getLogger().warn("--assume-task-role is deprecated; use --assume-role instead. Both forms continue to work, but --assume-role is the cross-command name.");
25951
+ }
25952
+ return options.assumeTaskRole;
25953
+ }
25954
+ }
25955
+ /**
26200
25956
  * Add the CLI options shared by both ECS-service commands (`start-service` and
26201
25957
  * `start-alb`) to a command. The command-specific argument / description and
26202
25958
  * the one unique option (`--host-port` vs `--lb-port`) are added by each
26203
25959
  * factory.
26204
25960
  */
26205
25961
  function addCommonEcsServiceOptions(cmd) {
26206
- 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.")).addOption(new Option("--shadow-ready-timeout <ms>", `Issue #265 — milliseconds the \`--watch\` rolling primitive waits for a shadow replica's first essential-container port to accept a TCP connection before swapping Cloud Map + front-door registrations off the old replica. Defaults to 60000 (60s) which covers realistic prod-shaped Node app cold-starts (TS->JS compile, full node_modules graph, framework boot, DB pool init). Bump higher when the reload log surfaces "TCP probe <ip>:<port> did not accept within <N>ms" — typical for Java / heavy ORM init / \`--inspect-brk\` attach pauses. Honored by --watch on \`${getEmbedConfig().binaryName} start-service\` and \`${getEmbedConfig().binaryName} start-alb\`. Env var: \`${getEmbedConfig().envPrefix}_SHADOW_READY_TIMEOUT_MS\` (flag wins over env).`).argParser(parseShadowReadyTimeout));
25962
+ cmd.addOption(ecsClusterOption()).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"));
25963
+ addEcsAssumeRoleOptions(cmd);
25964
+ cmd.addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--no-build", "Skip docker build on the local CDK-asset path for every container (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 ECR-pull / public-registry images. Per-container --image-override mappings take precedence (the override tag is used as-is, --no-build does not apply). Compatible with --no-pull.")).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.")).addOption(new Option("--shadow-ready-timeout <ms>", `Issue #265 — milliseconds the \`--watch\` rolling primitive waits for a shadow replica's first essential-container port to accept a TCP connection before swapping Cloud Map + front-door registrations off the old replica. Defaults to 60000 (60s) which covers realistic prod-shaped Node app cold-starts (TS->JS compile, full node_modules graph, framework boot, DB pool init). Bump higher when the reload log surfaces "TCP probe <ip>:<port> did not accept within <N>ms" — typical for Java / heavy ORM init / \`--inspect-brk\` attach pauses. Honored by --watch on \`${getEmbedConfig().binaryName} start-service\` and \`${getEmbedConfig().binaryName} start-alb\`. Env var: \`${getEmbedConfig().envPrefix}_SHADOW_READY_TIMEOUT_MS\` (flag wins over env).`).argParser(parseShadowReadyTimeout));
26207
25965
  [
26208
25966
  ...commonOptions(),
26209
25967
  ...appOptions(),
@@ -26232,6 +25990,345 @@ function addImageOverrideOptions(cmd) {
26232
25990
  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));
26233
25991
  }
26234
25992
 
25993
+ //#endregion
25994
+ //#region src/cli/commands/local-run-task.ts
25995
+ /**
25996
+ * `cdkl run-task <target>` — Phase 1 of the ECS local-execution
25997
+ * trilogy. Synthesizes the CDK app, locates the target
25998
+ * `AWS::ECS::TaskDefinition`, stands up a per-task docker network with
25999
+ * the AWS-published `amazon-ecs-local-container-endpoints` sidecar, and
26000
+ * starts every container in `dependsOn` order. The essential
26001
+ * container's exit code drives the CLI's exit.
26002
+ */
26003
+ async function localRunTaskCommand(target, options, extraStateProviders) {
26004
+ const logger = getLogger();
26005
+ if (options.verbose) logger.setLevel("debug");
26006
+ const state = createEcsRunState();
26007
+ let sigintHandler;
26008
+ let sigintCount = 0;
26009
+ let stateProvider;
26010
+ let profileCredsFile;
26011
+ let cleanupPromise;
26012
+ const cleanup = async () => {
26013
+ if (!cleanupPromise) cleanupPromise = (async () => {
26014
+ try {
26015
+ await cleanupEcsRun(state, { keepRunning: options.keepRunning });
26016
+ } catch (err) {
26017
+ getLogger().debug(`cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
26018
+ }
26019
+ if (profileCredsFile) try {
26020
+ await profileCredsFile.dispose();
26021
+ } catch (err) {
26022
+ getLogger().debug(`Failed to remove profile credentials tmpdir ${profileCredsFile.hostPath}: ${err instanceof Error ? err.message : String(err)}`);
26023
+ }
26024
+ })();
26025
+ await cleanupPromise;
26026
+ };
26027
+ try {
26028
+ await applyRoleArnIfSet({
26029
+ roleArn: options.roleArn,
26030
+ region: options.region,
26031
+ profile: options.profile
26032
+ });
26033
+ await ensureDockerAvailable();
26034
+ const appCmd = resolveApp(options.app);
26035
+ if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
26036
+ logger.info("Synthesizing CDK app...");
26037
+ const synthesizer = new Synthesizer();
26038
+ const context = parseContextOptions(options.context);
26039
+ const synthOpts = {
26040
+ app: appCmd,
26041
+ output: options.output,
26042
+ ...options.region && { region: options.region },
26043
+ ...options.profile && { profile: options.profile },
26044
+ ...Object.keys(context).length > 0 && { context }
26045
+ };
26046
+ const { stacks } = await synthesizer.synthesize(synthOpts);
26047
+ const resolvedTarget = await resolveSingleTarget(target, {
26048
+ entries: listTargets(stacks).ecsTaskDefinitions,
26049
+ message: "Select an ECS task definition to run",
26050
+ noun: "ECS task definitions",
26051
+ onMissing: () => new CdkLocalError(`${getEmbedConfig().cliName} run-task requires a <target> (an ECS task definition display path or logical ID). Run \`${getEmbedConfig().cliName} list\` to see them, or run it in a TTY to pick interactively.`, "LOCAL_RUN_TASK_TARGET_REQUIRED")
26052
+ });
26053
+ const candidate = pickCandidateStack(parseEcsTarget(resolvedTarget).stackPattern, stacks);
26054
+ stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
26055
+ const imageContext = await buildEcsImageResolutionContext(candidate, stateProvider, options);
26056
+ const task = resolveEcsTaskTarget(resolvedTarget, stacks, imageContext);
26057
+ logger.info(`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`);
26058
+ const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack);
26059
+ if (stateProvider && taskNeeds.needsCrossStackResolver) {
26060
+ const consumerRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? task.stack.region ?? "us-east-1";
26061
+ const resolver = await stateProvider.buildCrossStackResolver(consumerRegion);
26062
+ if (resolver) await applyCrossStackResolverToTask(task, {
26063
+ resources: imageContext?.stateResources ?? {},
26064
+ ...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
26065
+ ...imageContext?.stateParameters && { parameters: imageContext.stateParameters },
26066
+ ...imageContext?.stateSensitiveParameters?.length && { sensitiveParameters: new Set(imageContext.stateSensitiveParameters) },
26067
+ consumerRegion,
26068
+ crossStackResolver: resolver
26069
+ });
26070
+ } else if (!stateProvider && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against deployed state.");
26071
+ sigintHandler = () => {
26072
+ sigintCount += 1;
26073
+ if (sigintCount >= 2) {
26074
+ process.stderr.write("Force-exit on second ^C; container cleanup skipped.\n");
26075
+ process.exit(130);
26076
+ }
26077
+ logger.info("Stopping task...");
26078
+ cleanup().then(() => process.exit(130));
26079
+ };
26080
+ process.on("SIGINT", sigintHandler);
26081
+ const effectiveAssumeRole = resolveEcsAssumeRoleOption(options);
26082
+ let assumedCredentials;
26083
+ let resolvedRoleArn;
26084
+ if (effectiveAssumeRole === true) {
26085
+ if (!task.taskRoleArn) throw new Error(`--assume-role passed without an ARN but the task definition has no resolvable TaskRoleArn. Either the task definition does not set TaskRoleArn, or it points at a resource ${getEmbedConfig().binaryName} cannot resolve to an IAM Role at synth time. Pass the ARN explicitly: --assume-role <arn>`);
26086
+ resolvedRoleArn = await resolvePlaceholderAccount(task.taskRoleArn, options.region, options.profile);
26087
+ assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region, options.profile);
26088
+ } else if (typeof effectiveAssumeRole === "string") {
26089
+ resolvedRoleArn = effectiveAssumeRole;
26090
+ assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region, options.profile);
26091
+ }
26092
+ const sidecarCredentials = await resolveSidecarCredentials(options, assumedCredentials);
26093
+ if (options.profile && sidecarCredentials && !assumedCredentials) profileCredsFile = await writeProfileCredentialsFile(options.profile, sidecarCredentials);
26094
+ const envOverrides = readEnvOverridesFile(options.envVars);
26095
+ const runOpts = {
26096
+ cluster: options.cluster,
26097
+ containerHost: options.containerHost,
26098
+ skipPull: options.pull === false,
26099
+ skipBuild: options.build === false,
26100
+ keepRunning: options.keepRunning,
26101
+ detach: options.detach
26102
+ };
26103
+ if (envOverrides) runOpts.envOverrides = envOverrides;
26104
+ if (sidecarCredentials) runOpts.taskCredentials = sidecarCredentials;
26105
+ if (resolvedRoleArn) runOpts.taskRoleArn = resolvedRoleArn;
26106
+ if (options.platform) runOpts.platformOverride = options.platform;
26107
+ if (options.region) runOpts.region = options.region;
26108
+ if (options.ecrRoleArn) runOpts.ecrRoleArn = options.ecrRoleArn;
26109
+ if (options.profile) runOpts.profile = options.profile;
26110
+ const hostPortOverrides = parseHostPortOverrides(options.hostPort);
26111
+ if (Object.keys(hostPortOverrides).length > 0) runOpts.hostPortOverrides = hostPortOverrides;
26112
+ if (profileCredsFile) runOpts.profileCredentialsFile = {
26113
+ hostPath: profileCredsFile.hostPath,
26114
+ containerPath: profileCredsFile.containerPath,
26115
+ profileName: profileCredsFile.profileName
26116
+ };
26117
+ const result = await runEcsTask(task, runOpts, state);
26118
+ if (options.detach) {
26119
+ logger.info(`Task containers started in detached mode; ${getEmbedConfig().binaryName} is exiting.`);
26120
+ logger.info(`Use 'docker ps --filter network=${result.state.network?.networkName ?? "<network>"}' to inspect; tear down with 'docker rm -f' and 'docker network rm'.`);
26121
+ sigintCount = 99;
26122
+ return;
26123
+ }
26124
+ if (result.essentialContainerName) logger.info(`Essential container '${result.essentialContainerName}' exited with code ${result.exitCode}.`);
26125
+ if (result.exitCode !== 0) process.exitCode = result.exitCode;
26126
+ } finally {
26127
+ if (sigintHandler) process.off("SIGINT", sigintHandler);
26128
+ if (stateProvider) stateProvider.dispose();
26129
+ if (!options.detach) await cleanup();
26130
+ }
26131
+ }
26132
+ /**
26133
+ * If `arn` contains the `${AWS::AccountId}` placeholder emitted by the
26134
+ * resolver for inline same-stack IAM Roles, substitute the live caller
26135
+ * account via STS `GetCallerIdentity`. Otherwise pass through unchanged.
26136
+ */
26137
+ async function resolvePlaceholderAccount(arn, region, profile) {
26138
+ if (!arn.includes("${AWS::AccountId}")) return arn;
26139
+ const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
26140
+ const sts = new STSClient(buildStsClientConfig({
26141
+ region,
26142
+ profile
26143
+ }));
26144
+ try {
26145
+ const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
26146
+ if (!account) throw new Error(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'. Pass the ARN explicitly: --assume-task-role <arn>`);
26147
+ return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
26148
+ } finally {
26149
+ sts.destroy();
26150
+ }
26151
+ }
26152
+ /**
26153
+ * Assume `roleArn` and return temp credentials.
26154
+ */
26155
+ async function assumeTaskRole(roleArn, region, profile) {
26156
+ const { STSClient, AssumeRoleCommand } = await import("@aws-sdk/client-sts");
26157
+ const sts = new STSClient(buildStsClientConfig({
26158
+ region,
26159
+ profile
26160
+ }));
26161
+ try {
26162
+ const creds = (await sts.send(new AssumeRoleCommand({
26163
+ RoleArn: roleArn,
26164
+ RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-run-task-${Date.now()}`,
26165
+ DurationSeconds: 3600
26166
+ }))).Credentials;
26167
+ if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) throw new Error(`AssumeRole(${roleArn}) returned no usable credentials.`);
26168
+ return {
26169
+ accessKeyId: creds.AccessKeyId,
26170
+ secretAccessKey: creds.SecretAccessKey,
26171
+ sessionToken: creds.SessionToken
26172
+ };
26173
+ } finally {
26174
+ sts.destroy();
26175
+ }
26176
+ }
26177
+ /**
26178
+ * Build the substitution context the ECS task resolver consumes.
26179
+ * Returns `undefined` when no container's `Image` field needs
26180
+ * substitution — the resolver behaves as before in that case.
26181
+ */
26182
+ async function buildEcsImageResolutionContext(candidate, stateProvider, options) {
26183
+ const logger = getLogger();
26184
+ if (!candidate) return void 0;
26185
+ const needs = detectEcsImageResolutionNeeds(candidate);
26186
+ if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution) return;
26187
+ const ctx = {};
26188
+ const wantsPseudoForEnvOrSecret = !!stateProvider && needs.needsEnvOrSecretSubstitution;
26189
+ if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
26190
+ const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
26191
+ if (!region) logger.warn(`Resolver references \${AWS::Region} but ${getEmbedConfig().binaryName} could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.`);
26192
+ let accountId;
26193
+ try {
26194
+ accountId = await resolveCallerAccountId(region, options.profile);
26195
+ } catch (err) {
26196
+ logger.warn(`Resolver needs \${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. Substitution will be skipped; affected env / secret entries will be dropped with per-key warnings.`);
26197
+ }
26198
+ const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
26199
+ ctx.pseudoParameters = {
26200
+ ...accountId !== void 0 && { accountId },
26201
+ ...region !== void 0 && { region },
26202
+ ...partitionAndSuffix && {
26203
+ partition: partitionAndSuffix.partition,
26204
+ urlSuffix: partitionAndSuffix.urlSuffix
26205
+ }
26206
+ };
26207
+ }
26208
+ const wantsState = needs.needsStateResources || needs.needsEnvOrSecretSubstitution;
26209
+ if (stateProvider && wantsState) {
26210
+ const loaded = await stateProvider.load(candidate.stackName, candidate.region);
26211
+ if (loaded) ctx.stateResources = loaded.resources;
26212
+ else {
26213
+ const loadError = stateProvider.getLastLoadError?.();
26214
+ if (loadError) ctx.stateLoadFailureMessage = loadError;
26215
+ }
26216
+ if (needs.needsEnvOrSecretSubstitution && stateProvider.resolveTemplateSsmParameters) {
26217
+ const ssmParameters = await stateProvider.resolveTemplateSsmParameters(candidate.template);
26218
+ if (Object.keys(ssmParameters.values).length > 0) ctx.stateParameters = ssmParameters.values;
26219
+ if (ssmParameters.secureStringLogicalIds.length > 0) ctx.stateSensitiveParameters = ssmParameters.secureStringLogicalIds;
26220
+ }
26221
+ } else if (!stateProvider && needs.needsStateResources) logger.warn("Container Image references a same-stack AWS::ECR::Repository. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute the deployed repository URI. Otherwise the resolver will surface its existing error.");
26222
+ else if (!stateProvider && needs.needsEnvOrSecretSubstitution) logger.warn("Container Environment / Secrets entries contain CloudFormation intrinsics (Ref / Fn::GetAtt / Fn::Sub / Fn::Join). Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against deployed state. Without a state source these entries are dropped (per-key warnings will follow).");
26223
+ return ctx;
26224
+ }
26225
+ function pickCandidateStack(stackPattern, stacks) {
26226
+ if (stackPattern === null) {
26227
+ if (stacks.length === 1) return stacks[0];
26228
+ return;
26229
+ }
26230
+ const matched = matchStacks(stacks, [stackPattern]);
26231
+ if (matched.length === 1) return matched[0];
26232
+ }
26233
+ async function resolveCallerAccountId(region, profile) {
26234
+ const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
26235
+ const sts = new STSClient(buildStsClientConfig({
26236
+ region,
26237
+ profile
26238
+ }));
26239
+ try {
26240
+ return (await sts.send(new GetCallerIdentityCommand({}))).Account;
26241
+ } finally {
26242
+ sts.destroy();
26243
+ }
26244
+ }
26245
+ /**
26246
+ * Read the `--env-vars` JSON file using the same SAM-style shape as
26247
+ * `cdkl invoke --env-vars`: top-level keys are container names, with
26248
+ * `Parameters` reserved for global entries.
26249
+ */
26250
+ function readEnvOverridesFile(filePath) {
26251
+ if (!filePath) return void 0;
26252
+ let raw;
26253
+ try {
26254
+ raw = readFileSync(filePath, "utf-8");
26255
+ } catch (err) {
26256
+ throw new Error(`Failed to read --env-vars file '${filePath}': ${err instanceof Error ? err.message : String(err)}`);
26257
+ }
26258
+ let parsed;
26259
+ try {
26260
+ parsed = JSON.parse(raw);
26261
+ } catch (err) {
26262
+ throw new Error(`Failed to parse --env-vars file '${filePath}' as JSON: ${err instanceof Error ? err.message : String(err)}`);
26263
+ }
26264
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`--env-vars file '${filePath}' must contain a JSON object at the top level.`);
26265
+ return parsed;
26266
+ }
26267
+ /**
26268
+ * Pick the credentials forwarded to the AWS-published
26269
+ * `amazon-ecs-local-container-endpoints` sidecar. Precedence:
26270
+ * 1. `--assume-task-role <arn>` (or bare `--assume-task-role` against
26271
+ * a resolvable `TaskRoleArn`) → STS-assumed temp creds. Highest
26272
+ * priority — when the user opted in to IAM emulation, those creds
26273
+ * drive the sidecar regardless of `--profile`.
26274
+ * 2. `--profile <p>` → resolved via {@link resolveProfileCredentials}
26275
+ * (the SDK's default credential provider chain — SSO / IAM
26276
+ * Identity Center / fromIni / role-assumption). NEW in this PR.
26277
+ * 3. Neither set → `undefined`; the sidecar runs with its own
26278
+ * default credential chain (typically empty inside a fresh
26279
+ * container — user containers will get 4xx from the credentials
26280
+ * endpoint, mimicking IAM-misconfigured prod).
26281
+ *
26282
+ * Extracted as an exported helper so a unit test can exercise every
26283
+ * branch without having to mock the full Synth + Docker + AWS pipeline
26284
+ * (the strategy used for the Lambda container path).
26285
+ */
26286
+ async function resolveSidecarCredentials(options, assumedCredentials) {
26287
+ if (assumedCredentials) return assumedCredentials;
26288
+ if (options.profile) return resolveProfileCredentials(options.profile);
26289
+ }
26290
+ function createLocalRunTaskCommand(opts = {}) {
26291
+ setEmbedConfig(opts.embedConfig);
26292
+ const cmd = new Command("run-task").description("Run an AWS::ECS::TaskDefinition locally — pulls/builds images, sets up a per-task docker network with the AWS-published metadata-endpoints sidecar, and starts every container in dependsOn order. Target accepts a CDK display path (MyStack/MyService/TaskDef) or stack-qualified logical ID (MyStack:MyServiceTaskDefXYZ1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick the task definition from a list.").argument("[target]", "CDK display path or stack-qualified logical ID of the AWS::ECS::TaskDefinition to run (omit to pick interactively in a TTY)").action(withErrorHandling(async (target, options) => {
26293
+ await localRunTaskCommand(target, options, opts.extraStateProviders);
26294
+ }));
26295
+ addRunTaskSpecificOptions(cmd);
26296
+ [
26297
+ ...commonOptions(),
26298
+ ...appOptions(),
26299
+ ...contextOptions
26300
+ ].forEach((opt) => cmd.addOption(opt));
26301
+ cmd.addOption(regionOption);
26302
+ return cmd;
26303
+ }
26304
+ /**
26305
+ * Register the option block that `cdkl run-task` adds on top of the shared
26306
+ * common / app / context option helpers. Shared between `cdkl run-task` and
26307
+ * any host CLI (e.g. cdkd's `local run-task`) that wraps the single-task
26308
+ * ECS local runner, so adding or renaming a `run-task`-only flag here
26309
+ * propagates to every embedder without duplicate `.addOption(...)` blocks.
26310
+ *
26311
+ * Calling order only affects `--help` presentation (Commander parses
26312
+ * insertion-order-independent). The host-CLI convention is host-specific
26313
+ * options first, then this helper, then the shared common / app / context
26314
+ * options — host flags / run-task flags / common flags grouped in three
26315
+ * `--help` clusters. Chainable: returns `cmd`.
26316
+ *
26317
+ * NOTE: `run-task` does NOT compose with {@link addCommonEcsServiceOptions}
26318
+ * even though many flags overlap. The two ECS surfaces (single-task vs
26319
+ * multi-replica service) have intentionally divergent defaults
26320
+ * (`run-task` has no `--max-tasks` / `--restart-policy`; `start-service`
26321
+ * / `start-alb` have no `--host-port` / `--keep-running` / `--detach`),
26322
+ * and folding `run-task` into the service common block would mutate the
26323
+ * surface non-trivially. Each command keeps its own helper.
26324
+ */
26325
+ function addRunTaskSpecificOptions(cmd) {
26326
+ cmd.addOption(ecsClusterOption()).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."));
26327
+ addEcsAssumeRoleOptions(cmd);
26328
+ cmd.addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--no-build", "Skip docker build on every CDK-asset container (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 ECR-pull / public-registry containers. Compatible with --no-pull.")).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."));
26329
+ return cmd;
26330
+ }
26331
+
26235
26332
  //#endregion
26236
26333
  //#region src/cli/commands/local-start-service.ts
26237
26334
  /**
@@ -27156,7 +27253,7 @@ function createLocalStartAlbCommand(opts = {}) {
27156
27253
  */
27157
27254
  function addAlbSpecificOptions(cmd) {
27158
27255
  addImageOverrideOptions(cmd);
27159
- return cmd.addOption(new Option("--lb-port <listenerPort=hostPort...>", "Bind the local front-door on a specific host port (e.g. 80=8080); repeatable. Default: host port == ALB listener port. Use this on macOS to remap a privileged listener port (< 1024) to a non-privileged host port.")).addOption(new Option("--tls", "Terminate TLS locally for cloud-HTTPS listeners. Default: a cloud-HTTPS listener is served over plain HTTP locally (X-Forwarded-Proto: https is preserved so the upstream app still sees the deployed listener protocol). Implied by --tls-cert / --tls-key. Use this when local-dev cookies need Secure / SameSite=None, when the upstream app inspects TLS metadata, or for mTLS / SNI testing — otherwise plain HTTP is friendlier (no self-signed cert warnings in curl / browser).")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Implies --tls. Must be set together with --tls-key. Pass --tls alone (without --tls-cert / --tls-key) to auto-generate a self-signed cert (cached under $XDG_CACHE_HOME/cdk-local/alb-https/, default ~/.cache/cdk-local/alb-https/); requires openssl on PATH. The deployed Listener Certificates[] are NOT fetched (ACM private keys are not retrievable by design). The auto-generated cert lists DNS:localhost,IP:127.0.0.1 as SubjectAltName, so a client validating a non-loopback --container-host will fail the SAN check — pass --tls-cert / --tls-key with a SAN covering that host instead.")).addOption(new Option("--tls-key <path>", "PEM-encoded server private key matching --tls-cert. Implies --tls. Must be set together with --tls-cert.")).addOption(new Option("--no-verify-auth", "Disable local enforcement of authenticate-cognito / authenticate-oidc actions. Every request is served as if the auth check passed. Useful for local dev where you do not want to mint a Bearer token at all.")).addOption(new Option("--bearer-token <jwt>", "Default Bearer JWT injected as Authorization: Bearer <jwt> when the inbound request has none. Verified against the same JWKS / OIDC discovery URL the deployed ALB would (signature + iss + aud + exp). Local-dev convenience; cookie pass-through (AWSELBAuthSessionCookie-*) also works.")).addOption(new Option("--watch", "Hot-reload: re-synth + per-replica reload of every ECS service behind the ALB when the CDK source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). A per-firing classifier picks the per-replica primitive: source-only edits on interpreted-language handlers (Node/Python/Ruby/shell) take a bind-mount FAST PATH (`docker cp` the new source into each replica + `docker restart`; no rebuild, front-door pool entry unchanged since the IP/port are preserved). Dockerfile / dependency manifest / compiled-language source / ambiguous edits fall through to the rebuild rolling primitive — boot a shadow under a bumped generation suffix, wait for its container port to accept a TCP connection, atomically register it in the front-door pool, then drop the old entry and retire the old container. Either path rolls one replica at a time, so a continuous external request stream against the listener port sees zero connection refusals across the reload. The host front-door (TLS, JWKS cache, Lambda-target containers, listener sockets) stays up across the reload. Lambda target groups behind the ALB are a no-op on reload (the warm RIE container keeps its boot-time image). Off by default; existing replica(s) keep serving when synth fails mid-reload.").default(false));
27256
+ return cmd.addOption(new Option("--lb-port <listenerPort=hostPort...>", "Bind the local front-door on a specific host port (e.g. 80=8080); repeatable. Default: host port == ALB listener port. Use this on macOS to remap a privileged listener port (< 1024) to a non-privileged host port.")).addOption(new Option("--tls", "Terminate TLS locally for cloud-HTTPS listeners. Default: a cloud-HTTPS listener is served over plain HTTP locally (X-Forwarded-Proto: https is preserved so the upstream app still sees the deployed listener protocol). Implied by --tls-cert / --tls-key. Use this when local-dev cookies need Secure / SameSite=None, when the upstream app inspects TLS metadata, or for mTLS / SNI testing — otherwise plain HTTP is friendlier (no self-signed cert warnings in curl / browser).")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Implies --tls. Must be set together with --tls-key. Pass --tls alone (without --tls-cert / --tls-key) to auto-generate a self-signed cert (cached under $XDG_CACHE_HOME/cdk-local/alb-https/, default ~/.cache/cdk-local/alb-https/); requires openssl on PATH. The deployed Listener Certificates[] are NOT fetched (ACM private keys are not retrievable by design). The auto-generated cert lists DNS:localhost,IP:127.0.0.1 as SubjectAltName, so a client validating a non-loopback --container-host will fail the SAN check — pass --tls-cert / --tls-key with a SAN covering that host instead.")).addOption(new Option("--tls-key <path>", "PEM-encoded server private key matching --tls-cert. Implies --tls. Must be set together with --tls-cert.")).addOption(new Option("--no-verify-auth", "Disable local enforcement of authenticate-cognito / authenticate-oidc actions. Every request is served as if the auth check passed. Useful for local dev where you do not want to mint a Bearer token at all.")).addOption(new Option("--bearer-token <jwt>", "Default Bearer JWT this command INJECTS only when an inbound request has none (the default-when-missing role) — `cdkl start-alb` is the local ALB front-door RECEIVING outside-in requests, so this token is the fallback the front-door slots in as Authorization: Bearer <jwt> if the caller did not already supply one. Verified against the same JWKS / OIDC discovery URL the deployed ALB would (signature + iss + aud + exp). Cookie pass-through (AWSELBAuthSessionCookie-*) also bypasses the guard. Contrast with `cdkl invoke-agentcore --bearer-token`, where the role is reversed — that command is the outbound client and ALWAYS presents this token (the supplier).")).addOption(new Option("--watch", "Hot-reload: re-synth + per-replica reload of every ECS service behind the ALB when the CDK source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). A per-firing classifier picks the per-replica primitive: source-only edits on interpreted-language handlers (Node/Python/Ruby/shell) take a bind-mount FAST PATH (`docker cp` the new source into each replica + `docker restart`; no rebuild, front-door pool entry unchanged since the IP/port are preserved). Dockerfile / dependency manifest / compiled-language source / ambiguous edits fall through to the rebuild rolling primitive — boot a shadow under a bumped generation suffix, wait for its container port to accept a TCP connection, atomically register it in the front-door pool, then drop the old entry and retire the old container. Either path rolls one replica at a time, so a continuous external request stream against the listener port sees zero connection refusals across the reload. The host front-door (TLS, JWKS cache, Lambda-target containers, listener sockets) stays up across the reload. Lambda target groups behind the ALB are a no-op on reload (the warm RIE container keeps its boot-time image). Off by default; existing replica(s) keep serving when synth fails mid-reload.").default(false));
27160
27257
  }
27161
27258
 
27162
27259
  //#endregion
@@ -27252,5 +27349,5 @@ function addListSpecificOptions(cmd) {
27252
27349
  }
27253
27350
 
27254
27351
  //#endregion
27255
- export { AGENTCORE_SIGV4_SERVICE as $, formatStateRemedy as $n, evaluateResponseParameters as $t, isLocalCdkAssetImage as A, resolveCfnStackName as An, defaultCredentialsLoader as At, createLocalRunTaskCommand as B, parseSelectionExpressionPath as Bn, invokeRequestAuthorizer as Bt, buildImageOverrideTag as C, substituteEnvVarsFromStateAsync as Cn, filterRoutesByApiIdentifier as Ct, resolveImageOverrides as D, rejectExplicitCfnStackWithMultipleStacks as Dn, startApiServer as Dt, parseImageOverrideFlags as E, isCfnFlagPresent as En, readMtlsMaterialsFromDisk as Et, DEFAULT_SHADOW_READY_TIMEOUT_MS as F, resolveSingleTarget as Fn, verifyJwtAuthorizer as Ft, A2A_CONTAINER_PORT as G, AGENTCORE_AGUI_PROTOCOL as Gn, buildCorsConfigFromCloudFrontChain as Gt, addInvokeAgentCoreSpecificOptions as H, pickRefLogicalId as Hn, attachAuthorizers as Ht, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as I, countTargets as In, verifyJwtViaDiscovery as It, MCP_CONTAINER_PORT as J, AGENTCORE_RUNTIME_TYPE as Jn, matchRoute as Jt, A2A_PATH as K, AGENTCORE_HTTP_PROTOCOL as Kn, isFunctionUrlOacFronted as Kt, setShadowReadyTimeoutMs as L, listTargets as Ln, buildMethodArn as Lt, buildCloudMapIndex as M, collectSsmParameterRefs as Mn, buildJwksUrlFromIssuer as Mt, CloudMapRegistry as N, resolveSsmParameters as Nn, createJwksCache as Nt, runImageOverrideBuilds as O, resolveCfnFallbackRegion as On, resolveSelectionExpression as Ot, classifySourceChange as P, resolveWatchConfig as Pn, verifyCognitoJwt as Pt, parseSseForJsonRpc as Q, derivePseudoParametersFromRegion as Qn, buildRestV1Event as Qt, getContainerNetworkIp as R, discoverWebSocketApis as Rn, computeRequestIdentityHash as Rt, ImageOverrideError as S, substituteEnvVarsFromState as Sn, availableApiIdentifiers as St, mergeForService as T, createLocalStateProvider as Tn, groupRoutesByServer as Tt, createLocalInvokeAgentCoreCommand as U, resolveLambdaArnIntrinsic as Un, applyCorsResponseHeaders as Ut, attachContainerLogStreamer as V, discoverRoutes as Vn, invokeTokenAuthorizer as Vt, invokeAgentCoreWs as W, AGENTCORE_A2A_PROTOCOL as Wn, buildCorsConfigByApiId as Wt, MCP_PROTOCOL_VERSION as X, pickAgentCoreCandidateStack as Xn, applyAuthorizerOverlay as Xt, MCP_PATH as Y, AgentCoreResolutionError as Yn, translateLambdaResponse as Yt, mcpInvokeOnce as Z, resolveAgentCoreTarget as Zn, buildHttpApiV2Event as Zt, buildEcsImageResolutionContext as _, resolveRuntimeFileExtension as _n, createFileWatcher as _t, albStrategy as a, probeHostGatewaySupport as an, SUPPORTED_CODE_RUNTIMES as at, resolveSharedSidecarCredentials as b, substituteAgainstState as bn, materializeLayerFromArn as bt, resolveAlbTarget as c, buildMgmtEndpointEnvUrl as cn, renderCodeDockerfile as ct, addStartServiceSpecificOptions as d, buildConnectEvent as dn, createLocalInvokeCommand as dt, pickResponseTemplate as en, substituteImagePlaceholders as er, signAgentCoreInvocation as et, createLocalStartServiceCommand as f, buildDisconnectEvent as fn, addStartApiSpecificOptions as ft, addImageOverrideOptions as g, resolveRuntimeCodeMountPath as gn, createAuthorizerCache as gt, addCommonEcsServiceOptions as h, buildContainerImage as hn, resolveApiTargetSubset as ht, addAlbSpecificOptions as i, HOST_GATEWAY_MIN_VERSION as in, resolveProfileCredentials as ir, downloadAndExtractS3Bundle as it, listPinnedTargets as j, CfnLocalStateProvider as jn, buildCognitoJwksUrl as jt, describePinnedImageUri as k, resolveCfnRegion as kn, resolveServiceIntegrationParameters as kt, isApplicationLoadBalancer as l, handleConnectionsRequest as ln, toCmdArgv as lt, MAX_TASKS_SUBNET_RANGE_CAP as m, architectureToPlatform as mn, createWatchPredicates as mt, createLocalListCommand as n, tryParseStatus as nn, LocalInvokeBuildError as nr, invokeAgentCore as nt, createLocalStartAlbCommand as o, bufferToBody as on, buildAgentCoreCodeImage as ot, serviceStrategy as p, buildMessageEvent as pn, createLocalStartApiCommand as pt, a2aInvokeOnce as q, AGENTCORE_MCP_PROTOCOL as qn, matchPreflight as qt, formatTargetListing as r, VtlEvaluationError as rn, buildStsClientConfig as rr, waitForAgentCorePing as rt, parseLbPortOverrides as s, ConnectionRegistry as sn, computeCodeImageTag as st, addListSpecificOptions as t, selectIntegrationResponse as tn, tryResolveImageFnJoin as tr, AGENTCORE_SESSION_ID_HEADER as tt, resolveAlbFrontDoor as u, parseConnectionsPath as un, addInvokeSpecificOptions as ut, parseMaxTasks as v, resolveRuntimeImage as vn, attachStageContext as vt, enforceImageOverrideOrphans as w, LocalStateSourceError as wn, filterRoutesByApiIdentifiers as wt, runEcsServiceEmulator as x, substituteAgainstStateAsync as xn, resolveEnvVars as xt, parseRestartPolicy as y, EcsTaskResolutionError as yn, buildStageMap as yt, addRunTaskSpecificOptions as z, discoverWebSocketApisOrThrow as zn, evaluateCachedLambdaPolicy as zt };
27256
- //# sourceMappingURL=local-list-DgwUotOK.js.map
27352
+ export { MCP_PROTOCOL_VERSION as $, pickAgentCoreCandidateStack as $n, applyAuthorizerOverlay as $t, mergeForService as A, rejectExplicitCfnStackWithMultipleStacks as An, startApiServer as At, DEFAULT_SHADOW_READY_TIMEOUT_MS as B, listTargets as Bn, buildMethodArn as Bt, parseRestartPolicy as C, substituteAgainstState as Cn, materializeLayerFromArn as Ct, ImageOverrideError as D, LocalStateSourceError as Dn, filterRoutesByApiIdentifiers as Dt, runEcsServiceEmulator as E, substituteEnvVarsFromStateAsync as En, filterRoutesByApiIdentifier as Et, isLocalCdkAssetImage as F, collectSsmParameterRefs as Fn, buildJwksUrlFromIssuer as Ft, addInvokeAgentCoreSpecificOptions as G, pickRefLogicalId as Gn, attachAuthorizers as Gt, setShadowReadyTimeoutMs as H, discoverWebSocketApisOrThrow as Hn, evaluateCachedLambdaPolicy as Ht, listPinnedTargets as I, resolveSsmParameters as In, createJwksCache as It, A2A_CONTAINER_PORT as J, AGENTCORE_AGUI_PROTOCOL as Jn, buildCorsConfigFromCloudFrontChain as Jt, createLocalInvokeAgentCoreCommand as K, resolveLambdaArnIntrinsic as Kn, applyCorsResponseHeaders as Kt, buildCloudMapIndex as L, resolveWatchConfig as Ln, verifyCognitoJwt as Lt, resolveImageOverrides as M, resolveCfnRegion as Mn, resolveServiceIntegrationParameters as Mt, runImageOverrideBuilds as N, resolveCfnStackName as Nn, defaultCredentialsLoader as Nt, buildImageOverrideTag as O, createLocalStateProvider as On, groupRoutesByServer as Ot, describePinnedImageUri as P, CfnLocalStateProvider as Pn, buildCognitoJwksUrl as Pt, MCP_PATH as Q, AgentCoreResolutionError as Qn, translateLambdaResponse as Qt, CloudMapRegistry as R, resolveSingleTarget as Rn, verifyJwtAuthorizer as Rt, parseMaxTasks as S, EcsTaskResolutionError as Sn, buildStageMap as St, resolveSharedSidecarCredentials as T, substituteEnvVarsFromState as Tn, availableApiIdentifiers as Tt, getContainerNetworkIp as U, parseSelectionExpressionPath as Un, invokeRequestAuthorizer as Ut, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as V, discoverWebSocketApis as Vn, computeRequestIdentityHash as Vt, attachContainerLogStreamer as W, discoverRoutes as Wn, invokeTokenAuthorizer as Wt, a2aInvokeOnce as X, AGENTCORE_MCP_PROTOCOL as Xn, matchPreflight as Xt, A2A_PATH as Y, AGENTCORE_HTTP_PROTOCOL as Yn, isFunctionUrlOacFronted as Yt, MCP_CONTAINER_PORT as Z, AGENTCORE_RUNTIME_TYPE as Zn, matchRoute as Zt, addCommonEcsServiceOptions as _, architectureToPlatform as _n, createWatchPredicates as _t, albStrategy as a, tryParseStatus as an, LocalInvokeBuildError as ar, invokeAgentCore as at, buildEcsImageResolutionContext$1 as b, resolveRuntimeFileExtension as bn, createFileWatcher as bt, resolveAlbTarget as c, probeHostGatewaySupport as cn, SUPPORTED_CODE_RUNTIMES as ct, addStartServiceSpecificOptions as d, buildMgmtEndpointEnvUrl as dn, renderCodeDockerfile as dt, buildHttpApiV2Event as en, resolveAgentCoreTarget as er, mcpInvokeOnce as et, createLocalStartServiceCommand as f, handleConnectionsRequest as fn, toCmdArgv as ft, MAX_TASKS_SUBNET_RANGE_CAP as g, buildMessageEvent as gn, createLocalStartApiCommand as gt, createLocalRunTaskCommand as h, buildDisconnectEvent as hn, addStartApiSpecificOptions as ht, addAlbSpecificOptions as i, selectIntegrationResponse as in, tryResolveImageFnJoin as ir, AGENTCORE_SESSION_ID_HEADER as it, parseImageOverrideFlags as j, resolveCfnFallbackRegion as jn, resolveSelectionExpression as jt, enforceImageOverrideOrphans as k, isCfnFlagPresent as kn, readMtlsMaterialsFromDisk as kt, isApplicationLoadBalancer as l, bufferToBody as ln, buildAgentCoreCodeImage as lt, addRunTaskSpecificOptions as m, buildConnectEvent as mn, createLocalInvokeCommand as mt, createLocalListCommand as n, evaluateResponseParameters as nn, formatStateRemedy as nr, AGENTCORE_SIGV4_SERVICE as nt, createLocalStartAlbCommand as o, VtlEvaluationError as on, buildStsClientConfig as or, waitForAgentCorePing as ot, serviceStrategy as p, parseConnectionsPath as pn, addInvokeSpecificOptions as pt, invokeAgentCoreWs as q, AGENTCORE_A2A_PROTOCOL as qn, buildCorsConfigByApiId as qt, formatTargetListing as r, pickResponseTemplate as rn, substituteImagePlaceholders as rr, signAgentCoreInvocation as rt, parseLbPortOverrides as s, HOST_GATEWAY_MIN_VERSION as sn, resolveProfileCredentials as sr, downloadAndExtractS3Bundle as st, addListSpecificOptions as t, buildRestV1Event as tn, derivePseudoParametersFromRegion as tr, parseSseForJsonRpc as tt, resolveAlbFrontDoor as u, ConnectionRegistry as un, computeCodeImageTag as ut, addEcsAssumeRoleOptions as v, buildContainerImage as vn, resolveApiTargetSubset as vt, resolveEcsAssumeRoleOption as w, substituteAgainstStateAsync as wn, resolveEnvVars as wt, ecsClusterOption as x, resolveRuntimeImage as xn, attachStageContext as xt, addImageOverrideOptions as y, resolveRuntimeCodeMountPath as yn, createAuthorizerCache as yt, classifySourceChange as z, countTargets as zn, verifyJwtViaDiscovery as zt };
27353
+ //# sourceMappingURL=local-list-Dn0Al276.js.map