cdk-local 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { a as runDockerStreaming, i as runDockerForeground, n as formatDockerLoginError, o as spawnStreaming, r as getDockerCmd, s as getLogger } from "./docker-cmd-DTPLXRqh.js";
1
+ import { a as runDockerStreaming, c as getEmbedConfig, i as runDockerForeground, l as setEmbedConfig, n as formatDockerLoginError, o as spawnStreaming, r as getDockerCmd, s as getLogger } from "./docker-cmd-o4ovyAhR.js";
2
2
  import { cpSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import * as path from "node:path";
@@ -7,7 +7,7 @@ import { Command, Option } from "commander";
7
7
  import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
8
8
  import { AssetManifestArtifact } from "@aws-cdk/cloud-assembly-api";
9
9
  import { CdkAppMultiContext, NonInteractiveIoHost, Toolkit } from "@aws-cdk/toolkit-lib";
10
- import { CloudFormationClient, DescribeStackResourcesCommand, DescribeStacksCommand, ListExportsCommand } from "@aws-sdk/client-cloudformation";
10
+ import { CloudFormationClient, DescribeStacksCommand, ListExportsCommand, ListStackResourcesCommand } from "@aws-sdk/client-cloudformation";
11
11
  import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
12
12
  import { Readable } from "node:stream";
13
13
  import { pipeline } from "node:stream/promises";
@@ -40,18 +40,23 @@ function parseContextOptions(contextArgs) {
40
40
  return context;
41
41
  }
42
42
  /**
43
- * Options shared across every cdk-local command.
43
+ * Options shared across every cdk-local command. Built per-call (not a
44
+ * module-level const) so the `--role-arn` env-var hint reflects the active
45
+ * embed config, which the command factory installs before calling this.
44
46
  *
45
47
  * `--region` is intentionally NOT in `commonOptions` — local commands
46
48
  * pick the region from `AWS_REGION` / profile / synthesized stack env.
47
49
  * The deprecated flag below remains for muscle-memory compatibility.
48
50
  */
49
- const commonOptions = [
50
- new Option("--verbose", "Enable verbose logging").default(false),
51
- new Option("--profile <profile>", "AWS profile"),
52
- new Option("--role-arn <arn>", "IAM role ARN to assume for AWS API calls (env: CDKL_ROLE_ARN)"),
53
- new Option("-y, --yes", "Automatically answer interactive prompts with the recommended response").default(false)
54
- ];
51
+ function commonOptions() {
52
+ const { envPrefix } = getEmbedConfig();
53
+ return [
54
+ new Option("--verbose", "Enable verbose logging").default(false),
55
+ new Option("--profile <profile>", "AWS profile"),
56
+ new Option("--role-arn <arn>", `IAM role ARN to assume for AWS API calls (env: ${envPrefix}_ROLE_ARN)`),
57
+ new Option("-y, --yes", "Automatically answer interactive prompts with the recommended response").default(false)
58
+ ];
59
+ }
55
60
  /**
56
61
  * Deprecated `--region` option attached to every command.
57
62
  *
@@ -68,13 +73,18 @@ function warnIfDeprecatedRegion(options) {
68
73
  if (options.region !== void 0) process.stderr.write("Warning: --region is deprecated for this command and has no effect. Use the AWS_REGION environment variable or your AWS profile to override the SDK default region.\n");
69
74
  }
70
75
  /**
71
- * App options.
76
+ * App options. Built per-call (not a module-level const) so the `--app`
77
+ * env-var hint reflects the active embed config.
72
78
  *
73
- * `--app` is optional: falls back to `CDKL_APP` env var, then `cdk.json`
74
- * `app` field. Accepts either a shell command (e.g. `"node app.ts"`) or
75
- * a path to a pre-synthesized cloud assembly directory (e.g. `"cdk.out"`).
79
+ * `--app` is optional: falls back to `${envPrefix}_APP` env var, then
80
+ * `cdk.json` `app` field. Accepts either a shell command (e.g.
81
+ * `"node app.ts"`) or a path to a pre-synthesized cloud assembly directory
82
+ * (e.g. `"cdk.out"`).
76
83
  */
77
- const appOptions = [new Option("-a, --app <command>", "CDK app command (e.g., \"node app.ts\") or path to a pre-synthesized cloud assembly directory. Falls back to cdk.json or CDKL_APP env"), new Option("--output <path>", "Output directory for synthesis").default("cdk.out")];
84
+ function appOptions() {
85
+ const { envPrefix } = getEmbedConfig();
86
+ return [new Option("-a, --app <command>", `CDK app command (e.g., "node app.ts") or path to a pre-synthesized cloud assembly directory. Falls back to cdk.json or ${envPrefix}_APP env`), new Option("--output <path>", "Output directory for synthesis").default("cdk.out")];
87
+ }
78
88
  /**
79
89
  * Context options.
80
90
  */
@@ -125,7 +135,7 @@ function effectiveAssumeRoleArn(logicalId, opt) {
125
135
  * Default session duration is 1 hour.
126
136
  */
127
137
  async function applyRoleArnIfSet(opts) {
128
- const roleArn = opts.roleArn || process.env["CDKL_ROLE_ARN"];
138
+ const roleArn = opts.roleArn || process.env[`${getEmbedConfig().envPrefix}_ROLE_ARN`];
129
139
  if (!roleArn) return;
130
140
  const logger = getLogger().child("role-arn");
131
141
  logger.debug(`Assuming role ${roleArn}...`);
@@ -133,7 +143,7 @@ async function applyRoleArnIfSet(opts) {
133
143
  try {
134
144
  const response = await sts.send(new AssumeRoleCommand({
135
145
  RoleArn: roleArn,
136
- RoleSessionName: `cdkl-${Date.now()}`,
146
+ RoleSessionName: `${getEmbedConfig().binaryName}-${Date.now()}`,
137
147
  DurationSeconds: 3600
138
148
  }));
139
149
  if (!response.Credentials) throw new Error(`AssumeRole returned no credentials for role ${roleArn}`);
@@ -405,7 +415,7 @@ function loadCdkJson() {
405
415
  */
406
416
  function resolveApp(cliApp) {
407
417
  if (cliApp) return cliApp;
408
- const envApp = process.env["CDKL_APP"];
418
+ const envApp = process.env[`${getEmbedConfig().envPrefix}_APP`];
409
419
  if (envApp) return envApp;
410
420
  return loadCdkJson()?.app ?? void 0;
411
421
  }
@@ -507,7 +517,7 @@ function resolveCdkPathToLogicalIds(input, index) {
507
517
  * --from-cfn-stack` (issue #606).
508
518
  *
509
519
  * The shape mirrors the SAM CLI's `sam local invoke --stack-name X`
510
- * behavior: reach into a deployed CFn stack via `DescribeStackResources`
520
+ * behavior: reach into a deployed CFn stack via `ListStackResources`
511
521
  * to look up physical IDs of every same-stack resource, then make those
512
522
  * IDs available to the existing `state-resolver.ts` substitution engine.
513
523
  * This lets `cdkl *` substitute env vars / secrets / images that
@@ -518,11 +528,11 @@ function resolveCdkPathToLogicalIds(input, index) {
518
528
  * Wire-format mapping:
519
529
  *
520
530
  * - `Ref: <LogicalId>` → resolved via the synthetic `ResourceState`
521
- * map built from `DescribeStackResources.StackResources[]` (one
531
+ * map built from `ListStackResources.StackResourceSummaries[]` (one
522
532
  * entry per `(LogicalResourceId, PhysicalResourceId, ResourceType)`
523
533
  * tuple).
524
534
  * - `Fn::GetAtt: [<LogicalId>, <Attr>]` → **warn-and-drop**. CFn's
525
- * `DescribeStackResources` does NOT return per-attribute values
535
+ * `ListStackResources` does NOT return per-attribute values
526
536
  * and the v1 policy (issue #606 recommendation (a)) is to surface
527
537
  * a per-key warn instead of pulling in the full provisioning layer
528
538
  * to call provider-specific describe APIs (e.g. `GetQueueAttributes`
@@ -547,8 +557,14 @@ function resolveCdkPathToLogicalIds(input, index) {
547
557
  *
548
558
  * AWS API contract notes:
549
559
  *
550
- * - `DescribeStackResources` is unpaginated up to 500 resources (CFn's
551
- * hard stack cap). One call suffices for the entire stack.
560
+ * - `ListStackResources` is paginated and returns EVERY resource in
561
+ * the stack across pages. We deliberately do NOT use
562
+ * `DescribeStackResources` here: that API returns only the first
563
+ * 100 resources (AWS-documented hard cap) with no `NextToken`, so a
564
+ * stack with > 100 resources silently loses its tail — every `Ref`
565
+ * to a dropped resource then warn-and-drops its env var. The
566
+ * provider walks `ListStackResources`'s `NextToken` until the page
567
+ * set is exhausted so all resources are mapped regardless of count.
552
568
  * - `DescribeStacks` is unpaginated when called with `StackName`.
553
569
  * - `ListExports` is paginated; the provider walks `NextToken` until
554
570
  * the page set is exhausted.
@@ -592,9 +608,9 @@ var CfnLocalStateProvider = class {
592
608
  const client = this.getClient();
593
609
  let resourceMap;
594
610
  try {
595
- resourceMap = buildResourceStateMap((await client.send(new DescribeStackResourcesCommand({ StackName: this.cfnStackName }))).StackResources ?? []);
611
+ resourceMap = buildResourceStateMap(await fetchAllStackResources(client, this.cfnStackName));
596
612
  } catch (err) {
597
- logger.warn(`${this.label}: DescribeStackResources(${this.cfnStackName}) failed: ${formatAwsErrorForWarn(err)}. Was the stack deployed in region '${this.region}'? Falling back.`);
613
+ logger.warn(`${this.label}: ListStackResources(${this.cfnStackName}) failed: ${formatAwsErrorForWarn(err)}. Was the stack deployed in region '${this.region}'? Falling back.`);
598
614
  return;
599
615
  }
600
616
  let outputs;
@@ -663,7 +679,7 @@ var CfnLocalStateProvider = class {
663
679
  };
664
680
  /**
665
681
  * Build the synthetic per-logical-id resource map from
666
- * `DescribeStackResources` output. Each `ResourceState` carries the
682
+ * `ListStackResources` output. Each `ResourceState` carries the
667
683
  * physical id (covers `Ref`) and the resource type; `attributes` is
668
684
  * left empty per issue #606's (a) recommendation — the warn-and-drop
669
685
  * policy on unresolvable `Fn::GetAtt` is the v1 contract. The other
@@ -702,6 +718,34 @@ function buildOutputsMap(outputs) {
702
718
  return out;
703
719
  }
704
720
  /**
721
+ * Walk `ListStackResources` until every page is consumed and return the
722
+ * full `StackResourceSummary[]`. `ListStackResources` is paginated and
723
+ * returns up to 100 resources per page; the provider must walk every
724
+ * page so stacks with more than 100 resources are mapped completely.
725
+ *
726
+ * This is the reason the provider does not use `DescribeStackResources`:
727
+ * that API caps its response at the first 100 resources with no
728
+ * `NextToken`, so a > 100-resource stack silently loses its tail and the
729
+ * dropped resources' `Ref`s become unresolvable. Exported for unit
730
+ * testing.
731
+ */
732
+ async function fetchAllStackResources(client, stackName) {
733
+ const out = [];
734
+ let nextToken;
735
+ let pages = 0;
736
+ do {
737
+ const resp = await client.send(new ListStackResourcesCommand({
738
+ StackName: stackName,
739
+ ...nextToken !== void 0 && { NextToken: nextToken }
740
+ }));
741
+ for (const summary of resp.StackResourceSummaries ?? []) out.push(summary);
742
+ nextToken = resp.NextToken;
743
+ pages += 1;
744
+ if (pages > 100) throw new Error("ListStackResources pagination exceeded 100 pages — likely a malformed NextToken loop.");
745
+ } while (nextToken !== void 0 && nextToken !== "");
746
+ return out;
747
+ }
748
+ /**
705
749
  * Walk `ListExports` until every page is consumed and return the
706
750
  * `Name -> Value` map. Same-region only (CFn exports are
707
751
  * region-scoped); the caller picks the region at provider
@@ -758,7 +802,7 @@ function formatAwsErrorForWarn(err) {
758
802
  * Built-in flag (always wired):
759
803
  *
760
804
  * - `--from-cfn-stack [<cfn-stack-name>]` — CFn-backed; reads a
761
- * deployed CloudFormation stack via `DescribeStackResources`.
805
+ * deployed CloudFormation stack via `ListStackResources`.
762
806
  *
763
807
  * Host-extensible state sources (via the `extraStateProviders` option):
764
808
  *
@@ -847,7 +891,7 @@ var LocalStateSourceError = class extends Error {
847
891
  function rejectExplicitCfnStackWithMultipleStacks(options, routedStackCount) {
848
892
  if (routedStackCount <= 1) return;
849
893
  if (typeof options.fromCfnStack !== "string") return;
850
- throw new LocalStateSourceError(`--from-cfn-stack <name> cannot be used with multiple routed stacks (got ${routedStackCount}). An explicit CFn stack name applies to one stack only and would silently mismap logical IDs across siblings. Use bare --from-cfn-stack (each stack uses its own name as the CFn stack name) or run one cdkl invocation per stack.`);
894
+ throw new LocalStateSourceError(`--from-cfn-stack <name> cannot be used with multiple routed stacks (got ${routedStackCount}). An explicit CFn stack name applies to one stack only and would silently mismap logical IDs across siblings. Use bare --from-cfn-stack (each stack uses its own name as the CFn stack name) or run one ${getEmbedConfig().binaryName} invocation per stack.`);
851
895
  }
852
896
  /**
853
897
  * Pick and construct the right `LocalStateProvider` for the supplied
@@ -1317,7 +1361,7 @@ function resolveLambdaTarget(target, stacks) {
1317
1361
  const { logicalId, resource } = match;
1318
1362
  if (resource.Type !== "AWS::Lambda::Function") {
1319
1363
  if (resource.Type.startsWith("Custom::")) throw new LocalInvokeResolutionError(`Resource '${logicalId}' in ${stack.stackName} is a Custom Resource (${resource.Type}), not a Lambda function. Custom Resources are invoked by the deploy framework, not by users. If you want to test the underlying handler, target the ServiceToken Lambda directly.`);
1320
- throw new LocalInvokeResolutionError(`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not a Lambda function. cdkl invoke only works on AWS::Lambda::Function resources in v1.`);
1364
+ throw new LocalInvokeResolutionError(`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not a Lambda function. ${getEmbedConfig().cliName} invoke only works on AWS::Lambda::Function resources in v1.`);
1321
1365
  }
1322
1366
  return extractLambdaProperties(stack, logicalId, resource, resources);
1323
1367
  }
@@ -1366,7 +1410,7 @@ function extractLambdaProperties(stack, logicalId, resource, resources) {
1366
1410
  });
1367
1411
  const runtime = typeof props["Runtime"] === "string" ? props["Runtime"] : "";
1368
1412
  const handler = typeof props["Handler"] === "string" ? props["Handler"] : "";
1369
- if (!runtime) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has no Runtime property and no Code.ImageUri. cdk-local cannot tell if this is a ZIP or a container Lambda.`);
1413
+ if (!runtime) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has no Runtime property and no Code.ImageUri. ${getEmbedConfig().productName} cannot tell if this is a ZIP or a container Lambda.`);
1370
1414
  if (!handler) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has no Handler property.`);
1371
1415
  const inlineCode = typeof code["ZipFile"] === "string" ? code["ZipFile"] : void 0;
1372
1416
  let codePath = null;
@@ -1461,9 +1505,10 @@ function extractImageUri$1(value, logicalId, stackName, resources, region) {
1461
1505
  const pseudoParameters = derivePseudoParametersFromRegion(region);
1462
1506
  const joinResolved = tryResolveImageFnJoin(value, resources, pseudoParameters ? { pseudoParameters } : void 0);
1463
1507
  if (joinResolved.kind === "resolved") return joinResolved.uri;
1464
- if (joinResolved.kind === "needs-state") throw new LocalInvokeResolutionError(`Lambda '${logicalId}' in ${stackName} references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. cdkl invoke cannot resolve the repository URI without state — deploy the stack first (so cdk-local records the repository physical id), rebuild via lambda.DockerImageCode.fromImageAsset, or pin a public image.`);
1465
- if (joinResolved.kind === "unsupported-join") throw new LocalInvokeResolutionError(`Lambda '${logicalId}' in ${stackName} has an unsupported Fn::Join Code.ImageUri shape: ${joinResolved.reason}. cdkl invoke recognizes the canonical CDK 2.x lambda.DockerImageCode.fromEcr Fn::Join shape (delimiter "" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).`);
1466
- throw new LocalInvokeResolutionError(`Lambda '${logicalId}' in ${stackName} has an Fn::Join Code.ImageUri that cdkl invoke cannot resolve${pseudoParameters ? " (likely ${AWS::AccountId}, which cdk-local cannot derive without --from-state or STS)" : ` (cdk-local could not derive AWS pseudo parameters because stack.region was undefined)`}. Workarounds: deploy first and run with --from-state, or pin a fully-literal public image URI.`);
1508
+ if (joinResolved.kind === "needs-state") throw new LocalInvokeResolutionError(`Lambda '${logicalId}' in ${stackName} references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. ${getEmbedConfig().cliName} invoke cannot resolve the repository URI without state — deploy the stack first (so ${getEmbedConfig().productName} records the repository physical id), rebuild via lambda.DockerImageCode.fromImageAsset, or pin a public image.`);
1509
+ if (joinResolved.kind === "unsupported-join") throw new LocalInvokeResolutionError(`Lambda '${logicalId}' in ${stackName} has an unsupported Fn::Join Code.ImageUri shape: ${joinResolved.reason}. ${getEmbedConfig().cliName} invoke recognizes the canonical CDK 2.x lambda.DockerImageCode.fromEcr Fn::Join shape (delimiter "" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).`);
1510
+ const accountIdHint = pseudoParameters ? ` (likely \${AWS::AccountId}, which ${getEmbedConfig().productName} cannot derive without --from-state or STS)` : ` (${getEmbedConfig().productName} could not derive AWS pseudo parameters because stack.region was undefined)`;
1511
+ throw new LocalInvokeResolutionError(`Lambda '${logicalId}' in ${stackName} has an Fn::Join Code.ImageUri that ${getEmbedConfig().cliName} invoke cannot resolve${accountIdHint}. Workarounds: deploy first and run with --from-state, or pin a fully-literal public image URI.`);
1467
1512
  }
1468
1513
  }
1469
1514
  }
@@ -1485,7 +1530,7 @@ function extractImageLambdaProperties(args) {
1485
1530
  const first = arches[0];
1486
1531
  if (first === "arm64") architecture = "arm64";
1487
1532
  else if (first === "x86_64") architecture = "x86_64";
1488
- else throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has unsupported Architectures value '${String(first)}'. cdkl invoke supports x86_64 and arm64.`);
1533
+ else throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has unsupported Architectures value '${String(first)}'. ${getEmbedConfig().cliName} invoke supports x86_64 and arm64.`);
1489
1534
  }
1490
1535
  return {
1491
1536
  kind: "image",
@@ -1515,7 +1560,7 @@ function extractImageLambdaProperties(args) {
1515
1560
  */
1516
1561
  function resolveAssetCodePath$1(stack, logicalId, resource) {
1517
1562
  const assetPath = resource.Metadata?.["aws:asset:path"];
1518
- if (typeof assetPath !== "string" || assetPath.length === 0) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has no Metadata['aws:asset:path']. cdkl invoke needs this hint to find the local asset directory. Re-synthesize the app (without \`--output <stale-dir>\`) and retry.`);
1563
+ if (typeof assetPath !== "string" || assetPath.length === 0) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} invoke needs this hint to find the local asset directory. Re-synthesize the app (without \`--output <stale-dir>\`) and retry.`);
1519
1564
  const cdkOutDir = stack.assetManifestPath ? dirname(stack.assetManifestPath) : process.cwd();
1520
1565
  const abs = isAbsolute(assetPath) ? assetPath : resolve(cdkOutDir, assetPath);
1521
1566
  if (!existsSync(abs) || !statSync(abs).isDirectory()) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' asset directory '${abs}' does not exist or is not a directory. Re-synthesize the app and retry.`);
@@ -1571,7 +1616,7 @@ function resolveLambdaLayers(stack, logicalId, props) {
1571
1616
  const entry = layers[i];
1572
1617
  if (typeof entry === "string") {
1573
1618
  const parsed = parseLayerVersionArn(entry);
1574
- if (!parsed) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has a Layers entry [${i}] cdk-local cannot resolve locally: literal string '${entry}'. Expected a same-stack Ref / Fn::GetAtt to an AWS::Lambda::LayerVersion OR a literal layer-version ARN of the form arn:aws:lambda:<region>:<account>:layer:<name>:<version>.`);
1619
+ if (!parsed) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has a Layers entry [${i}] ${getEmbedConfig().productName} cannot resolve locally: literal string '${entry}'. Expected a same-stack Ref / Fn::GetAtt to an AWS::Lambda::LayerVersion OR a literal layer-version ARN of the form arn:aws:lambda:<region>:<account>:layer:<name>:<version>.`);
1575
1620
  out.push({
1576
1621
  kind: "arn",
1577
1622
  logicalId: parsed.arn,
@@ -1580,7 +1625,7 @@ function resolveLambdaLayers(stack, logicalId, props) {
1580
1625
  continue;
1581
1626
  }
1582
1627
  const layerLogicalId = pickLayerLogicalId(entry);
1583
- if (!layerLogicalId) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has a Layers entry [${i}] cdk-local cannot resolve locally: ${describeLayerEntry(entry)}. Expected a same-stack Ref / Fn::GetAtt to an AWS::Lambda::LayerVersion OR a literal layer-version ARN of the form arn:aws:lambda:<region>:<account>:layer:<name>:<version>.`);
1628
+ if (!layerLogicalId) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has a Layers entry [${i}] ${getEmbedConfig().productName} cannot resolve locally: ${describeLayerEntry(entry)}. Expected a same-stack Ref / Fn::GetAtt to an AWS::Lambda::LayerVersion OR a literal layer-version ARN of the form arn:aws:lambda:<region>:<account>:layer:<name>:<version>.`);
1584
1629
  const layerResource = resources[layerLogicalId];
1585
1630
  if (!layerResource) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' Layers entry [${i}] references '${layerLogicalId}', but no resource with that logical ID exists in stack '${stack.stackName}'.`);
1586
1631
  if (layerResource.Type !== "AWS::Lambda::LayerVersion") throw new LocalInvokeResolutionError(`Lambda '${logicalId}' Layers entry [${i}] references '${layerLogicalId}' (${layerResource.Type}), which is not an AWS::Lambda::LayerVersion.`);
@@ -1716,7 +1761,7 @@ async function materializeLayerFromArn(layer, options = {}) {
1716
1761
  } catch (err) {
1717
1762
  throw new LayerMaterializationError(`Layer ${layer.arn}: failed to download layer ZIP from the presigned URL: ${errMsg(err)}.`);
1718
1763
  }
1719
- const dir = await mkdtemp(join(tmpdir(), `cdkl-arn-layer-${layer.name}-${layer.version}-`));
1764
+ const dir = await mkdtemp(join(tmpdir(), `${getEmbedConfig().resourceNamePrefix}-arn-layer-${layer.name}-${layer.version}-`));
1720
1765
  try {
1721
1766
  await unzipBufferToDirectory(zipBytes, dir);
1722
1767
  } catch (err) {
@@ -1782,7 +1827,7 @@ async function buildAssumeRoleCommand(roleArn) {
1782
1827
  const { AssumeRoleCommand } = await import("@aws-sdk/client-sts");
1783
1828
  return new AssumeRoleCommand({
1784
1829
  RoleArn: roleArn,
1785
- RoleSessionName: `cdkl-layer-${Date.now()}`,
1830
+ RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-layer-${Date.now()}`,
1786
1831
  DurationSeconds: 3600
1787
1832
  });
1788
1833
  }
@@ -2005,7 +2050,7 @@ function resolveRef(arg, context) {
2005
2050
  const resource = context.resources[arg];
2006
2051
  if (!resource) return {
2007
2052
  kind: "unresolved",
2008
- reason: `Ref '${arg}': no record in cdkd state (was the resource deployed?)`
2053
+ reason: `Ref '${arg}': no record in the state source (was the resource deployed?)`
2009
2054
  };
2010
2055
  return {
2011
2056
  kind: "literal",
@@ -2015,7 +2060,7 @@ function resolveRef(arg, context) {
2015
2060
  function resolvePseudoParameter(name, pseudo) {
2016
2061
  if (!pseudo) return {
2017
2062
  kind: "unresolved",
2018
- reason: `Ref '${name}': pseudo parameter not supplied (need --from-state context)`
2063
+ reason: `Ref '${name}': pseudo parameter not supplied (need an active state source, e.g. --from-cfn-stack)`
2019
2064
  };
2020
2065
  switch (name) {
2021
2066
  case "AWS::AccountId":
@@ -2077,12 +2122,12 @@ function resolveGetAtt(arg, context) {
2077
2122
  const resource = context.resources[logicalId];
2078
2123
  if (!resource) return {
2079
2124
  kind: "unresolved",
2080
- reason: `Fn::GetAtt '${logicalId}.${attr}': no record in cdkd state`
2125
+ reason: `Fn::GetAtt '${logicalId}.${attr}': no record in the state source`
2081
2126
  };
2082
2127
  const cached = resource.attributes?.[attr];
2083
2128
  if (cached === void 0) return {
2084
2129
  kind: "unresolved",
2085
- reason: `Fn::GetAtt '${logicalId}.${attr}': attribute not captured in cdkd state at deploy time`
2130
+ reason: `Fn::GetAtt '${logicalId}.${attr}': attribute not captured in the state source at deploy time`
2086
2131
  };
2087
2132
  if (typeof cached === "string" || typeof cached === "number" || typeof cached === "boolean") return {
2088
2133
  kind: "literal",
@@ -2372,7 +2417,7 @@ async function resolveImportValueAsync(arg, context) {
2372
2417
  const exportName = inner.value;
2373
2418
  if (!context.crossStackResolver) return {
2374
2419
  kind: "unresolved",
2375
- reason: `Fn::ImportValue '${exportName}': no cross-stack resolver supplied (pass --from-state and ensure the producer stack was deployed via cdkd deploy)`
2420
+ reason: `Fn::ImportValue '${exportName}': no cross-stack resolver supplied (pass a state-source flag, e.g. --from-cfn-stack, and ensure the producer stack was deployed)`
2376
2421
  };
2377
2422
  let resolved;
2378
2423
  try {
@@ -2385,7 +2430,7 @@ async function resolveImportValueAsync(arg, context) {
2385
2430
  }
2386
2431
  if (resolved === void 0) return {
2387
2432
  kind: "unresolved",
2388
- reason: `Fn::ImportValue '${exportName}': export not found in any cdkd-managed stack in this region`
2433
+ reason: `Fn::ImportValue '${exportName}': export not found in any deployed stack in this region`
2389
2434
  };
2390
2435
  return {
2391
2436
  kind: "literal",
@@ -2443,11 +2488,11 @@ async function resolveGetStackOutputAsync(arg, context) {
2443
2488
  };
2444
2489
  if (args["RoleArn"] !== void 0 && args["RoleArn"] !== null) return {
2445
2490
  kind: "unresolved",
2446
- reason: `Fn::GetStackOutput '${stackName}.${outputName}': RoleArn (cross-account) is not yet supported by --from-state — tracked under issue #449`
2491
+ reason: `Fn::GetStackOutput '${stackName}.${outputName}': RoleArn (cross-account) is not yet supported by the state source — tracked under issue #449`
2447
2492
  };
2448
2493
  if (!context.crossStackResolver) return {
2449
2494
  kind: "unresolved",
2450
- reason: `Fn::GetStackOutput '${stackName}.${outputName}': no cross-stack resolver supplied (pass --from-state and ensure the producer stack was deployed via cdkd deploy)`
2495
+ reason: `Fn::GetStackOutput '${stackName}.${outputName}': no cross-stack resolver supplied (pass a state-source flag, e.g. --from-cfn-stack, and ensure the producer stack was deployed)`
2451
2496
  };
2452
2497
  let resolved;
2453
2498
  try {
@@ -2841,7 +2886,7 @@ function resolveEcsTaskTarget(target, stacks, context) {
2841
2886
  logicalId = parsed.pathOrId;
2842
2887
  }
2843
2888
  if (!logicalId || !resource) throw notFoundError$1(target, stack, resources);
2844
- if (resource.Type === "AWS::Lambda::Function") throw new EcsTaskResolutionError(`Resource '${logicalId}' in ${stack.stackName} is a Lambda function, not an ECS task definition. Use \`cdkl invoke\` for Lambda; \`cdkl run-task\` is ECS only.`);
2889
+ if (resource.Type === "AWS::Lambda::Function") throw new EcsTaskResolutionError(`Resource '${logicalId}' in ${stack.stackName} is a Lambda function, not an ECS task definition. Use \`${getEmbedConfig().cliName} invoke\` for Lambda; \`${getEmbedConfig().cliName} run-task\` is ECS only.`);
2845
2890
  if (resource.Type !== "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not an AWS::ECS::TaskDefinition.`);
2846
2891
  return extractTaskDefinitionProperties(stack, logicalId, resource, context);
2847
2892
  }
@@ -2864,7 +2909,7 @@ function extractTaskDefinitionProperties(stack, logicalId, resource, context) {
2864
2909
  if (rawNetworkMode === "bridge" || rawNetworkMode === "awsvpc" || rawNetworkMode === "host" || rawNetworkMode === "none") networkMode = rawNetworkMode;
2865
2910
  else throw new EcsTaskResolutionError(`Task definition '${logicalId}' has unsupported NetworkMode '${rawNetworkMode}'. Supported values: bridge / awsvpc / host / none.`);
2866
2911
  if (networkMode === "awsvpc") warnings.push(`NetworkMode 'awsvpc' on '${logicalId}' is mapped to docker bridge locally — docker cannot emulate ENI-per-task. AWS SDK calls still reach public endpoints via the developer network.`);
2867
- if (props["ProxyConfiguration"]) warnings.push(`Task definition '${logicalId}' declares 'ProxyConfiguration' (custom Envoy / App Mesh bootstrap), which is NOT honored by 'cdkl start-service' / 'cdkl run-task' in v1. Local execution will run without the configured proxy. See design doc § 2 for the rationale.`);
2912
+ if (props["ProxyConfiguration"]) warnings.push(`Task definition '${logicalId}' declares 'ProxyConfiguration' (custom Envoy / App Mesh bootstrap), which is NOT honored by '${getEmbedConfig().cliName} start-service' / '${getEmbedConfig().cliName} run-task' in v1. Local execution will run without the configured proxy. See design doc § 2 for the rationale.`);
2868
2913
  const resources = stack.template.Resources ?? {};
2869
2914
  const subContext = buildSubstitutionContextFromImageContext(context);
2870
2915
  const taskRoleArn = resolveRoleArn(props["TaskRoleArn"], resources, subContext);
@@ -3122,10 +3167,10 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
3122
3167
  if (getAttImage) return classifyResolvedImage(getAttImage);
3123
3168
  const joinResolved = tryResolveImageFnJoin(raw, resources, context);
3124
3169
  if (joinResolved.kind === "resolved") return classifyResolvedImage(joinResolved.uri);
3125
- if (joinResolved.kind === "needs-state") throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. cdkl run-task cannot resolve the repository URI without state — pass --from-state (the stack must have been deployed via cdkd deploy), build via ContainerImage.fromAsset, or pin a public image.`);
3126
- if (joinResolved.kind === "unsupported-join") throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an unsupported Fn::Join Image shape: ${joinResolved.reason}. cdkl run-task recognizes the canonical CDK 2.x ContainerImage.fromEcrRepository Fn::Join shape (delimiter "" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).`);
3170
+ if (joinResolved.kind === "needs-state") throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. ${getEmbedConfig().cliName} run-task cannot resolve the repository URI without state — pass --from-state (the stack must have been deployed via cdkd deploy), build via ContainerImage.fromAsset, or pin a public image.`);
3171
+ if (joinResolved.kind === "unsupported-join") throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an unsupported Fn::Join Image shape: ${joinResolved.reason}. ${getEmbedConfig().cliName} run-task recognizes the canonical CDK 2.x ContainerImage.fromEcrRepository Fn::Join shape (delimiter "" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).`);
3127
3172
  const flat = extractImageString(raw);
3128
- if (!flat) throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an unparseable Image property. cdkl run-task v1 supports flat string images, single-key Fn::Sub bodies, and CDK-asset Image references.`);
3173
+ if (!flat) throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an unparseable Image property. ${getEmbedConfig().cliName} run-task v1 supports flat string images, single-key Fn::Sub bodies, and CDK-asset Image references.`);
3129
3174
  if (flat.includes("cdk-hnb659fds-container-assets-")) {
3130
3175
  const hashMatch = /:([a-f0-9]{8,})$/.exec(flat);
3131
3176
  const out = { kind: "cdk-asset" };
@@ -3135,9 +3180,9 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
3135
3180
  const substituted = substituteImagePlaceholders(flat, resources, context);
3136
3181
  if (substituted.includes("${")) {
3137
3182
  const unresolvedRepoRef = findUnresolvedEcrRepositoryRef(substituted, resources);
3138
- if (unresolvedRepoRef) throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' references same-stack ECR repository '${unresolvedRepoRef}'. cdkl run-task v1 cannot resolve the repository URI without state — pass --from-state (the stack must have been deployed via cdkd deploy), build via ContainerImage.fromAsset, or pin a public image.`);
3139
- if (substituted.includes("AWS::")) throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image that references AWS pseudo parameters (${substituted}). cdk-local could not resolve them: confirm AWS credentials are configured so STS GetCallerIdentity succeeds, and that --region / AWS_REGION names the target region. Workaround: build the image locally (ContainerImage.fromAsset) or pin a public image.`);
3140
- throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image with unresolved \${...} placeholders (${substituted}). cdkl run-task v1 only resolves AWS pseudo parameters and same-stack AWS::ECR::Repository refs.`);
3183
+ if (unresolvedRepoRef) throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' references same-stack ECR repository '${unresolvedRepoRef}'. ${getEmbedConfig().cliName} run-task v1 cannot resolve the repository URI without state — pass --from-state (the stack must have been deployed via cdkd deploy), build via ContainerImage.fromAsset, or pin a public image.`);
3184
+ if (substituted.includes("AWS::")) throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image that references AWS pseudo parameters (${substituted}). ${getEmbedConfig().productName} could not resolve them: confirm AWS credentials are configured so STS GetCallerIdentity succeeds, and that --region / AWS_REGION names the target region. Workaround: build the image locally (ContainerImage.fromAsset) or pin a public image.`);
3185
+ throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image with unresolved \${...} placeholders (${substituted}). ${getEmbedConfig().cliName} run-task v1 only resolves AWS pseudo parameters and same-stack AWS::ECR::Repository refs.`);
3141
3186
  }
3142
3187
  const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(substituted);
3143
3188
  if (ecrMatch) return {
@@ -3242,8 +3287,8 @@ function parseVolume(raw, idx, taskLogicalId, subContext) {
3242
3287
  const v = raw;
3243
3288
  const name = pickString(v["Name"]);
3244
3289
  if (!name) throw new EcsTaskResolutionError(`Task '${taskLogicalId}' Volumes[${idx}] has no Name.`);
3245
- if (v["EFSVolumeConfiguration"]) throw new EcsTaskResolutionError(`Task '${taskLogicalId}' Volumes[${idx}] '${name}' uses EFSVolumeConfiguration, which cdkl run-task cannot proxy locally. Workaround: bind-mount a local directory at the same containerPath via Host: { SourcePath: '<local-path>' }, or override at runtime via --env-vars semantics for a Phase 2 follow-up.`);
3246
- if (v["FSxWindowsFileServerVolumeConfiguration"]) throw new EcsTaskResolutionError(`Task '${taskLogicalId}' Volumes[${idx}] '${name}' uses FSxWindowsFileServerVolumeConfiguration, which cdkl run-task cannot proxy locally.`);
3290
+ if (v["EFSVolumeConfiguration"]) throw new EcsTaskResolutionError(`Task '${taskLogicalId}' Volumes[${idx}] '${name}' uses EFSVolumeConfiguration, which ${getEmbedConfig().cliName} run-task cannot proxy locally. Workaround: bind-mount a local directory at the same containerPath via Host: { SourcePath: '<local-path>' }, or override at runtime via --env-vars semantics for a Phase 2 follow-up.`);
3291
+ if (v["FSxWindowsFileServerVolumeConfiguration"]) throw new EcsTaskResolutionError(`Task '${taskLogicalId}' Volumes[${idx}] '${name}' uses FSxWindowsFileServerVolumeConfiguration, which ${getEmbedConfig().cliName} run-task cannot proxy locally.`);
3247
3292
  const dockerCfg = v["DockerVolumeConfiguration"];
3248
3293
  if (dockerCfg && typeof dockerCfg === "object") {
3249
3294
  const d = dockerCfg;
@@ -3487,6 +3532,43 @@ async function applyCrossStackResolverToTask(task, context) {
3487
3532
 
3488
3533
  //#endregion
3489
3534
  //#region src/local/runtime-image.ts
3535
+ /**
3536
+ * Map a CloudFormation `Runtime` string to the AWS Lambda base image that
3537
+ * bundles the matching runtime + the Lambda Runtime Interface Emulator (RIE),
3538
+ * plus the source-file extension for inline-code materialization.
3539
+ *
3540
+ * Per D1 in the issue, cdk-local uses the **full** base image
3541
+ * (`public.ecr.aws/lambda/<lang>:<version>`, ~600MB) over SAM's lighter
3542
+ * `public.ecr.aws/sam/emulation-<lang>` (~150MB). The size cost is one-time
3543
+ * per machine; in exchange the local runtime is the same artifact AWS runs
3544
+ * for container Lambdas, so a "works locally, breaks in AWS" mismatch is
3545
+ * almost always a config issue rather than an image divergence.
3546
+ *
3547
+ * Supports every current AWS Lambda runtime — Node.js, Python, Ruby,
3548
+ * Java, .NET, and the OS-only `provided.al2` / `provided.al2023` (the
3549
+ * canonical hosts for Go via a `bootstrap` binary, Rust, C/C++, or any
3550
+ * other compiled native runtime). The deprecated `go1.x` runtime is
3551
+ * explicitly rejected with a migration pointer to `provided.al2023`.
3552
+ *
3553
+ * Truly unknown runtime strings (e.g. typos like `nodejs99.x`, or
3554
+ * back-revs AWS retired well before cdk-local existed) fall through to a
3555
+ * generic error that lists every supported runtime.
3556
+ *
3557
+ * Ruby uses the same `<file>.<func>` handler grammar as Node.js and Python,
3558
+ * so the inline-code materializer's `lastIndexOf('.')` parse works
3559
+ * unchanged; only the file extension (`.rb`) differs.
3560
+ *
3561
+ * Java, .NET, and `provided.*` are **asset-backed only** — the Handler
3562
+ * value names a compiled artifact (`package.Class::method` for Java's
3563
+ * JVM class on the classpath; `Assembly::Namespace.Class::Method` for
3564
+ * .NET's CLR assembly / DLL; an arbitrary identifier per the user's
3565
+ * `bootstrap` binary for `provided.*`), which can only be supplied as a
3566
+ * compiled artifact directory packaged via `lambda.Code.fromAsset(...)`.
3567
+ * Inline `Code.ZipFile` has no meaning for any of them, so
3568
+ * `fileExtension` is `null` for every Java / .NET / `provided.*` entry
3569
+ * and `resolveRuntimeFileExtension` throws a runtime-specific message
3570
+ * routing the user to `Code.fromAsset(...)`.
3571
+ */
3490
3572
  const SUPPORTED_RUNTIMES = {
3491
3573
  "nodejs18.x": {
3492
3574
  image: "public.ecr.aws/lambda/nodejs:18",
@@ -3608,7 +3690,7 @@ function resolveRuntimeSpec(runtime) {
3608
3690
  const spec = SUPPORTED_RUNTIMES[runtime];
3609
3691
  if (spec) return spec;
3610
3692
  if (runtime === "go1.x") throw new UnsupportedRuntimeError(runtime, "Runtime 'go1.x' was deprecated by AWS Lambda on 2024-01-08 and is no longer available. Migrate to the OS-only runtime: build your Go program as a `bootstrap` binary and set the CDK runtime to `lambda.Runtime.PROVIDED_AL2023` (or `lambda.Runtime.PROVIDED_AL2`). See https://docs.aws.amazon.com/lambda/latest/dg/lambda-golang.html");
3611
- throw new UnsupportedRuntimeError(runtime, `Unknown runtime '${runtime}'. cdkl invoke supports nodejs18.x / nodejs20.x / nodejs22.x / nodejs24.x / python3.11 / python3.12 / python3.13 / python3.14 / ruby3.2 / ruby3.3 / java8.al2 / java11 / java17 / java21 / dotnet6 / dotnet8 / provided.al2 / provided.al2023.`);
3693
+ throw new UnsupportedRuntimeError(runtime, `Unknown runtime '${runtime}'. ${getEmbedConfig().cliName} invoke supports nodejs18.x / nodejs20.x / nodejs22.x / nodejs24.x / python3.11 / python3.12 / python3.13 / python3.14 / ruby3.2 / ruby3.3 / java8.al2 / java11 / java17 / java21 / dotnet6 / dotnet8 / provided.al2 / provided.al2023.`);
3612
3694
  }
3613
3695
  /**
3614
3696
  * In-container path where cdk-local should bind-mount the function's
@@ -3794,7 +3876,7 @@ async function ensureDockerAvailable() {
3794
3876
  ]);
3795
3877
  } catch (error) {
3796
3878
  const err = error;
3797
- if (err.code === "ENOENT") throw new DockerRunnerError(`${cmd} is not installed or not on PATH. cdkl invoke needs Docker (or a compatible CLI specified via CDK_DOCKER) — install it and retry.`);
3879
+ if (err.code === "ENOENT") throw new DockerRunnerError(`${cmd} is not installed or not on PATH. ${getEmbedConfig().cliName} invoke needs Docker (or a compatible CLI specified via CDK_DOCKER) — install it and retry.`);
3798
3880
  throw new DockerRunnerError(`${cmd} daemon is not reachable: ${err.stderr?.trim() || err.message || String(error)}. Start Docker Desktop / the docker daemon and retry.`);
3799
3881
  }
3800
3882
  }
@@ -4041,7 +4123,7 @@ function isCredentialFresh(creds) {
4041
4123
  async function pullEcrImage(imageUri, options) {
4042
4124
  const logger = getLogger().child("ecr-puller");
4043
4125
  const parsed = parseEcrUri(imageUri);
4044
- if (!parsed) throw new LocalInvokeBuildError(`Image URI '${imageUri}' is not an ECR URI. cdkl invoke v1 only authenticates against ECR for the deployed-image fallback path.`);
4126
+ if (!parsed) throw new LocalInvokeBuildError(`Image URI '${imageUri}' is not an ECR URI. ${getEmbedConfig().cliName} invoke v1 only authenticates against ECR for the deployed-image fallback path.`);
4045
4127
  const callerRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"];
4046
4128
  if (options.skipPull) {
4047
4129
  logger.info(`Skipping ECR pull (--no-pull). Verifying ${imageUri} is in local cache...`);
@@ -4107,7 +4189,7 @@ async function assumeRoleForEcr(roleArn, callerRegion, logger) {
4107
4189
  try {
4108
4190
  const creds = (await sts.send(new AssumeRoleCommand({
4109
4191
  RoleArn: roleArn,
4110
- RoleSessionName: `cdkl-ecr-${Date.now()}`,
4192
+ RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-ecr-${Date.now()}`,
4111
4193
  DurationSeconds: 3600
4112
4194
  }))).Credentials;
4113
4195
  if (!creds || !creds.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) throw new LocalInvokeBuildError(`AssumeRole(${roleArn}) returned no usable credentials. Verify the role's trust policy allows your identity to assume it.`);
@@ -4162,7 +4244,7 @@ async function verifyImageInLocalCache(imageUri) {
4162
4244
  imageUri
4163
4245
  ]);
4164
4246
  } catch {
4165
- throw new LocalInvokeBuildError(`Image '${imageUri}' is not in the local docker cache and --no-pull was set. Either remove --no-pull (cdk-local will pull from ECR) or pre-pull the image manually with \`docker pull\`.`);
4247
+ throw new LocalInvokeBuildError(`Image '${imageUri}' is not in the local docker cache and --no-pull was set. Either remove --no-pull (${getEmbedConfig().productName} will pull from ECR) or pre-pull the image manually with \`docker pull\`.`);
4166
4248
  }
4167
4249
  }
4168
4250
  /**
@@ -4265,7 +4347,7 @@ function computeLocalTag(source) {
4265
4347
  pushField(hash, "dockerOutputs", (source.dockerOutputs ?? []).join(""));
4266
4348
  pushField(hash, "cacheFrom", (source.cacheFrom ?? []).map((o) => JSON.stringify(o)).join(""));
4267
4349
  pushField(hash, "cacheTo", source.cacheTo ? JSON.stringify(source.cacheTo) : "");
4268
- return `cdkl-invoke-${hash.digest("hex").slice(0, 16)}`;
4350
+ return `${getEmbedConfig().resourceNamePrefix}-invoke-${hash.digest("hex").slice(0, 16)}`;
4269
4351
  }
4270
4352
  function pushField(hash, name, value) {
4271
4353
  hash.update(name);
@@ -4852,16 +4934,19 @@ function singleFlight(fn, onError) {
4852
4934
  //#region src/cli/commands/local-profile-credentials-file.ts
4853
4935
  /**
4854
4936
  * Path inside the container where the credentials file is mounted. Fixed
4855
- * (not user-configurable) so the env-var injection is stable.
4856
- * `/cdk-local-aws/` is outside `/var/task` (the Lambda code mount) and
4857
- * outside `/root/` (which the user's handler may bind-mount or modify),
4858
- * so there is no collision risk with the user's payload.
4937
+ * per run (not user-configurable) so the env-var injection is stable. The
4938
+ * default `/cdk-local-aws/` is outside `/var/task` (the Lambda code mount)
4939
+ * and outside `/root/` (which the user's handler may bind-mount or
4940
+ * modify), so there is no collision risk with the user's payload. The
4941
+ * `${awsBindMountPath}` segment honors the active embed config.
4859
4942
  */
4860
- const CONTAINER_AWS_CREDENTIALS_PATH = "/cdk-local-aws/credentials";
4943
+ function getContainerAwsCredentialsPath() {
4944
+ return `${getEmbedConfig().awsBindMountPath}/credentials`;
4945
+ }
4861
4946
  /**
4862
4947
  * Write a temporary AWS shared-credentials file containing the resolved
4863
4948
  * `--profile <name>` credentials, ready to bind-mount into a Lambda
4864
- * container at {@link CONTAINER_AWS_CREDENTIALS_PATH}.
4949
+ * container at {@link getContainerAwsCredentialsPath}.
4865
4950
  *
4866
4951
  * The file content is the standard `[profile-name]` INI shape:
4867
4952
  *
@@ -4882,7 +4967,7 @@ const CONTAINER_AWS_CREDENTIALS_PATH = "/cdk-local-aws/credentials";
4882
4967
  async function writeProfileCredentialsFile(profileName, creds) {
4883
4968
  if (profileName === "") throw new Error("writeProfileCredentialsFile: profile name must not be empty.");
4884
4969
  if (/[\r\n[\]]/.test(profileName)) throw new Error(`writeProfileCredentialsFile: profile name '${profileName}' contains a forbidden character (any of CR, LF, '[', ']' would corrupt the INI file or the docker -e env var).`);
4885
- const dir = await mkdtemp(path.join(tmpdir(), "cdk-local-profile-creds-"));
4970
+ const dir = await mkdtemp(path.join(tmpdir(), `${getEmbedConfig().productName}-profile-creds-`));
4886
4971
  const hostPath = path.join(dir, "credentials");
4887
4972
  const lines = [
4888
4973
  `[${profileName}]`,
@@ -4893,7 +4978,7 @@ async function writeProfileCredentialsFile(profileName, creds) {
4893
4978
  await writeFile(hostPath, lines.join("\n") + "\n", { mode: 384 });
4894
4979
  return {
4895
4980
  hostPath,
4896
- containerPath: CONTAINER_AWS_CREDENTIALS_PATH,
4981
+ containerPath: getContainerAwsCredentialsPath(),
4897
4982
  profileName,
4898
4983
  dispose: async () => {
4899
4984
  await rm(dir, {
@@ -4977,7 +5062,7 @@ async function localInvokeCommand(target, options, extraStateProviders) {
4977
5062
  const profileCredentials = options.profile ? await resolveProfileCredentials$1(options.profile) : void 0;
4978
5063
  if (options.profile && profileCredentials) profileCredsFile = await writeProfileCredentialsFile(options.profile, profileCredentials);
4979
5064
  const appCmd = resolveApp(options.app);
4980
- if (!appCmd) throw new Error("No CDK app specified. Pass --app, set CDKL_APP, or add \"app\" to cdk.json.");
5065
+ if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
4981
5066
  logger.info("Synthesizing CDK app...");
4982
5067
  const synthesizer = new Synthesizer();
4983
5068
  const context = parseContextOptions(options.context);
@@ -5198,7 +5283,7 @@ function materializeLambdaLayers$1(layers) {
5198
5283
  containerPath: "/opt",
5199
5284
  readOnly: true
5200
5285
  } };
5201
- const tmpDir = mkdtempSync(path.join(tmpdir(), "cdkl-invoke-layers-"));
5286
+ const tmpDir = mkdtempSync(path.join(tmpdir(), `${getEmbedConfig().resourceNamePrefix}-invoke-layers-`));
5202
5287
  for (const layer of layers) cpSync(layer.assetPath, tmpDir, {
5203
5288
  recursive: true,
5204
5289
  force: true
@@ -5222,7 +5307,7 @@ async function resolveContainerImagePlan(lambda, options) {
5222
5307
  noBuild: options.build === false
5223
5308
  });
5224
5309
  else {
5225
- 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 cdkl can authenticate against. Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.`);
5310
+ 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.`);
5226
5311
  logger.info(`No matching cdk.out asset for ${lambda.imageUri}; falling back to ECR pull (same-acct/region only)...`);
5227
5312
  imageRef = await pullEcrImage(lambda.imageUri, {
5228
5313
  skipPull: options.pull === false,
@@ -5276,7 +5361,7 @@ function envHasCrossStackIntrinsic(templateEnv) {
5276
5361
  async function resolvePseudoParametersForInvoke(stackRegion, options) {
5277
5362
  const logger = getLogger();
5278
5363
  const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? stackRegion;
5279
- if (!region) logger.warn("Resolver references ${AWS::Region} but cdkl could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.");
5364
+ 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.`);
5280
5365
  let accountId;
5281
5366
  try {
5282
5367
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
@@ -5348,7 +5433,7 @@ async function assumeLambdaExecutionRole$1(roleArn, region) {
5348
5433
  try {
5349
5434
  const creds = (await sts.send(new AssumeRoleCommand({
5350
5435
  RoleArn: roleArn,
5351
- RoleSessionName: `cdkl-invoke-${Date.now()}`,
5436
+ RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-invoke-${Date.now()}`,
5352
5437
  DurationSeconds: 3600
5353
5438
  }))).Credentials;
5354
5439
  if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) throw new Error(`AssumeRole(${roleArn}) returned no usable credentials.`);
@@ -5405,7 +5490,7 @@ function materializeInlineCode$1(handler, source, fileExtension) {
5405
5490
  const lastDot = handler.lastIndexOf(".");
5406
5491
  if (lastDot <= 0) throw new Error(`Handler '${handler}' is malformed: expected '<modulePath>.<exportName>'.`);
5407
5492
  const modulePath = handler.substring(0, lastDot);
5408
- const dir = mkdtempSync(path.join(tmpdir(), "cdkl-invoke-"));
5493
+ const dir = mkdtempSync(path.join(tmpdir(), `${getEmbedConfig().resourceNamePrefix}-invoke-`));
5409
5494
  const filePath = path.join(dir, `${modulePath}${fileExtension}`);
5410
5495
  mkdirSync(path.dirname(filePath), { recursive: true });
5411
5496
  writeFileSync(filePath, source, "utf-8");
@@ -5438,12 +5523,13 @@ function pickReferencedLogicalId(intrinsic) {
5438
5523
  }
5439
5524
  }
5440
5525
  function createLocalInvokeCommand(opts = {}) {
5441
- const invoke = new Command("invoke").description("Run a Lambda function locally in a Docker container (RIE-backed). Target accepts a CDK display path (MyStack/MyApi/Handler) or stack-qualified logical ID (MyStack:MyApiHandler1234ABCD). Single-stack apps may omit the stack prefix.").argument("<target>", "CDK display path or stack-qualified logical ID of the Lambda to invoke").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 (use cached image) — no-op for IMAGE local-build path; `docker build` does not pull base layers by default")).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 DescribeStackResources 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 DescribeStackResources 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.")).action(withErrorHandling(async (target, options) => {
5526
+ setEmbedConfig(opts.embedConfig);
5527
+ const invoke = new Command("invoke").description("Run a Lambda function locally in a Docker container (RIE-backed). Target accepts a CDK display path (MyStack/MyApi/Handler) or stack-qualified logical ID (MyStack:MyApiHandler1234ABCD). Single-stack apps may omit the stack prefix.").argument("<target>", "CDK display path or stack-qualified logical ID of the Lambda to invoke").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 (use cached image) — no-op for IMAGE local-build path; `docker build` does not pull base layers by default")).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.")).action(withErrorHandling(async (target, options) => {
5442
5528
  await localInvokeCommand(target, options, opts.extraStateProviders);
5443
5529
  }));
5444
5530
  [
5445
- ...commonOptions,
5446
- ...appOptions,
5531
+ ...commonOptions(),
5532
+ ...appOptions(),
5447
5533
  ...contextOptions
5448
5534
  ].forEach((option) => invoke.addOption(option));
5449
5535
  invoke.addOption(deprecatedRegionOption);
@@ -5815,7 +5901,7 @@ async function dispatchSfnStopExecution(params, region) {
5815
5901
  return okJson(await client.send(new mod.StopExecutionCommand(input)));
5816
5902
  }
5817
5903
  async function dispatchAppConfigGetConfiguration(params, region) {
5818
- return errorResponse(501, "AppConfig-GetConfiguration is recognized but cdk-local does not yet bundle @aws-sdk/client-appconfig. Use the deployed API for this subtype, or open an issue if you need local emulation.");
5904
+ return errorResponse(501, `AppConfig-GetConfiguration is recognized but ${getEmbedConfig().productName} does not yet bundle @aws-sdk/client-appconfig. Use the deployed API for this subtype, or open an issue if you need local emulation.`);
5819
5905
  }
5820
5906
  function requireParams(params, required) {
5821
5907
  const missing = required.filter((k) => !params[k] || params[k].trim() === "");
@@ -6044,7 +6130,7 @@ function discoverRoutes(stacks) {
6044
6130
  errors.push(err instanceof Error ? err.message : String(err));
6045
6131
  }
6046
6132
  }
6047
- if (errors.length > 0) throw new RouteDiscoveryError(`cdkl start-api: ${errors.length} malformed route(s) in the synthesized template:\n` + errors.map((e) => ` - ${e}`).join("\n"));
6133
+ if (errors.length > 0) throw new RouteDiscoveryError(`${getEmbedConfig().cliName} start-api: ${errors.length} malformed route(s) in the synthesized template:\n` + errors.map((e) => ` - ${e}`).join("\n"));
6048
6134
  return routes;
6049
6135
  }
6050
6136
  /**
@@ -6177,7 +6263,7 @@ function discoverRestV1Method(logicalId, resource, template, stackName) {
6177
6263
  method: httpMethod,
6178
6264
  pathPattern: path,
6179
6265
  lambdaLogicalId: "",
6180
- unsupported: { reason: `${stackName}/${logicalId}.Integration.Uri: ${arnOutcome.detail} (got ${shortJson$1(integrationUri)}). Lambda Arn intrinsics on cross-stack / imported references are not resolvable locally; deploy the producer stack and use \`cdkl invoke --from-state\` shapes if you need it.` }
6266
+ unsupported: { reason: `${stackName}/${logicalId}.Integration.Uri: ${arnOutcome.detail} (got ${shortJson$1(integrationUri)}). Lambda Arn intrinsics on cross-stack / imported references are not resolvable locally; deploy the producer stack and use \`${getEmbedConfig().cliName} invoke --from-state\` shapes if you need it.` }
6181
6267
  }];
6182
6268
  return [{
6183
6269
  ...baseRoute,
@@ -6271,7 +6357,7 @@ function buildHttpProxyIntegrationConfig(integration, stackName, logicalId) {
6271
6357
  const uri = integration["Uri"];
6272
6358
  if (typeof uri !== "string" || uri.length === 0) return {
6273
6359
  kind: "unsupported",
6274
- reason: `${stackName}/${logicalId}: HTTP_PROXY Integration.Uri must be a literal string in v1 (cdkl start-api does not resolve Fn::Sub / Fn::Join in HTTP_PROXY Uris); got ${shortJson$1(uri)}.`
6360
+ reason: `${stackName}/${logicalId}: HTTP_PROXY Integration.Uri must be a literal string in v1 (${getEmbedConfig().cliName} start-api does not resolve Fn::Sub / Fn::Join in HTTP_PROXY Uris); got ${shortJson$1(uri)}.`
6275
6361
  };
6276
6362
  const integrationHttpMethod = pickStringField(integration, "IntegrationHttpMethod");
6277
6363
  const requestParameters = pickStringRecord(integration["RequestParameters"]);
@@ -6295,7 +6381,7 @@ function buildHttpIntegrationConfig(integration, stackName, logicalId) {
6295
6381
  const uri = integration["Uri"];
6296
6382
  if (typeof uri !== "string" || uri.length === 0) return {
6297
6383
  kind: "unsupported",
6298
- reason: `${stackName}/${logicalId}: HTTP Integration.Uri must be a literal string in v1 (cdkl start-api does not resolve Fn::Sub / Fn::Join in HTTP Uris); got ${shortJson$1(uri)}.`
6384
+ reason: `${stackName}/${logicalId}: HTTP Integration.Uri must be a literal string in v1 (${getEmbedConfig().cliName} start-api does not resolve Fn::Sub / Fn::Join in HTTP Uris); got ${shortJson$1(uri)}.`
6299
6385
  };
6300
6386
  const integrationHttpMethod = pickStringField(integration, "IntegrationHttpMethod");
6301
6387
  const requestParameters = pickStringRecord(integration["RequestParameters"]);
@@ -6329,7 +6415,7 @@ function buildAwsIntegrationConfig(integration, stackName, logicalId) {
6329
6415
  const uri = integration["Uri"];
6330
6416
  if (!uriContainsLambdaMarker(uri)) return {
6331
6417
  kind: "unsupported",
6332
- reason: `${stackName}/${logicalId}: REST v1 AWS integration targeting a non-Lambda service (Uri ${shortJson$1(uri)}) is not emulated locally in cdkl v1. Lambda non-proxy AWS integrations are supported; direct AWS service integrations (S3 / SQS / SNS / DynamoDB) require deploying to AWS. See https://github.com/go-to-k/cdkd/blob/main/docs/local-emulation.md.`
6418
+ reason: `${stackName}/${logicalId}: REST v1 AWS integration targeting a non-Lambda service (Uri ${shortJson$1(uri)}) is not emulated locally in ${getEmbedConfig().binaryName} v1. Lambda non-proxy AWS integrations are supported; direct AWS service integrations (S3 / SQS / SNS / DynamoDB) require deploying to AWS. See https://github.com/go-to-k/cdkd/blob/main/docs/local-emulation.md.`
6333
6419
  };
6334
6420
  const arnOutcome = resolveLambdaArnOutcome(uri);
6335
6421
  if (arnOutcome.kind === "unsupported") return {
@@ -6552,7 +6638,7 @@ function classifyServiceIntegrationRoute(baseRoute, integrationProps, stackName,
6552
6638
  if (!isSupportedSubtype(subtypeRaw)) return {
6553
6639
  ...baseRoute,
6554
6640
  lambdaLogicalId: "",
6555
- unsupported: { reason: `${declaredAt}: HTTP API v2 service integration subtype '${stringifyValue(subtypeRaw)}' is not supported by cdkl start-api (see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html for the supported list).` }
6641
+ unsupported: { reason: `${declaredAt}: HTTP API v2 service integration subtype '${stringifyValue(subtypeRaw)}' is not supported by ${getEmbedConfig().cliName} start-api (see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html for the supported list).` }
6556
6642
  };
6557
6643
  const requestParameters = integrationProps["RequestParameters"];
6558
6644
  if (!requestParameters || typeof requestParameters !== "object" || Array.isArray(requestParameters)) return {
@@ -6823,7 +6909,7 @@ function discoverOneApi(logicalId, resource, template, stackName) {
6823
6909
  const routes = collectRoutesForApi(logicalId, template, stackName);
6824
6910
  if (routes.length === 0) throw new Error(`${declaredAt}: WebSocket API has no AWS::ApiGatewayV2::Route children — at least one route (typically '$connect') is required to dispatch.`);
6825
6911
  const authRoutes = collectAuthRoutesForApi(logicalId, template, stackName);
6826
- const unsupported = authRoutes.length > 0 ? { reason: `WebSocket API requires authorizer support, which cdkl v1 does not emulate. Affected route(s): ${authRoutes.map((r) => `${r.routeKey} [AuthorizationType=${r.authorizationType}]`).join(", ")}. The API will be discovered but no upgrade requests will be accepted on this server.` } : void 0;
6912
+ const unsupported = authRoutes.length > 0 ? { reason: `WebSocket API requires authorizer support, which ${getEmbedConfig().binaryName} v1 does not emulate. Affected route(s): ${authRoutes.map((r) => `${r.routeKey} [AuthorizationType=${r.authorizationType}]`).join(", ")}. The API will be discovered but no upgrade requests will be accepted on this server.` } : void 0;
6827
6913
  return {
6828
6914
  apiLogicalId: logicalId,
6829
6915
  apiStackName: stackName,
@@ -6879,7 +6965,7 @@ function collectAuthRoutesForApi(apiLogicalId, template, _stackName) {
6879
6965
  * fuller JSONPath / VTL evaluator and are out of scope for v1.
6880
6966
  */
6881
6967
  function assertSupportedSelectionExpression(expr, declaredAt) {
6882
- if (!/^\$request\.body(?:\.[A-Za-z_][A-Za-z0-9_]*)+$/.test(expr)) throw new Error(`${declaredAt}: RouteSelectionExpression '${expr}' is not supported in cdkl start-api v1 — only '$request.body.<key>' shapes (optionally nested via dots) are recognized. File a follow-up issue if you need '$request.header.X' / '$context.X' / array-index access.`);
6968
+ if (!/^\$request\.body(?:\.[A-Za-z_][A-Za-z0-9_]*)+$/.test(expr)) throw new Error(`${declaredAt}: RouteSelectionExpression '${expr}' is not supported in ${getEmbedConfig().cliName} start-api v1 — only '$request.body.<key>' shapes (optionally nested via dots) are recognized. File a follow-up issue if you need '$request.header.X' / '$context.X' / array-index access.`);
6883
6969
  }
6884
6970
  /**
6885
6971
  * Parse a `$request.body.x.y` selection expression into the JSON-path
@@ -6933,7 +7019,7 @@ function collectRoutesForApi(apiLogicalId, template, stackName) {
6933
7019
  if (!integration || integration.Type !== "AWS::ApiGatewayV2::Integration") throw new Error(`${declaredAt}: Target points at '${targetLogicalId}' which is not an AWS::ApiGatewayV2::Integration.`);
6934
7020
  const integrationProps = integration.Properties ?? {};
6935
7021
  const integrationType = integrationProps["IntegrationType"];
6936
- if (integrationType !== "AWS_PROXY") throw new Error(`${declaredAt}: WebSocket route IntegrationType '${String(integrationType)}' is not supported in cdkl start-api v1 — only AWS_PROXY (Lambda) integrations are emulated.`);
7022
+ if (integrationType !== "AWS_PROXY") throw new Error(`${declaredAt}: WebSocket route IntegrationType '${String(integrationType)}' is not supported in ${getEmbedConfig().cliName} start-api v1 — only AWS_PROXY (Lambda) integrations are emulated.`);
6937
7023
  const arnOutcome = resolveLambdaArnIntrinsic(integrationProps["IntegrationUri"]);
6938
7024
  if (arnOutcome.kind === "unsupported") throw new Error(`${stackName}/${targetLogicalId}.IntegrationUri: ${arnOutcome.detail} — WebSocket routes must point at a same-template Lambda.`);
6939
7025
  result.push({
@@ -7920,6 +8006,85 @@ async function probeHostGatewaySupport() {
7920
8006
 
7921
8007
  //#endregion
7922
8008
  //#region src/local/vtl-engine.ts
8009
+ /**
8010
+ * AWS API Gateway VTL (Velocity Template Language) evaluator — hand-rolled
8011
+ * minimal subset for `cdkl start-api`'s REST v1 non-AWS_PROXY
8012
+ * integrations (#457).
8013
+ *
8014
+ * Background
8015
+ * ----------
8016
+ *
8017
+ * AWS API Gateway uses VTL to map between HTTP request / response shapes
8018
+ * and integration backend (Lambda non-proxy / HTTP / MOCK / AWS service)
8019
+ * request / response shapes. The full VTL spec is large; AWS API Gateway
8020
+ * exposes a SUBSET plus three AWS-specific built-in variables (`$input`,
8021
+ * `$context`, `$util`). cdk-local implements the SUBSET that real CDK apps
8022
+ * use in practice — anything outside it surfaces a clear error rather
8023
+ * than silently producing wrong output. See the
8024
+ * `VtlEvaluationError.message` field for the exact name when something
8025
+ * is unsupported.
8026
+ *
8027
+ * Supported VTL features
8028
+ * ----------------------
8029
+ *
8030
+ * - Variable references: `$var` / `${var}` / `$obj.field` /
8031
+ * `$obj.field.subField` (Velocity property notation).
8032
+ * - Method calls on built-ins: `$input.body` / `$input.json('$.path')` /
8033
+ * `$input.path('$.path')` / `$input.params()` / `$input.params('name')` /
8034
+ * `$input.params('header')` / `$context.*` / `$util.escapeJavaScript(x)` /
8035
+ * `$util.base64Encode(x)` / `$util.base64Decode(x)` / `$util.urlEncode(x)` /
8036
+ * `$util.urlDecode(x)` / `$util.parseJson(x)`.
8037
+ * - `#set($var = expr)` directives.
8038
+ * - `#if(cond) ... #elseif(cond) ... #else ... #end` blocks.
8039
+ * - `#foreach($x in $list) ... #end` loops.
8040
+ * - String / number / boolean / null literals: `"text"`, `'text'`, `42`,
8041
+ * `3.14`, `true`, `false`, `null`.
8042
+ * - Logical operators: `&&`, `||`, `!`.
8043
+ * - Comparison operators: `==`, `!=`, `<`, `<=`, `>`, `>=`.
8044
+ * - Implicit string concatenation (literal text + interpolated `$var`).
8045
+ *
8046
+ * NOT supported (intentionally)
8047
+ * -----------------------------
8048
+ *
8049
+ * - User-defined macros (`#macro`).
8050
+ * - `#parse` / `#include`.
8051
+ * - Velocity's arithmetic operators (`+ - * /`) outside literal concat.
8052
+ * - Range operator (`[1..5]`).
8053
+ * - `$velocityCount` and other Velocity context built-ins.
8054
+ *
8055
+ * AWS API Gateway-specific bindings
8056
+ * ---------------------------------
8057
+ *
8058
+ * `$input.body` — the raw request body as a string.
8059
+ * `$input.json('$.path.to.field')` — JSONPath against the body,
8060
+ * returned as a JSON-stringified value (so primitives are JSON-quoted).
8061
+ * `$input.path('$.path.to.field')` — JSONPath against the body, returned
8062
+ * as the native value (primitives unquoted).
8063
+ * `$input.params()` — `{header: {...}, querystring: {...}, path: {...}}`.
8064
+ * `$input.params('name')` — order: path > query > header (deployed
8065
+ * behavior; AWS docs).
8066
+ * `$input.params('header').<name>` — header lookup.
8067
+ * `$input.params('querystring').<name>` — querystring lookup.
8068
+ * `$input.params('path').<name>` — path-parameter lookup.
8069
+ *
8070
+ * `$context.requestId` — synthesized per request.
8071
+ * `$context.identity.sourceIp` — request client IP.
8072
+ * `$context.identity.userAgent` — request user agent.
8073
+ * `$context.httpMethod` — request method.
8074
+ * `$context.resourcePath` — the route's pathPattern.
8075
+ * `$context.stage` — the route's stage name (`$default` if no stage).
8076
+ *
8077
+ * `$util.escapeJavaScript(s)` — escape `\`, `'`, `"`, `\n`, `\r`, `\t`
8078
+ * for embedding inside a JavaScript string literal.
8079
+ * `$util.base64Encode(s)` — base64 (no padding stripped).
8080
+ * `$util.base64Decode(s)` — UTF-8 decode of base64.
8081
+ * `$util.urlEncode(s)` / `$util.urlDecode(s)` — RFC 3986 encoding.
8082
+ * `$util.parseJson(s)` — `JSON.parse(s)`.
8083
+ *
8084
+ * Mismatches between cdk-local's evaluator and AWS-deployed VTL surface as
8085
+ * `VtlEvaluationError`. Spurious whitespace / formatting differences are
8086
+ * acceptable and not in the contract.
8087
+ */
7923
8088
  /** Error thrown when a template references an unsupported VTL feature. */
7924
8089
  var VtlEvaluationError = class VtlEvaluationError extends Error {
7925
8090
  constructor(message) {
@@ -8063,7 +8228,7 @@ var VtlEvaluator = class {
8063
8228
  case "else":
8064
8229
  case "elseif":
8065
8230
  case "end": throw new VtlEvaluationError(`Unexpected #${name} outside of a #if / #foreach block`);
8066
- default: throw new VtlEvaluationError(`Unsupported VTL directive #${name} (cdkl start-api supports #set / #if / #elseif / #else / #foreach / #end / ##)`);
8231
+ default: throw new VtlEvaluationError(`Unsupported VTL directive #${name} (${getEmbedConfig().cliName} start-api supports #set / #if / #elseif / #else / #foreach / #end / ##)`);
8067
8232
  }
8068
8233
  }
8069
8234
  /**
@@ -8335,7 +8500,7 @@ var VtlEvaluator = class {
8335
8500
  * value must be a function or a special-cased built-in.
8336
8501
  */
8337
8502
  callValueAsMethod(value, argsRaw, refPath) {
8338
- if (typeof value !== "function") throw new VtlEvaluationError(`Reference '$${refPath}' is not callable (got ${typeof value}). cdk-local supports calling $input / $util / $context method-style references only.`);
8503
+ if (typeof value !== "function") throw new VtlEvaluationError(`Reference '$${refPath}' is not callable (got ${typeof value}). ${getEmbedConfig().productName} supports calling $input / $util / $context method-style references only.`);
8339
8504
  return value(...this.parseArgList(argsRaw));
8340
8505
  }
8341
8506
  /**
@@ -8654,7 +8819,7 @@ function applyJsonPath(root, expr) {
8654
8819
  if (c === ".") {
8655
8820
  i++;
8656
8821
  const m = /^[a-zA-Z_][a-zA-Z_0-9]*/.exec(trimmed.slice(i));
8657
- if (!m) throw new VtlEvaluationError(`Unsupported JSONPath syntax at position ${i}: '${trimmed}' (cdk-local supports $, $.field, $.field.sub, $.array[index] only).`);
8822
+ if (!m) throw new VtlEvaluationError(`Unsupported JSONPath syntax at position ${i}: '${trimmed}' (${getEmbedConfig().productName} supports $, $.field, $.field.sub, $.array[index] only).`);
8658
8823
  cursor = lookupField(cursor, m[0]);
8659
8824
  i += m[0].length;
8660
8825
  continue;
@@ -8668,7 +8833,7 @@ function applyJsonPath(root, expr) {
8668
8833
  if (Array.isArray(cursor)) cursor = cursor[idx];
8669
8834
  else cursor = null;
8670
8835
  } else if (inside.startsWith("\"") && inside.endsWith("\"") || inside.startsWith("'") && inside.endsWith("'")) cursor = lookupField(cursor, inside.slice(1, -1));
8671
- else throw new VtlEvaluationError(`Unsupported JSONPath bracket expression: '${inside}' (cdk-local supports integer indices and quoted string keys only).`);
8836
+ else throw new VtlEvaluationError(`Unsupported JSONPath bracket expression: '${inside}' (${getEmbedConfig().productName} supports integer indices and quoted string keys only).`);
8672
8837
  i = close + 1;
8673
8838
  continue;
8674
8839
  }
@@ -8802,7 +8967,7 @@ function evaluateResponseParameters(responseParameters, opts = {}) {
8802
8967
  for (const [key, value] of Object.entries(responseParameters)) {
8803
8968
  const headerMatch = /^method\.response\.header\.(.+)$/.exec(key);
8804
8969
  if (!headerMatch) {
8805
- opts.onUnsupported?.(key, value, `Only method.response.header.<name> keys are supported on REST v1 ResponseParameters; cdk-local cannot map ${key}.`);
8970
+ opts.onUnsupported?.(key, value, `Only method.response.header.<name> keys are supported on REST v1 ResponseParameters; ${getEmbedConfig().productName} cannot map ${key}.`);
8806
8971
  continue;
8807
8972
  }
8808
8973
  const headerName = headerMatch[1].toLowerCase();
@@ -8814,7 +8979,7 @@ function evaluateResponseParameters(responseParameters, opts = {}) {
8814
8979
  out[headerName] = value.slice(1, -1);
8815
8980
  continue;
8816
8981
  }
8817
- opts.onUnsupported?.(key, value, `ResponseParameter value '${value}' is a mapping expression (integration.response.* / context.*) which cdkl start-api does not emulate. Only single-quoted literals are honored.`);
8982
+ opts.onUnsupported?.(key, value, `ResponseParameter value '${value}' is a mapping expression (integration.response.* / context.*) which ${getEmbedConfig().cliName} start-api does not emulate. Only single-quoted literals are honored.`);
8818
8983
  }
8819
8984
  return out;
8820
8985
  }
@@ -9270,7 +9435,7 @@ function warnSsrfRiskyUri(uri, routeLabel, warn) {
9270
9435
  return;
9271
9436
  }
9272
9437
  const classification = classifyInternalHost(host);
9273
- if (classification !== void 0) warn(`Integration URI for ${routeLabel} points at ${host} — ${classification}. cdk-local does NOT block this; ensure the upstream is intentional.`);
9438
+ if (classification !== void 0) warn(`Integration URI for ${routeLabel} points at ${host} — ${classification}. ${getEmbedConfig().productName} does NOT block this; ensure the upstream is intentional.`);
9274
9439
  }
9275
9440
  function safeJsonParse(s) {
9276
9441
  try {
@@ -9318,8 +9483,8 @@ function applyRequestParameters(requestParameters, req, out) {
9318
9483
  const queryMatch = /^integration\.request\.querystring\.(.+)$/.exec(key);
9319
9484
  const pathMatch = /^integration\.request\.path\.(.+)$/.exec(key);
9320
9485
  if (headerMatch) out.headers[headerMatch[1].toLowerCase()] = resolved;
9321
- else if (queryMatch) logger.warn(`RequestParameter '${key}' (querystring rewrite) is recognized but cdk-local applies querystring rewrites only via URI placeholder substitution; ignoring.`);
9322
- else if (pathMatch) logger.warn(`RequestParameter '${key}' (path rewrite) is recognized but cdk-local substitutes path placeholders via {param} in the URI; ignoring.`);
9486
+ else if (queryMatch) logger.warn(`RequestParameter '${key}' (querystring rewrite) is recognized but ${getEmbedConfig().productName} applies querystring rewrites only via URI placeholder substitution; ignoring.`);
9487
+ else if (pathMatch) logger.warn(`RequestParameter '${key}' (path rewrite) is recognized but ${getEmbedConfig().productName} substitutes path placeholders via {param} in the URI; ignoring.`);
9323
9488
  else logger.warn(`Unsupported RequestParameter key '${key}'; skipping.`);
9324
9489
  }
9325
9490
  }
@@ -9431,7 +9596,7 @@ function createContainerPool(specs, options) {
9431
9596
  */
9432
9597
  async function startOne(spec) {
9433
9598
  const hostPort = await pickFreePort();
9434
- const name = `cdkl-${spec.lambda.logicalId}-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
9599
+ const name = `${getEmbedConfig().resourceNamePrefix}-${spec.lambda.logicalId}-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
9435
9600
  logger.debug(`Starting container ${name} for ${spec.lambda.logicalId} (kind=${spec.kind}) on ${spec.containerHost}:${hostPort}`);
9436
9601
  let containerId;
9437
9602
  if (spec.kind === "zip") {
@@ -9660,7 +9825,7 @@ function createContainerPool(specs, options) {
9660
9825
  let anyTimedOut = false;
9661
9826
  for (const r of drainResults) if (r.timedOut) {
9662
9827
  anyTimedOut = true;
9663
- logger.warn(`Container pool dispose timed out waiting for ${r.entry.inUse.size} in-flight handle(s) on ${r.entry.logicalId} after ${drainTimeoutMs}ms; tearing down anyway. The verify.sh \`docker rm -f cdkl-*\` sweep is the safety net.`);
9828
+ logger.warn(`Container pool dispose timed out waiting for ${r.entry.inUse.size} in-flight handle(s) on ${r.entry.logicalId} after ${drainTimeoutMs}ms; tearing down anyway. The verify.sh \`docker rm -f ${getEmbedConfig().resourceNamePrefix}-*\` sweep is the safety net.`);
9664
9829
  }
9665
9830
  if (!anyTimedOut) logger.debug(`In-flight drain completed in ${Date.now() - drainStart}ms`);
9666
9831
  }
@@ -10878,7 +11043,7 @@ function resolveRestV1Authorizer(authorizerLogicalId, template, stackName, decla
10878
11043
  declaredAt
10879
11044
  };
10880
11045
  }
10881
- throw new RouteDiscoveryError(`${stackName}/${authorizerLogicalId}: AWS::ApiGateway::Authorizer.Type '${String(type)}' is not supported by cdkl start-api (only TOKEN / REQUEST / COGNITO_USER_POOLS are accepted at the Authorizer resource).`);
11046
+ throw new RouteDiscoveryError(`${stackName}/${authorizerLogicalId}: AWS::ApiGateway::Authorizer.Type '${String(type)}' is not supported by ${getEmbedConfig().cliName} start-api (only TOKEN / REQUEST / COGNITO_USER_POOLS are accepted at the Authorizer resource).`);
10882
11047
  }
10883
11048
  /**
10884
11049
  * Resolve an `AWS::ApiGatewayV2::Authorizer`. HTTP v2 has only `REQUEST`
@@ -10919,7 +11084,7 @@ function resolveHttpApiAuthorizer(authorizerLogicalId, routeAuthorizationScopes,
10919
11084
  declaredAt
10920
11085
  };
10921
11086
  }
10922
- throw new RouteDiscoveryError(`${stackName}/${authorizerLogicalId}: AWS::ApiGatewayV2::Authorizer.AuthorizerType '${String(authType)}' is not supported by cdkl start-api (only REQUEST / JWT).`);
11087
+ throw new RouteDiscoveryError(`${stackName}/${authorizerLogicalId}: AWS::ApiGatewayV2::Authorizer.AuthorizerType '${String(authType)}' is not supported by ${getEmbedConfig().cliName} start-api (only REQUEST / JWT).`);
10923
11088
  }
10924
11089
  /**
10925
11090
  * Thrown by {@link resolveLambdaArn} when the authorizer's
@@ -11119,8 +11284,8 @@ function pickStringFromArn(value, location) {
11119
11284
  const arg = obj["Fn::GetAtt"];
11120
11285
  if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") {
11121
11286
  const logicalId = arg[0];
11122
- getLogger().warn(`${location}: uses Fn::GetAtt against logical ID '${logicalId}'. cdkl start-api cannot resolve the deployed user pool ARN — synthesizing an unreachable placeholder so JWKS pass-through admits every token. For real signature verification, set 'providerArns: [pool.userPoolArn]' explicitly on the CDK construct.`);
11123
- return `arn:aws:cognito-idp:us-east-1:000000000000:userpool/us-east-1_cdklplaceholder${logicalId}`;
11287
+ getLogger().warn(`${location}: uses Fn::GetAtt against logical ID '${logicalId}'. ${getEmbedConfig().cliName} start-api cannot resolve the deployed user pool ARN — synthesizing an unreachable placeholder so JWKS pass-through admits every token. For real signature verification, set 'providerArns: [pool.userPoolArn]' explicitly on the CDK construct.`);
11288
+ return `arn:aws:cognito-idp:us-east-1:000000000000:userpool/us-east-1_${getEmbedConfig().binaryName}placeholder${logicalId}`;
11124
11289
  }
11125
11290
  }
11126
11291
  }
@@ -11182,7 +11347,7 @@ function attachAuthorizers(stacks, routes) {
11182
11347
  errors.push(err instanceof Error ? err.message : String(err));
11183
11348
  }
11184
11349
  }
11185
- if (errors.length > 0) throw new RouteDiscoveryError(`cdkl start-api: ${errors.length} authorizer error(s):\n` + errors.map((e) => ` - ${e}`).join("\n"));
11350
+ if (errors.length > 0) throw new RouteDiscoveryError(`${getEmbedConfig().cliName} start-api: ${errors.length} authorizer error(s):\n` + errors.map((e) => ` - ${e}`).join("\n"));
11186
11351
  return out;
11187
11352
  }
11188
11353
  /**
@@ -13386,7 +13551,7 @@ function flattenHeadersForMapping(req) {
13386
13551
  * absent values.
13387
13552
  */
13388
13553
  function buildServiceIntegrationContextVars(req, route) {
13389
- const requestId = `cdkl-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
13554
+ const requestId = `${getEmbedConfig().binaryName}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
13390
13555
  const sourceIp = req.socket.remoteAddress ?? "127.0.0.1";
13391
13556
  return {
13392
13557
  requestId,
@@ -13987,7 +14152,7 @@ async function localStartApiCommand(target, options, extraStateProviders) {
13987
14152
  let apiFilter = target;
13988
14153
  if (options.api !== void 0) {
13989
14154
  if (target !== void 0) throw new Error(`Cannot specify both positional target ('${target}') and --api flag ('${options.api}'). Use one or the other. The positional form is preferred — '--api' is a deprecated alias.`);
13990
- logger.warn("[deprecated] --api <id> will be removed in a future major release. Use the positional argument instead: 'cdkl start-api <id>'.");
14155
+ logger.warn(`[deprecated] --api <id> will be removed in a future major release. Use the positional argument instead: '${getEmbedConfig().cliName} start-api <id>'.`);
13991
14156
  apiFilter = options.api;
13992
14157
  }
13993
14158
  warnIfDeprecatedRegion(options);
@@ -13997,7 +14162,7 @@ async function localStartApiCommand(target, options, extraStateProviders) {
13997
14162
  });
13998
14163
  await ensureDockerAvailable();
13999
14164
  const appCmd = resolveApp(options.app);
14000
- if (!appCmd) throw new Error("No CDK app specified. Pass --app, set CDKL_APP, or add \"app\" to cdk.json.");
14165
+ if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
14001
14166
  const overrides = readEnvOverridesFile$2(options.envVars);
14002
14167
  const debugPortBase = options.debugPortBase ? parseDebugPort(options.debugPortBase) : void 0;
14003
14168
  const perLambdaConcurrency = parsePerLambdaConcurrency(options.perLambdaConcurrency);
@@ -14040,13 +14205,13 @@ async function localStartApiCommand(target, options, extraStateProviders) {
14040
14205
  if (targetStacks.length === 0) throw new Error("No stacks matched. Pass --stack <name> (or --from-cfn-stack <name>) or run from a single-stack app.");
14041
14206
  const routedStackNames = targetStacks.map((s) => s.stackName);
14042
14207
  tryEmitFromCfnRedundancyTipOnce(options.fromCfnStack, routedStackNames, fromCfnTipEmitted, (routedStackName) => {
14043
- logger.info(`tip: --from-cfn-stack value matches the routed stack name (${routedStackName}); you can omit the value: \`cdkl start-api ... --from-cfn-stack\` (bare flag) resolves to the same value.`);
14208
+ logger.info(`tip: --from-cfn-stack value matches the routed stack name (${routedStackName}); you can omit the value: \`${getEmbedConfig().cliName} start-api ... --from-cfn-stack\` (bare flag) resolves to the same value.`);
14044
14209
  });
14045
14210
  const routes = discoverRoutes(targetStacks);
14046
14211
  const wsDiscovery = discoverWebSocketApis(targetStacks);
14047
14212
  if (wsDiscovery.errors.length > 0) for (const e of wsDiscovery.errors) logger.warn(`WebSocket discovery: ${e}`);
14048
14213
  const webSocketApis = wsDiscovery.apis;
14049
- if (routes.length === 0 && webSocketApis.length === 0) throw new Error("No supported API routes were discovered. cdkl start-api supports AWS::ApiGateway::* (REST v1), AWS::ApiGatewayV2::* (HTTP + WebSocket), and AWS::Lambda::Url (Function URL) with AWS_PROXY integrations only.");
14214
+ if (routes.length === 0 && webSocketApis.length === 0) throw new Error(`No supported API routes were discovered. ${getEmbedConfig().cliName} start-api supports AWS::ApiGateway::* (REST v1), AWS::ApiGatewayV2::* (HTTP + WebSocket), and AWS::Lambda::Url (Function URL) with AWS_PROXY integrations only.`);
14050
14215
  const stageMap = /* @__PURE__ */ new Map();
14051
14216
  for (const stack of targetStacks) {
14052
14217
  const m = buildStageMap(stack.template, options.stage);
@@ -14209,7 +14374,7 @@ async function localStartApiCommand(target, options, extraStateProviders) {
14209
14374
  warnUnsupportedWebSocketApis(initialWsApis, logger);
14210
14375
  if (initialWsApis.filter((api) => !api.unsupported).length > 0) {
14211
14376
  const probe = await probeHostGatewaySupport();
14212
- if (!probe.supported) throw new Error(`cdkl start-api requires Docker ${HOST_GATEWAY_MIN_VERSION.major}.${HOST_GATEWAY_MIN_VERSION.minor}+ for WebSocket API support (--add-host=host.docker.internal:host-gateway needs the 20.10 host-gateway alias). Detected server version: ${probe.rawVersion || "<empty — daemon unreachable or output stripped>"}. Upgrade Docker, or remove the WebSocket API from this app to fall back to HTTP-only start-api.`);
14377
+ if (!probe.supported) throw new Error(`${getEmbedConfig().cliName} start-api requires Docker ${HOST_GATEWAY_MIN_VERSION.major}.${HOST_GATEWAY_MIN_VERSION.minor}+ for WebSocket API support (--add-host=host.docker.internal:host-gateway needs the 20.10 host-gateway alias). Detected server version: ${probe.rawVersion || "<empty — daemon unreachable or output stripped>"}. Upgrade Docker, or remove the WebSocket API from this app to fall back to HTTP-only start-api.`);
14213
14378
  if (probe.parsed === null) logger.warn(`Docker server version "${probe.rawVersion}" did not match the canonical "<major>.<minor>" shape; assuming host-gateway support. If WebSocket containers fail to reach the local server, verify your Docker-compatible CLI honors --add-host=host.docker.internal:host-gateway.`);
14214
14379
  }
14215
14380
  for (const api of initialWsApis) {
@@ -14385,7 +14550,7 @@ async function localStartApiCommand(target, options, extraStateProviders) {
14385
14550
  if (shutdownStarted) {
14386
14551
  if (!forceExitArmed) {
14387
14552
  forceExitArmed = true;
14388
- logger.warn(`Received second ${signal}; force-exiting. Orphan containers may remain — run 'docker ps --filter name=cdkl-' and 'docker rm -f' to clean up.`);
14553
+ logger.warn(`Received second ${signal}; force-exiting. Orphan containers may remain — run 'docker ps --filter name=${getEmbedConfig().resourceNamePrefix}-' and 'docker rm -f' to clean up.`);
14389
14554
  process.exit(130);
14390
14555
  }
14391
14556
  return;
@@ -14579,11 +14744,11 @@ function warnIamRoutes(routesWithAuth) {
14579
14744
  }
14580
14745
  if (iamRoutes.length === 0 && oacRoutes.length === 0) return false;
14581
14746
  if (iamRoutes.length > 0) {
14582
- logger.warn(`${iamRoutes.length} route(s) declare AuthorizationType: AWS_IAM — cdkl start-api verifies SigV4 signatures against your local AWS credentials, but does NOT emulate IAM policy evaluation (resource / action / condition rules). Signature-verified callers reach the handler under their own identity; downstream authorization is the dev's responsibility.`);
14747
+ logger.warn(`${iamRoutes.length} route(s) declare AuthorizationType: AWS_IAM — ${getEmbedConfig().cliName} start-api verifies SigV4 signatures against your local AWS credentials, but does NOT emulate IAM policy evaluation (resource / action / condition rules). Signature-verified callers reach the handler under their own identity; downstream authorization is the dev's responsibility.`);
14583
14748
  for (const declaredAt of iamRoutes) logger.warn(` - ${declaredAt}`);
14584
14749
  }
14585
14750
  if (oacRoutes.length > 0) {
14586
- logger.warn(`${oacRoutes.length} Function URL route(s) with AuthType: AWS_IAM are fronted by a CloudFront Origin Access Control. In production CloudFront re-signs the origin request, so no local client signature can be verified — cdkl start-api passes these through (warn-and-pass) WITHOUT requiring --allow-unverified-sigv4. Do NOT trust the request identity in handler code.`);
14751
+ logger.warn(`${oacRoutes.length} Function URL route(s) with AuthType: AWS_IAM are fronted by a CloudFront Origin Access Control. In production CloudFront re-signs the origin request, so no local client signature can be verified — ${getEmbedConfig().cliName} start-api passes these through (warn-and-pass) WITHOUT requiring --allow-unverified-sigv4. Do NOT trust the request identity in handler code.`);
14587
14752
  for (const declaredAt of oacRoutes) logger.warn(` - ${declaredAt}`);
14588
14753
  }
14589
14754
  return true;
@@ -14710,7 +14875,7 @@ async function resolveContainerImageForStartApi(lambda, skipPull) {
14710
14875
  const logger = getLogger();
14711
14876
  const localBuild = await resolveLocalBuildPlan(lambda);
14712
14877
  if (localBuild) return { imageRef: await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, { architecture: lambda.architecture }) };
14713
- 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 cdkl can authenticate against. Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.`);
14878
+ 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.`);
14714
14879
  logger.info(`No matching cdk.out asset for ${lambda.imageUri}; falling back to ECR pull (same-acct/region only)...`);
14715
14880
  return { imageRef: await pullEcrImage(lambda.imageUri, { skipPull }) };
14716
14881
  }
@@ -14782,7 +14947,7 @@ async function materializeLambdaLayers(layers, layerTmpDirs, layerRoleArn) {
14782
14947
  });
14783
14948
  }
14784
14949
  if (flat.length === 1) return flat[0].assetPath;
14785
- const dir = mkdtempSync(path.join(tmpdir(), "cdkl-start-api-layers-"));
14950
+ const dir = mkdtempSync(path.join(tmpdir(), `${getEmbedConfig().resourceNamePrefix}-start-api-layers-`));
14786
14951
  for (const layer of flat) cpSync(layer.assetPath, dir, {
14787
14952
  recursive: true,
14788
14953
  force: true
@@ -14810,7 +14975,7 @@ function resolveLambdaByLogicalId(logicalId, stacks) {
14810
14975
  });
14811
14976
  const runtime = typeof props["Runtime"] === "string" ? props["Runtime"] : "";
14812
14977
  const handler = typeof props["Handler"] === "string" ? props["Handler"] : "";
14813
- if (!runtime) throw new Error(`Lambda '${logicalId}' has no Runtime property and no Code.ImageUri. cdkl start-api cannot tell if this is a ZIP or a container Lambda.`);
14978
+ if (!runtime) throw new Error(`Lambda '${logicalId}' has no Runtime property and no Code.ImageUri. ${getEmbedConfig().cliName} start-api cannot tell if this is a ZIP or a container Lambda.`);
14814
14979
  if (!handler) throw new Error(`Lambda '${logicalId}' has no Handler property.`);
14815
14980
  const inlineCode = typeof code["ZipFile"] === "string" ? code["ZipFile"] : void 0;
14816
14981
  let codePath = null;
@@ -14870,9 +15035,10 @@ function extractImageUri(value, logicalId, stackName, resources, region) {
14870
15035
  const pseudoParameters = derivePseudoParametersFromRegion(region);
14871
15036
  const joinResolved = tryResolveImageFnJoin(value, resources, pseudoParameters ? { pseudoParameters } : void 0);
14872
15037
  if (joinResolved.kind === "resolved") return joinResolved.uri;
14873
- if (joinResolved.kind === "needs-state") throw new Error(`Lambda '${logicalId}' in ${stackName} references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. cdkl start-api cannot resolve the repository URI without state — deploy the stack first, rebuild via lambda.DockerImageCode.fromImageAsset, or pin a public image.`);
14874
- if (joinResolved.kind === "unsupported-join") throw new Error(`Lambda '${logicalId}' in ${stackName} has an unsupported Fn::Join Code.ImageUri shape: ${joinResolved.reason}. cdkl start-api recognizes the canonical CDK 2.x lambda.DockerImageCode.fromEcr Fn::Join shape (delimiter "" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).`);
14875
- throw new Error(`Lambda '${logicalId}' in ${stackName} has an Fn::Join Code.ImageUri that cdkl start-api cannot resolve${pseudoParameters ? " (likely ${AWS::AccountId}, which cdkl cannot derive without a state source or STS)" : ` (cdkl could not derive AWS pseudo parameters because stack.region was undefined)`}. Workarounds: deploy first and run with a state-source flag (e.g. --from-cfn-stack or a host-provided extension), or pin a fully-literal public image URI.`);
15038
+ if (joinResolved.kind === "needs-state") throw new Error(`Lambda '${logicalId}' in ${stackName} references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. ${getEmbedConfig().cliName} start-api cannot resolve the repository URI without state — deploy the stack first, rebuild via lambda.DockerImageCode.fromImageAsset, or pin a public image.`);
15039
+ if (joinResolved.kind === "unsupported-join") throw new Error(`Lambda '${logicalId}' in ${stackName} has an unsupported Fn::Join Code.ImageUri shape: ${joinResolved.reason}. ${getEmbedConfig().cliName} start-api recognizes the canonical CDK 2.x lambda.DockerImageCode.fromEcr Fn::Join shape (delimiter "" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).`);
15040
+ const accountIdHint = pseudoParameters ? ` (likely \${AWS::AccountId}, which ${getEmbedConfig().binaryName} cannot derive without a state source or STS)` : ` (${getEmbedConfig().binaryName} could not derive AWS pseudo parameters because stack.region was undefined)`;
15041
+ throw new Error(`Lambda '${logicalId}' in ${stackName} has an Fn::Join Code.ImageUri that ${getEmbedConfig().cliName} start-api cannot resolve${accountIdHint}. Workarounds: deploy first and run with a state-source flag (e.g. --from-cfn-stack or a host-provided extension), or pin a fully-literal public image URI.`);
14876
15042
  }
14877
15043
  }
14878
15044
  }
@@ -14895,7 +15061,7 @@ function resolveImageLambda(args) {
14895
15061
  const first = arches[0];
14896
15062
  if (first === "arm64") architecture = "arm64";
14897
15063
  else if (first === "x86_64") architecture = "x86_64";
14898
- else throw new Error(`Lambda '${logicalId}' has unsupported Architectures value '${String(first)}'. cdkl start-api supports x86_64 and arm64.`);
15064
+ else throw new Error(`Lambda '${logicalId}' has unsupported Architectures value '${String(first)}'. ${getEmbedConfig().cliName} start-api supports x86_64 and arm64.`);
14899
15065
  }
14900
15066
  const ephemeralStorageMb = extractEphemeralStorageMb(props, logicalId);
14901
15067
  return {
@@ -14919,7 +15085,7 @@ function resolveImageLambda(args) {
14919
15085
  */
14920
15086
  function resolveAssetCodePath(stack, logicalId, resource) {
14921
15087
  const assetPath = resource.Metadata?.["aws:asset:path"];
14922
- if (typeof assetPath !== "string" || assetPath.length === 0) throw new Error(`Lambda '${logicalId}' has no Metadata['aws:asset:path']. cdkl start-api needs this hint to find the local asset directory. Re-synthesize the app and retry.`);
15088
+ if (typeof assetPath !== "string" || assetPath.length === 0) throw new Error(`Lambda '${logicalId}' has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} start-api needs this hint to find the local asset directory. Re-synthesize the app and retry.`);
14923
15089
  const cdkOutDir = stack.assetManifestPath ? path.dirname(stack.assetManifestPath) : process.cwd();
14924
15090
  return path.isAbsolute(assetPath) ? assetPath : path.resolve(cdkOutDir, assetPath);
14925
15091
  }
@@ -14976,7 +15142,7 @@ function materializeInlineCode(handler, source, fileExtension, tmpDirsOut) {
14976
15142
  const lastDot = handler.lastIndexOf(".");
14977
15143
  if (lastDot <= 0) throw new Error(`Handler '${handler}' is malformed: expected '<modulePath>.<exportName>'.`);
14978
15144
  const modulePath = handler.substring(0, lastDot);
14979
- const dir = mkdtempSync(path.join(tmpdir(), "cdkl-start-api-"));
15145
+ const dir = mkdtempSync(path.join(tmpdir(), `${getEmbedConfig().resourceNamePrefix}-start-api-`));
14980
15146
  tmpDirsOut.add(dir);
14981
15147
  const filePath = path.join(dir, `${modulePath}${fileExtension}`);
14982
15148
  mkdirSync(path.dirname(filePath), { recursive: true });
@@ -15085,7 +15251,7 @@ async function assumeLambdaExecutionRole(roleArn, region) {
15085
15251
  try {
15086
15252
  const creds = (await sts.send(new AssumeRoleCommand({
15087
15253
  RoleArn: roleArn,
15088
- RoleSessionName: `cdkl-start-api-${Date.now()}`,
15254
+ RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-start-api-${Date.now()}`,
15089
15255
  DurationSeconds: 3600
15090
15256
  }))).Credentials;
15091
15257
  if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) throw new Error(`AssumeRole(${roleArn}) returned no usable credentials.`);
@@ -15231,7 +15397,7 @@ async function reloadAllServers(args) {
15231
15397
  const newKeys = new Set(newByKey.keys());
15232
15398
  const added = [...newKeys].filter((k) => !oldKeys.has(k));
15233
15399
  const removed = [...oldKeys].filter((k) => !newKeys.has(k));
15234
- if (added.length > 0) logger.warn(`Reload detected new API surface(s): ${added.join(", ")}. Restart 'cdkl start-api' to serve them.`);
15400
+ if (added.length > 0) logger.warn(`Reload detected new API surface(s): ${added.join(", ")}. Restart '${getEmbedConfig().cliName} start-api' to serve them.`);
15235
15401
  if (removed.length > 0) logger.warn(`Reload detected removed API surface(s): ${removed.join(", ")}. Their servers will keep serving stale routes until restart.`);
15236
15402
  for (const booted of servers) {
15237
15403
  const group = newByKey.get(booted.group.serverKey);
@@ -15419,12 +15585,13 @@ function resolveMtlsConfig(options) {
15419
15585
  * Builder for the `start-api` subcommand.
15420
15586
  */
15421
15587
  function createLocalStartApiCommand(opts = {}) {
15422
- const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and AWS_IAM auth (REST v1 `AuthorizationType: AWS_IAM` and Function URL `AuthType: AWS_IAM` — SigV4 signature verification only; IAM policy evaluation is NOT emulated). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.").argument("[target]", "Optional API filter. Accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted, every discovered API gets its own server. Mirrors `cdkl invoke` / `cdkl run-task` target syntax.").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("--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 cdk.out/ or asset directories change. 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 <target> argument instead. 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 DescribeStackResources 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 DescribeStackResources 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=cdkl-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("--allow-unverified-sigv4", "Opt-in: allow AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id, OR no local AWS credentials configured) to pass through with a placeholder principalId. DEFAULT off — fail-closed so unauthenticated bypass is impossible against `event.requestContext.identity.accessKey`-trusting handler code. Use only in dev loops where you understand the risk.").default(false)).action(withErrorHandling(async (target, options) => {
15588
+ setEmbedConfig(opts.embedConfig);
15589
+ const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and AWS_IAM auth (REST v1 `AuthorizationType: AWS_IAM` and Function URL `AuthType: AWS_IAM` — SigV4 signature verification only; IAM policy evaluation is NOT emulated). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.").argument("[target]", `Optional API filter. Accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted, every discovered API gets its own server. Mirrors \`${getEmbedConfig().cliName} invoke\` / \`${getEmbedConfig().cliName} run-task\` target syntax.`).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("--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 cdk.out/ or asset directories change. 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 <target> argument instead. 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("--allow-unverified-sigv4", "Opt-in: allow AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id, OR no local AWS credentials configured) to pass through with a placeholder principalId. DEFAULT off — fail-closed so unauthenticated bypass is impossible against `event.requestContext.identity.accessKey`-trusting handler code. Use only in dev loops where you understand the risk.").default(false)).action(withErrorHandling(async (target, options) => {
15423
15590
  await localStartApiCommand(target, options, opts.extraStateProviders);
15424
15591
  }));
15425
15592
  [
15426
- ...commonOptions,
15427
- ...appOptions,
15593
+ ...commonOptions(),
15594
+ ...appOptions(),
15428
15595
  ...contextOptions
15429
15596
  ].forEach((opt) => startApi.addOption(opt));
15430
15597
  startApi.addOption(deprecatedRegionOption);
@@ -15504,7 +15671,7 @@ const SHARED_SVC_SUBNET_OCTET = 171;
15504
15671
  * CLI tears down ONCE at the end of the run.
15505
15672
  */
15506
15673
  async function createSharedSvcNetwork(options = {}) {
15507
- const networkName = `${options.prefix ?? "cdkl"}-svc-${randomBytes(4).toString("hex")}`;
15674
+ const networkName = `${options.prefix ?? getEmbedConfig().resourceNamePrefix}-svc-${randomBytes(4).toString("hex")}`;
15508
15675
  const { cidr, sidecarIp } = buildEndpointSubnet(171);
15509
15676
  return {
15510
15677
  networkName,
@@ -15544,7 +15711,7 @@ async function createNetworkAndSidecar(args) {
15544
15711
  ]);
15545
15712
  } catch (err) {
15546
15713
  const e = err;
15547
- throw new DockerRunnerError(`docker network create failed: ${e.stderr?.trim() || e.message || String(err)}. Hint: another cdk-local run may already own subnet ${cidr}; wait for it to finish, or remove the leftover network with \`docker network ls\` + \`docker network rm\`. \`cdkl start-service\` shares one network across every service in the run; bare \`cdkl run-task\` uses a per-task network so only one run can be active at a time.`);
15714
+ throw new DockerRunnerError(`docker network create failed: ${e.stderr?.trim() || e.message || String(err)}. Hint: another ${getEmbedConfig().productName} run may already own subnet ${cidr}; wait for it to finish, or remove the leftover network with \`docker network ls\` + \`docker network rm\`. \`${getEmbedConfig().cliName} start-service\` shares one network across every service in the run; bare \`${getEmbedConfig().cliName} run-task\` uses a per-task network so only one run can be active at a time.`);
15548
15715
  }
15549
15716
  const sidecarArgs = [
15550
15717
  "run",
@@ -15583,7 +15750,7 @@ async function createNetworkAndSidecar(args) {
15583
15750
  * lookup at container start doesn't race.
15584
15751
  */
15585
15752
  async function createTaskNetwork(options = {}) {
15586
- const networkName = `${options.prefix ?? "cdkl"}-task-${randomBytes(4).toString("hex")}`;
15753
+ const networkName = `${options.prefix ?? getEmbedConfig().resourceNamePrefix}-task-${randomBytes(4).toString("hex")}`;
15587
15754
  const { cidr, sidecarIp } = options.subnetOctet === void 0 ? {
15588
15755
  cidr: DEFAULT_METADATA_ENDPOINT_SUBNET,
15589
15756
  sidecarIp: METADATA_ENDPOINT_IP
@@ -16130,7 +16297,7 @@ async function prepareOneImage(task, container, options) {
16130
16297
  if (image.assetHash && dockerImages[image.assetHash]) asset = dockerImages[image.assetHash];
16131
16298
  else if (entries.length === 1) asset = entries[0][1];
16132
16299
  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.`);
16133
- const tag = `cdkl-run-task-${(image.assetHash ?? "single").slice(0, 16)}`;
16300
+ const tag = `${getEmbedConfig().resourceNamePrefix}-run-task-${(image.assetHash ?? "single").slice(0, 16)}`;
16134
16301
  const actualTag = await buildDockerImage(asset, cdkOutDir, {
16135
16302
  tag,
16136
16303
  ...options.platformOverride !== void 0 && { platform: options.platformOverride },
@@ -16169,7 +16336,7 @@ async function realizeDockerVolumes(volumes, state) {
16169
16336
  if (cfg?.driver) args.push("--driver", cfg.driver);
16170
16337
  if (cfg?.driverOpts) for (const [k, val] of Object.entries(cfg.driverOpts)) args.push("--opt", `${k}=${val}`);
16171
16338
  if (cfg?.labels) for (const [k, val] of Object.entries(cfg.labels)) args.push("--label", `${k}=${val}`);
16172
- const dockerVolumeName = `cdkl-${v.name}-${randHex(4)}`;
16339
+ const dockerVolumeName = `${getEmbedConfig().resourceNamePrefix}-${v.name}-${randHex(4)}`;
16173
16340
  args.push(dockerVolumeName);
16174
16341
  try {
16175
16342
  await execFileAsync$1(getDockerCmd(), args);
@@ -16209,7 +16376,7 @@ function groupSecretsByContainer(resolved) {
16209
16376
  function buildDockerRunArgs(opts) {
16210
16377
  const { task, container, image, network, volumeByName, secrets, containerHost, roleArn } = opts;
16211
16378
  const args = ["run", "-d"];
16212
- args.push("--name", `cdkl-${task.family}-${container.name}-${randHex(3)}`);
16379
+ args.push("--name", `${getEmbedConfig().resourceNamePrefix}-${task.family}-${container.name}-${randHex(3)}`);
16213
16380
  args.push("--network", network);
16214
16381
  args.push("--network-alias", container.name);
16215
16382
  if (opts.networkAliases && opts.networkAliases.length > 0) {
@@ -16341,7 +16508,7 @@ async function localRunTaskCommand(target, options, extraStateProviders) {
16341
16508
  });
16342
16509
  await ensureDockerAvailable();
16343
16510
  const appCmd = resolveApp(options.app);
16344
- if (!appCmd) throw new Error("No CDK app specified. Pass --app, set CDKL_APP, or add \"app\" to cdk.json.");
16511
+ if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
16345
16512
  logger.info("Synthesizing CDK app...");
16346
16513
  const synthesizer = new Synthesizer();
16347
16514
  const context = parseContextOptions(options.context);
@@ -16382,7 +16549,7 @@ async function localRunTaskCommand(target, options, extraStateProviders) {
16382
16549
  let assumedCredentials;
16383
16550
  let resolvedRoleArn;
16384
16551
  if (options.assumeTaskRole === true) {
16385
- 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 cdkl cannot resolve to an IAM Role at synth time. Pass the ARN explicitly: --assume-task-role <arn>");
16552
+ 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>`);
16386
16553
  resolvedRoleArn = await resolvePlaceholderAccount$1(task.taskRoleArn, options.region);
16387
16554
  assumedCredentials = await assumeTaskRole$1(resolvedRoleArn, options.region);
16388
16555
  } else if (typeof options.assumeTaskRole === "string") {
@@ -16412,7 +16579,7 @@ async function localRunTaskCommand(target, options, extraStateProviders) {
16412
16579
  };
16413
16580
  const result = await runEcsTask(task, runOpts, state);
16414
16581
  if (options.detach) {
16415
- logger.info("Task containers started in detached mode; cdkl is exiting.");
16582
+ logger.info(`Task containers started in detached mode; ${getEmbedConfig().binaryName} is exiting.`);
16416
16583
  logger.info(`Use 'docker ps --filter network=${result.state.network?.networkName ?? "<network>"}' to inspect; tear down with 'docker rm -f' and 'docker network rm'.`);
16417
16584
  sigintCount = 99;
16418
16585
  return;
@@ -16451,7 +16618,7 @@ async function assumeTaskRole$1(roleArn, region) {
16451
16618
  try {
16452
16619
  const creds = (await sts.send(new AssumeRoleCommand({
16453
16620
  RoleArn: roleArn,
16454
- RoleSessionName: `cdkl-run-task-${Date.now()}`,
16621
+ RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-run-task-${Date.now()}`,
16455
16622
  DurationSeconds: 3600
16456
16623
  }))).Credentials;
16457
16624
  if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) throw new Error(`AssumeRole(${roleArn}) returned no usable credentials.`);
@@ -16478,7 +16645,7 @@ async function buildEcsImageResolutionContext$1(candidate, stateProvider, option
16478
16645
  const wantsPseudoForEnvOrSecret = !!stateProvider && needs.needsEnvOrSecretSubstitution;
16479
16646
  if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
16480
16647
  const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
16481
- if (!region) logger.warn("Resolver references ${AWS::Region} but cdkl could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.");
16648
+ 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.`);
16482
16649
  let accountId;
16483
16650
  try {
16484
16651
  accountId = await resolveCallerAccountId$1(region);
@@ -16566,12 +16733,13 @@ async function resolveSidecarCredentials(options, assumedCredentials) {
16566
16733
  if (options.profile) return resolveProfileCredentials(options.profile);
16567
16734
  }
16568
16735
  function createLocalRunTaskCommand(opts = {}) {
16569
- 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.").argument("<target>", "CDK display path or stack-qualified logical ID of the AWS::ECS::TaskDefinition to run").addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default("cdkl")).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 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 DescribeStackResources 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 cdkl stack name; pass an explicit value when the CFn stack name differs. Fn::GetAtt is warn-and-dropped in v1 (CFn DescribeStackResources 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.")).action(withErrorHandling(async (target, options) => {
16736
+ setEmbedConfig(opts.embedConfig);
16737
+ 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.").argument("<target>", "CDK display path or stack-qualified logical ID of the AWS::ECS::TaskDefinition to run").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 function role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--keep-running", "Don't docker rm -f the user containers on task exit (network + sidecar are still torn down). Use when you want to docker exec into a stopped container for post-mortems.").default(false)).addOption(new Option("--detach", "Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", `Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (\`cdk deploy\`). Bare form uses the ${getEmbedConfig().binaryName} stack name; pass an explicit value when the CFn stack name differs. Fn::GetAtt is warn-and-dropped 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.")).action(withErrorHandling(async (target, options) => {
16570
16738
  await localRunTaskCommand(target, options, opts.extraStateProviders);
16571
16739
  }));
16572
16740
  [
16573
- ...commonOptions,
16574
- ...appOptions,
16741
+ ...commonOptions(),
16742
+ ...appOptions(),
16575
16743
  ...contextOptions
16576
16744
  ].forEach((opt) => cmd.addOption(opt));
16577
16745
  cmd.addOption(deprecatedRegionOption);
@@ -16615,7 +16783,7 @@ function resolveEcsServiceTarget(target, stacks, context) {
16615
16783
  serviceLogicalId = parsed.pathOrId;
16616
16784
  }
16617
16785
  if (!serviceLogicalId || !serviceResource) throw notFoundError(target, stack, resources);
16618
- if (serviceResource.Type === "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource '${serviceLogicalId}' in ${stack.stackName} is an ECS TaskDefinition, not a Service. Use \`cdkl run-task\` for one-shot tasks; \`cdkl start-service\` is Service-only.`);
16786
+ 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.`);
16619
16787
  if (serviceResource.Type !== "AWS::ECS::Service") throw new EcsTaskResolutionError(`Resource '${serviceLogicalId}' in ${stack.stackName} is ${serviceResource.Type}, not an AWS::ECS::Service.`);
16620
16788
  return extractServiceProperties(stack, serviceLogicalId, serviceResource, stacks, context);
16621
16789
  }
@@ -16674,7 +16842,7 @@ function extractServiceConnect(raw, task) {
16674
16842
  const cfg = raw;
16675
16843
  if (cfg["Enabled"] === false) return void 0;
16676
16844
  const namespaceName = pickServiceConnectNamespace(cfg["Namespace"]);
16677
- if (!namespaceName) throw new EcsTaskResolutionError(`ServiceConnectConfiguration.Namespace must be a literal string (the Cloud Map namespace name like 'cdkl.local'); got ${JSON.stringify(cfg["Namespace"])}. Intrinsic / cross-stack namespace references are not supported in v1.`);
16845
+ if (!namespaceName) throw new EcsTaskResolutionError(`ServiceConnectConfiguration.Namespace must be a literal string (the Cloud Map namespace name like '${getEmbedConfig().resourceNamePrefix}.local'); got ${JSON.stringify(cfg["Namespace"])}. Intrinsic / cross-stack namespace references are not supported in v1.`);
16678
16846
  const rawServices = cfg["Services"];
16679
16847
  if (!Array.isArray(rawServices) || rawServices.length === 0) return {
16680
16848
  namespaceName,
@@ -16735,7 +16903,7 @@ function extractServiceRegistries(raw, serviceLogicalId, warnings) {
16735
16903
  const registryArn = e["RegistryArn"];
16736
16904
  let cloudMapServiceLogicalId;
16737
16905
  if (typeof registryArn === "string") {
16738
- warnings.push(`ECS Service '${serviceLogicalId}' ServiceRegistries[] entry has a literal-string RegistryArn ('${registryArn}'); cdk-local cannot resolve external Cloud Map services locally. Skipping this registration; peer services will not discover this endpoint through the in-process registry. Use Fn::GetAtt: [<CloudMapServiceLogicalId>, "Arn"] instead so cdk-local can resolve the namespace + service name from the synthesized template.`);
16906
+ warnings.push(`ECS Service '${serviceLogicalId}' ServiceRegistries[] entry has a literal-string RegistryArn ('${registryArn}'); ${getEmbedConfig().productName} cannot resolve external Cloud Map services locally. Skipping this registration; peer services will not discover this endpoint through the in-process registry. Use Fn::GetAtt: [<CloudMapServiceLogicalId>, "Arn"] instead so ${getEmbedConfig().productName} can resolve the namespace + service name from the synthesized template.`);
16739
16907
  continue;
16740
16908
  }
16741
16909
  if (registryArn && typeof registryArn === "object" && !Array.isArray(registryArn)) {
@@ -16776,7 +16944,7 @@ function resolveTaskDefinitionReference(taskDefRef, stack, serviceLogicalId) {
16776
16944
  return refValue;
16777
16945
  }
16778
16946
  }
16779
- throw new EcsTaskResolutionError(`ECS Service '${serviceLogicalId}' has an unsupported TaskDefinition reference shape: ${JSON.stringify(taskDefRef)}. cdkl start-service v1 supports only Ref to a same-stack AWS::ECS::TaskDefinition; cross-stack TaskDefinitions are deferred.`);
16947
+ throw new EcsTaskResolutionError(`ECS Service '${serviceLogicalId}' has an unsupported TaskDefinition reference shape: ${JSON.stringify(taskDefRef)}. ${getEmbedConfig().cliName} start-service v1 supports only Ref to a same-stack AWS::ECS::TaskDefinition; cross-stack TaskDefinitions are deferred.`);
16780
16948
  }
16781
16949
  function parseDesiredCount(raw, serviceLogicalId) {
16782
16950
  if (raw === void 0 || raw === null) return 1;
@@ -17189,7 +17357,7 @@ async function publishReplicaToCloudMap(service, instance, discovery, ownerKeyPr
17189
17357
  if (service.serviceRegistries.length > 0) {
17190
17358
  const index = discovery.cloudMapIndexByStack.get(service.stack.stackName);
17191
17359
  if (!index) {
17192
- logger.warn(`ECS Service '${service.serviceLogicalId}' declares ServiceRegistries[] but cdk-local has no Cloud Map index for stack ${service.stack.stackName}. Skipping registration.`);
17360
+ logger.warn(`ECS Service '${service.serviceLogicalId}' declares ServiceRegistries[] but ${getEmbedConfig().productName} has no Cloud Map index for stack ${service.stack.stackName}. Skipping registration.`);
17193
17361
  return;
17194
17362
  }
17195
17363
  let j = 0;
@@ -17292,7 +17460,7 @@ function pickEssentialContainerId(instance, service) {
17292
17460
  const defaultWaitForExitImpl = async (containerId) => {
17293
17461
  const { execFile } = await import("node:child_process");
17294
17462
  const { promisify } = await import("node:util");
17295
- const { getDockerCmd } = await import("./docker-cmd-DTPLXRqh.js").then((n) => n.t);
17463
+ const { getDockerCmd } = await import("./docker-cmd-o4ovyAhR.js").then((n) => n.t);
17296
17464
  const { stdout } = await promisify(execFile)(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
17297
17465
  const code = parseInt(stdout.trim(), 10);
17298
17466
  return Number.isFinite(code) ? code : -1;
@@ -17542,7 +17710,7 @@ function buildCloudMapIndex(stack) {
17542
17710
  const warnings = [];
17543
17711
  const resources = stack.template.Resources ?? {};
17544
17712
  for (const [logicalId, resource] of Object.entries(resources)) {
17545
- if (resource.Type === "AWS::ServiceDiscovery::PublicDnsNamespace") throw new EcsTaskResolutionError(`Stack ${stack.stackName}: AWS::ServiceDiscovery::PublicDnsNamespace '${logicalId}' is not supported by local emulation — public DNS defeats the "local" point. Use a PrivateDnsNamespace for cdkl start-service.`);
17713
+ if (resource.Type === "AWS::ServiceDiscovery::PublicDnsNamespace") throw new EcsTaskResolutionError(`Stack ${stack.stackName}: AWS::ServiceDiscovery::PublicDnsNamespace '${logicalId}' is not supported by local emulation — public DNS defeats the "local" point. Use a PrivateDnsNamespace for ${getEmbedConfig().cliName} start-service.`);
17546
17714
  if (resource.Type === "AWS::ServiceDiscovery::HttpNamespace") throw new EcsTaskResolutionError(`Stack ${stack.stackName}: AWS::ServiceDiscovery::HttpNamespace '${logicalId}' is not supported by local emulation — HttpNamespace uses the AWS Cloud Map DiscoverInstances API directly (no DNS), which would require shimming the AWS SDK inside every container. Use a PrivateDnsNamespace + DnsConfig instead.`);
17547
17715
  if (resource.Type !== "AWS::ServiceDiscovery::PrivateDnsNamespace") continue;
17548
17716
  const props = resource.Properties ?? {};
@@ -17636,7 +17804,7 @@ async function localStartServiceCommand(targets, options, extraStateProviders) {
17636
17804
  if (options.verbose) logger.setLevel("debug");
17637
17805
  warnIfDeprecatedRegion(options);
17638
17806
  const skipPull = options.pull === false;
17639
- if (!targets || targets.length === 0) throw new LocalStartServiceError("cdkl start-service requires at least one <target>. Pass one or more service paths like 'Stack/Orders' 'Stack/Frontend'.");
17807
+ if (!targets || targets.length === 0) throw new LocalStartServiceError(`${getEmbedConfig().cliName} start-service requires at least one <target>. Pass one or more service paths like 'Stack/Orders' 'Stack/Frontend'.`);
17640
17808
  rejectExplicitCfnStackWithMultipleStacks(options, targets.length);
17641
17809
  const perTarget = targets.map((t) => ({
17642
17810
  target: t,
@@ -17678,7 +17846,7 @@ async function localStartServiceCommand(targets, options, extraStateProviders) {
17678
17846
  });
17679
17847
  await ensureDockerAvailable();
17680
17848
  const appCmd = resolveApp(options.app);
17681
- if (!appCmd) throw new Error("No CDK app specified. Pass --app, set CDKL_APP, or add \"app\" to cdk.json.");
17849
+ if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
17682
17850
  logger.info("Synthesizing CDK app...");
17683
17851
  const synthesizer = new Synthesizer();
17684
17852
  const context = parseContextOptions(options.context);
@@ -17826,7 +17994,7 @@ async function assumeTaskRole(roleArn, region) {
17826
17994
  try {
17827
17995
  const creds = (await sts.send(new AssumeRoleCommand({
17828
17996
  RoleArn: roleArn,
17829
- RoleSessionName: `cdkl-start-service-${Date.now()}`,
17997
+ RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-start-service-${Date.now()}`,
17830
17998
  DurationSeconds: 3600
17831
17999
  }))).Credentials;
17832
18000
  if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) throw new LocalStartServiceError(`AssumeRole(${roleArn}) returned no usable credentials.`);
@@ -17852,7 +18020,7 @@ async function buildEcsImageResolutionContext(target, stacks, options, stateProv
17852
18020
  const wantsPseudoForEnvOrSecret = !!stateProvider && needs.needsEnvOrSecretSubstitution;
17853
18021
  if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
17854
18022
  const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
17855
- if (!region) logger.warn("Resolver references ${AWS::Region} but cdkl could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.");
18023
+ 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.`);
17856
18024
  let accountId;
17857
18025
  try {
17858
18026
  accountId = await resolveCallerAccountId(region);
@@ -17959,12 +18127,13 @@ async function resolveSharedSidecarCredentials(options) {
17959
18127
  if (options.profile) return resolveProfileCredentials(options.profile);
17960
18128
  }
17961
18129
  function createLocalStartServiceCommand(opts = {}) {
17962
- const cmd = new Command("start-service").description("Run one or more AWS::ECS::Service resources locally as a long-running emulator. Spins up DesiredCount task replicas per service (clamped by --max-tasks) using the same per-task docker network + metadata sidecar pattern as `cdkl run-task`, then keeps each replica running and restarts it on exit per --restart-policy. ^C tears every replica + sidecar + network down. Each <target> accepts a CDK display path (MyStack/MyService) or stack-qualified logical ID (MyStack:MyServiceXYZ); single-stack apps may omit the stack prefix. When two or more <target>s are supplied, every service is booted into a shared Cloud Map / Service Connect registry so peer services discover each other via docker --add-host overlay.").argument("<targets...>", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ECS::Service resources to run").addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default("cdkl")).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 DescribeStackResources 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 cdkl stack name; pass an explicit value when the CFn stack name differs. Fn::GetAtt is warn-and-dropped in v1 (CFn DescribeStackResources 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.")).action(withErrorHandling(async (targets, options) => {
18130
+ setEmbedConfig(opts.embedConfig);
18131
+ const cmd = new Command("start-service").description(`Run one or more AWS::ECS::Service resources locally as a long-running emulator. Spins up DesiredCount task replicas per service (clamped by --max-tasks) using the same per-task docker network + metadata sidecar pattern as \`${getEmbedConfig().cliName} run-task\`, then keeps each replica running and restarts it on exit per --restart-policy. ^C tears every replica + sidecar + network down. Each <target> accepts a CDK display path (MyStack/MyService) or stack-qualified logical ID (MyStack:MyServiceXYZ); single-stack apps may omit the stack prefix. When two or more <target>s are supplied, every service is booted into a shared Cloud Map / Service Connect registry so peer services discover each other via docker --add-host overlay.`).argument("<targets...>", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ECS::Service resources to run").addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default(getEmbedConfig().resourceNamePrefix)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed task role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--max-tasks <n>", `Hard cap on local replica count. Caps the template DesiredCount so local dev machines don't run an unbounded number of containers. Cannot exceed ${83} due to the per-replica link-local /24 subnet allocator's range.`).default(3).argParser(parseMaxTasks)).addOption(new Option("--restart-policy <policy>", "How to react when an essential container exits. 'on-failure' (default) restarts only on non-zero exit; 'always' restarts on every exit; 'none' shuts the replica down and runs the service degraded.").default("on-failure").argParser(parseRestartPolicy)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", `Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (\`cdk deploy\`). Bare form uses the ${getEmbedConfig().binaryName} stack name; pass an explicit value when the CFn stack name differs. Fn::GetAtt is warn-and-dropped 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.")).action(withErrorHandling(async (targets, options) => {
17963
18132
  await localStartServiceCommand(targets, options, opts.extraStateProviders);
17964
18133
  }));
17965
18134
  [
17966
- ...commonOptions,
17967
- ...appOptions,
18135
+ ...commonOptions(),
18136
+ ...appOptions(),
17968
18137
  ...contextOptions
17969
18138
  ].forEach((opt) => cmd.addOption(opt));
17970
18139
  cmd.addOption(deprecatedRegionOption);
@@ -17973,4 +18142,4 @@ function createLocalStartServiceCommand(opts = {}) {
17973
18142
 
17974
18143
  //#endregion
17975
18144
  export { LocalStateSourceError as a, rejectExplicitCfnStackWithMultipleStacks as c, CfnLocalStateProvider as d, createLocalInvokeCommand as i, resolveCfnRegion as l, createLocalRunTaskCommand as n, createLocalStateProvider as o, createLocalStartApiCommand as r, isCfnFlagPresent as s, createLocalStartServiceCommand as t, resolveCfnStackName as u };
17976
- //# sourceMappingURL=local-start-service-zoDos4zT.js.map
18145
+ //# sourceMappingURL=local-start-service-gYRarUgM.js.map