@go-to-k/cdkd 0.94.15 → 0.95.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -420,6 +420,8 @@ and same-stack Lambda Layers bind-mounted at `/opt`.
420
420
  ```bash
421
421
  cdkd local start-api # one HTTP server per discovered API
422
422
  cdkd local start-api --port 3000 # pin the first server's port
423
+ cdkd local start-api --api MyHttpApi # filter by logical id
424
+ cdkd local start-api --api MyStack/MyHttpApi # OR: CDK Construct path
423
425
  cdkd local start-api --warm --watch # pre-start + hot reload
424
426
  ```
425
427
 
package/dist/cli.js CHANGED
@@ -35003,7 +35003,7 @@ function discoverRoutes(stacks) {
35003
35003
  routes.push(...discoverHttpApiRoute(logicalId, resource, template, stack.stackName));
35004
35004
  break;
35005
35005
  case "AWS::Lambda::Url":
35006
- routes.push(...discoverFunctionUrl(logicalId, resource, stack.stackName));
35006
+ routes.push(...discoverFunctionUrl(logicalId, resource, template, stack.stackName));
35007
35007
  break;
35008
35008
  default: break;
35009
35009
  }
@@ -35040,14 +35040,19 @@ function discoverRestV1Method(logicalId, resource, template, stackName) {
35040
35040
  if (!restApiLogicalId) throw new Error(`${stackName}/${logicalId} (AWS::ApiGateway::Method): RestApiId must be a { Ref: '...' } reference (got ${shortJson$1(restApiId)}).`);
35041
35041
  const resourceId = props["ResourceId"];
35042
35042
  const path = buildRestV1Path(resourceId, restApiLogicalId, template, stackName, logicalId);
35043
+ const httpMethod = stringifyValue(props["HttpMethod"] ?? "ANY");
35044
+ const stage = pickRestV1Stage(restApiLogicalId, template);
35045
+ const restApiCdkPath = readApiCdkPath(restApiLogicalId, template);
35043
35046
  return [{
35044
- method: stringifyValue(props["HttpMethod"] ?? "ANY"),
35047
+ method: httpMethod,
35045
35048
  pathPattern: path,
35046
35049
  lambdaLogicalId,
35047
35050
  source: "rest-v1",
35048
35051
  apiVersion: "v1",
35049
- stage: pickRestV1Stage(restApiLogicalId, template),
35052
+ stage,
35050
35053
  apiLogicalId: restApiLogicalId,
35054
+ apiStackName: stackName,
35055
+ ...restApiCdkPath !== void 0 && { apiCdkPath: restApiCdkPath },
35051
35056
  declaredAt: `${stackName}/${logicalId}`
35052
35057
  }];
35053
35058
  }
@@ -35139,6 +35144,7 @@ function discoverHttpApiRoute(logicalId, resource, template, stackName) {
35139
35144
  if (integrationProps["IntegrationSubtype"] !== void 0) throw new Error(`${stackName}/${logicalId} (AWS::ApiGatewayV2::Route): IntegrationSubtype '${stringifyValue(integrationProps["IntegrationSubtype"])}' is not supported (ApiGatewayV2 service integrations like SQS/EventBridge cannot run locally).`);
35140
35145
  const lambdaLogicalId = resolveLambdaArnIntrinsic(integrationProps["IntegrationUri"], `${stackName}/${integrationLogicalId}.IntegrationUri`);
35141
35146
  const { method, pathPattern } = parseRouteKey(routeKey);
35147
+ const apiCdkPath = readApiCdkPath(apiLogicalId, template);
35142
35148
  return [{
35143
35149
  method,
35144
35150
  pathPattern,
@@ -35147,6 +35153,8 @@ function discoverHttpApiRoute(logicalId, resource, template, stackName) {
35147
35153
  apiVersion: "v2",
35148
35154
  stage: "$default",
35149
35155
  apiLogicalId,
35156
+ apiStackName: stackName,
35157
+ ...apiCdkPath !== void 0 && { apiCdkPath },
35150
35158
  declaredAt: `${stackName}/${logicalId}`
35151
35159
  }];
35152
35160
  }
@@ -35159,23 +35167,38 @@ function discoverHttpApiRoute(logicalId, resource, template, stackName) {
35159
35167
  * we cannot do locally, and RESPONSE_STREAM uses a streaming response shape
35160
35168
  * (`InvokeWithResponseStream`) the RIE container does not implement.
35161
35169
  */
35162
- function discoverFunctionUrl(logicalId, resource, stackName) {
35170
+ function discoverFunctionUrl(logicalId, resource, template, stackName) {
35163
35171
  const props = resource.Properties ?? {};
35164
35172
  const authType = props["AuthType"];
35165
35173
  if (authType !== "NONE") throw new Error(`${stackName}/${logicalId} (AWS::Lambda::Url): AuthType '${String(authType)}' is not supported (only NONE — IAM auth requires SigV4 verification cdkd cannot emulate locally; deferred follow-up PR).`);
35166
35174
  if (props["InvokeMode"] === "RESPONSE_STREAM") throw new Error(`${stackName}/${logicalId} (AWS::Lambda::Url): InvokeMode RESPONSE_STREAM is not supported (deferred follow-up PR).`);
35167
35175
  const targetArn = props["TargetFunctionArn"];
35176
+ const lambdaLogicalId = resolveLambdaArnIntrinsic(targetArn, `${stackName}/${logicalId}.TargetFunctionArn`);
35177
+ const lambdaCdkPath = readApiCdkPath(lambdaLogicalId, template);
35168
35178
  return [{
35169
35179
  method: "ANY",
35170
35180
  pathPattern: "/{proxy+}",
35171
- lambdaLogicalId: resolveLambdaArnIntrinsic(targetArn, `${stackName}/${logicalId}.TargetFunctionArn`),
35181
+ lambdaLogicalId,
35172
35182
  source: "function-url",
35173
35183
  apiVersion: "v2",
35174
35184
  stage: "$default",
35185
+ apiStackName: stackName,
35186
+ ...lambdaCdkPath !== void 0 && { apiCdkPath: lambdaCdkPath },
35175
35187
  declaredAt: `${stackName}/${logicalId}`
35176
35188
  }];
35177
35189
  }
35178
35190
  /**
35191
+ * Read the `aws:cdk:path` metadata of the resource at `logicalId`,
35192
+ * returning the empty string when the resource is missing or the
35193
+ * metadata isn't set. Hides the "may be missing for a hand-rolled
35194
+ * `cfn.Resource`" branch from every call site.
35195
+ */
35196
+ function readApiCdkPath(logicalId, template) {
35197
+ const resource = template.Resources?.[logicalId];
35198
+ if (!resource) return void 0;
35199
+ return readCdkPath(resource) || void 0;
35200
+ }
35201
+ /**
35179
35202
  * Local intrinsic resolver for `IntegrationUri` (and the equivalent
35180
35203
  * `Uri` field on REST v1 Method.Integration). Delegates to the shared
35181
35204
  * `resolveLambdaArnIntrinsic` in `intrinsic-lambda-arn.ts` (extracted in
@@ -37901,39 +37924,74 @@ function groupRoutesByServer(routes) {
37901
37924
  /**
37902
37925
  * Filter the route list to a single API by user-supplied identifier.
37903
37926
  *
37904
- * Matches against both the parent API logical id (HTTP API / REST v1)
37905
- * AND the Function URL's backing-Lambda logical id, so users can pass
37906
- * any of:
37907
- *
37908
- * - The HTTP API logical id (e.g. `MyHttpApi`)
37909
- * - The REST API logical id (e.g. `MyRestApi`)
37910
- * - The Lambda logical id backing a Function URL (e.g. `GoHandler`)
37927
+ * Accepts four input forms matches the rest of the `cdkd local *`
37928
+ * target-resolution family (`local invoke <target>` /
37929
+ * `local run-task <target>`) for consistency:
37930
+ *
37931
+ * 1. **Bare logical id** (`MyHttpApi`) — exact match on the parent
37932
+ * API's logical id, or on the backing Lambda's logical id for
37933
+ * Function URLs.
37934
+ * 2. **Stack-qualified logical id** (`MyStack:MyHttpApi`) — exact
37935
+ * match on `<stackName>:<logicalId>`. Useful in multi-stack apps
37936
+ * where the same bare logical id appears in two stacks.
37937
+ * 3. **CDK Construct path / display path** (`MyStack/MyHttpApi`) —
37938
+ * exact match on the resource's `aws:cdk:path` metadata.
37939
+ * 4. **CDK Construct path prefix** — when the input is a strict
37940
+ * ancestor of the resource's `aws:cdk:path` (i.e.
37941
+ * `cdkPath.startsWith(input + '/')`). Mirrors the prefix rule
37942
+ * `cdkd orphan` uses so an L2 wrapper path resolves to its L1
37943
+ * child (`MyStack/MyHttpApi` matches `MyStack/MyHttpApi/Resource`).
37944
+ *
37945
+ * Routes discovered before this field set was added (or routes where
37946
+ * the synthesized template doesn't carry `aws:cdk:path` metadata —
37947
+ * e.g. hand-rolled `cfn.Resource` defs) silently fall through to the
37948
+ * bare-logical-id-only path so the change is non-breaking.
37911
37949
  *
37912
37950
  * Returns an empty array when no route matches — the caller is
37913
37951
  * responsible for surfacing a "no API matched" error with the list of
37914
37952
  * available identifiers (see {@link availableApiIdentifiers}).
37915
37953
  */
37916
37954
  function filterRoutesByApiIdentifier(routes, identifier) {
37917
- return routes.filter((rwa) => {
37918
- const r = rwa.route;
37919
- if (r.source === "function-url") return r.lambdaLogicalId === identifier;
37920
- return r.apiLogicalId === identifier;
37921
- });
37955
+ return routes.filter((rwa) => routeMatchesIdentifier(rwa.route, identifier));
37956
+ }
37957
+ /**
37958
+ * Predicate behind {@link filterRoutesByApiIdentifier} and
37959
+ * {@link availableApiIdentifiers}'s primary-form selection. Exported
37960
+ * for test coverage only — the production code path goes through
37961
+ * `filterRoutesByApiIdentifier`.
37962
+ */
37963
+ function routeMatchesIdentifier(route, identifier) {
37964
+ const bareId = route.source === "function-url" ? route.lambdaLogicalId : route.apiLogicalId;
37965
+ if (bareId && bareId === identifier) return true;
37966
+ if (route.apiStackName) {
37967
+ if (bareId && identifier === `${route.apiStackName}:${bareId}`) return true;
37968
+ }
37969
+ if (route.apiCdkPath) {
37970
+ if (identifier === route.apiCdkPath) return true;
37971
+ if (route.apiCdkPath.startsWith(`${identifier}/`)) return true;
37972
+ }
37973
+ return false;
37922
37974
  }
37923
37975
  /**
37924
37976
  * Enumerate every distinct API identifier in the route list, in the
37925
37977
  * order they were discovered. Useful for the "available APIs" error
37926
37978
  * message when `--api <id>` doesn't match.
37979
+ *
37980
+ * Returns the **primary form** per API (CDK Construct path when
37981
+ * available, else bare logical id) — the "available identifiers" hint
37982
+ * stays compact while pointing users at the form most likely to round-
37983
+ * trip across rename refactors.
37927
37984
  */
37928
37985
  function availableApiIdentifiers(routes) {
37929
37986
  const seen = /* @__PURE__ */ new Set();
37930
37987
  const out = [];
37931
37988
  for (const rwa of routes) {
37932
37989
  const r = rwa.route;
37933
- const id = r.source === "function-url" ? r.lambdaLogicalId : r.apiLogicalId ?? "<unknown>";
37934
- if (!seen.has(id)) {
37935
- seen.add(id);
37936
- out.push(id);
37990
+ const bareId = r.source === "function-url" ? r.lambdaLogicalId : r.apiLogicalId ?? "<unknown>";
37991
+ const primary = r.apiCdkPath ?? bareId;
37992
+ if (!seen.has(primary)) {
37993
+ seen.add(primary);
37994
+ out.push(primary);
37937
37995
  }
37938
37996
  }
37939
37997
  return out;
@@ -38932,7 +38990,7 @@ function parseDebugPort(raw) {
38932
38990
  * Builder for the `start-api` subcommand. Wired up by `local.ts`.
38933
38991
  */
38934
38992
  function createLocalStartApiCommand() {
38935
- 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 and Cognito User Pool / HTTP v2 JWT authorizers; 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.").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>", "Restrict to a single API surface by its logical id (HTTP API / REST API logical id, or the backing Lambda's logical id for Function URLs). When unset, every discovered API gets its own server on its own port (basePort, basePort+1, ... when --port is set; auto-allocated otherwise).")).action(withErrorHandling(localStartApiCommand));
38993
+ 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 and Cognito User Pool / HTTP v2 JWT authorizers; 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.").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>", "Restrict to a single API surface. Accepts the bare CDK logical id (e.g. 'MyHttpApi'), the stack-qualified logical id ('MyStack:MyHttpApi'), the full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). For Function URLs, the path forms reference the backing Lambda's aws:cdk:path. When unset, every discovered API gets its own server on its own port (basePort, basePort+1, ... when --port is set; auto-allocated otherwise).")).action(withErrorHandling(localStartApiCommand));
38936
38994
  [
38937
38995
  ...commonOptions,
38938
38996
  ...appOptions,
@@ -41771,7 +41829,7 @@ function reorderArgs(argv) {
41771
41829
  */
41772
41830
  async function main() {
41773
41831
  const program = new Command();
41774
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.94.14");
41832
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.94.15");
41775
41833
  program.addCommand(createBootstrapCommand());
41776
41834
  program.addCommand(createSynthCommand());
41777
41835
  program.addCommand(createListCommand());