@go-to-k/cdkd 0.79.0 → 0.81.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/dist/cli.js CHANGED
@@ -44850,8 +44850,8 @@ var ApiGatewayV2Provider = class {
44850
44850
  async createRoute(logicalId, resourceType, properties) {
44851
44851
  this.logger.debug(`Creating API Gateway V2 Route ${logicalId}`);
44852
44852
  const apiId = properties["ApiId"];
44853
- const routeKey2 = properties["RouteKey"];
44854
- if (!apiId || !routeKey2) {
44853
+ const routeKey = properties["RouteKey"];
44854
+ if (!apiId || !routeKey) {
44855
44855
  throw new ProvisioningError(
44856
44856
  `ApiId and RouteKey are required for API Gateway V2 Route ${logicalId}`,
44857
44857
  resourceType,
@@ -44862,7 +44862,7 @@ var ApiGatewayV2Provider = class {
44862
44862
  const response = await this.getClient().send(
44863
44863
  new CreateRouteCommand2({
44864
44864
  ApiId: apiId,
44865
- RouteKey: routeKey2,
44865
+ RouteKey: routeKey,
44866
44866
  Target: properties["Target"],
44867
44867
  AuthorizationType: properties["AuthorizationType"],
44868
44868
  AuthorizerId: properties["AuthorizerId"]
@@ -70579,7 +70579,9 @@ var SUPPORTED_RUNTIMES = {
70579
70579
  java17: { image: "public.ecr.aws/lambda/java:17", fileExtension: null },
70580
70580
  java21: { image: "public.ecr.aws/lambda/java:21", fileExtension: null },
70581
70581
  dotnet6: { image: "public.ecr.aws/lambda/dotnet:6", fileExtension: null },
70582
- dotnet8: { image: "public.ecr.aws/lambda/dotnet:8", fileExtension: null }
70582
+ dotnet8: { image: "public.ecr.aws/lambda/dotnet:8", fileExtension: null },
70583
+ "provided.al2": { image: "public.ecr.aws/lambda/provided:al2", fileExtension: null },
70584
+ "provided.al2023": { image: "public.ecr.aws/lambda/provided:al2023", fileExtension: null }
70583
70585
  };
70584
70586
  var UnsupportedRuntimeError = class _UnsupportedRuntimeError extends Error {
70585
70587
  constructor(runtime, message) {
@@ -70597,7 +70599,7 @@ function resolveRuntimeFileExtension(runtime) {
70597
70599
  if (spec.fileExtension === null) {
70598
70600
  throw new UnsupportedRuntimeError(
70599
70601
  runtime,
70600
- `Inline 'Code.ZipFile' is not supported for runtime '${runtime}'. The Lambda Handler shape for this runtime names a compiled artifact (a JVM class, a .NET assembly, or a native binary) that cannot be expressed as a single inline source file. Use \`lambda.Code.fromAsset(<dir>)\` with a directory containing the compiled output (.class hierarchy / JAR / DLL / binary).`
70602
+ `Inline 'Code.ZipFile' is not supported for runtime '${runtime}'. The Lambda Handler shape for this runtime names a compiled artifact (a JVM class, a .NET assembly, or \u2014 for the OS-only \`provided.*\` runtimes \u2014 an arbitrary \`bootstrap\` binary) that cannot be expressed as a single inline source file. Use \`lambda.Code.fromAsset(<dir>)\` with a directory containing the compiled output (.class hierarchy / JAR / DLL / native binary).`
70601
70603
  );
70602
70604
  }
70603
70605
  return spec.fileExtension;
@@ -70612,17 +70614,24 @@ function resolveRuntimeSpec(runtime) {
70612
70614
  const spec = SUPPORTED_RUNTIMES[runtime];
70613
70615
  if (spec)
70614
70616
  return spec;
70615
- if (runtime.startsWith("go") || runtime.startsWith("provided")) {
70617
+ if (runtime === "go1.x") {
70616
70618
  throw new UnsupportedRuntimeError(
70617
70619
  runtime,
70618
- `Runtime '${runtime}' is not yet supported in cdkd local invoke. Supported runtimes: Node.js (nodejs18.x / nodejs20.x / nodejs22.x / nodejs24.x), Python (python3.11 / python3.12 / python3.13 / python3.14), Ruby (ruby3.2 / ruby3.3), Java (java8.al2 / java11 / java17 / java21), .NET (dotnet6 / dotnet8). Other runtimes follow in subsequent PRs.`
70620
+ `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`
70619
70621
  );
70620
70622
  }
70621
70623
  throw new UnsupportedRuntimeError(
70622
70624
  runtime,
70623
- `Unknown runtime '${runtime}'. cdkd local 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.`
70625
+ `Unknown runtime '${runtime}'. cdkd local 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.`
70624
70626
  );
70625
70627
  }
70628
+ function resolveRuntimeCodeMountPath(runtime) {
70629
+ resolveRuntimeSpec(runtime);
70630
+ if (runtime === "provided.al2" || runtime === "provided.al2023") {
70631
+ return "/var/runtime";
70632
+ }
70633
+ return "/var/task";
70634
+ }
70626
70635
 
70627
70636
  // src/local/docker-runner.ts
70628
70637
  import { execFile as execFile3, spawn as spawn3 } from "node:child_process";
@@ -70642,8 +70651,38 @@ async function pullImage(image, skipPull) {
70642
70651
  logger.debug(`Skipping docker pull for ${image} (--no-pull)`);
70643
70652
  return;
70644
70653
  }
70645
- logger.info(`Pulling ${image}...`);
70646
- await runForeground("docker", ["pull", image]);
70654
+ if (getLogger().getLevel() === "debug") {
70655
+ logger.info(`Pulling ${image}...`);
70656
+ await runForeground("docker", ["pull", image]);
70657
+ return;
70658
+ }
70659
+ logger.debug(`Pulling ${image} (silent \u2014 pass --verbose to stream progress)`);
70660
+ await runCaptured("docker", ["pull", image], image);
70661
+ }
70662
+ function runCaptured(cmd, args, image) {
70663
+ return new Promise((resolveProc, rejectProc) => {
70664
+ const proc = spawn3(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
70665
+ let stdout = "";
70666
+ let stderr = "";
70667
+ proc.stdout?.on("data", (chunk) => {
70668
+ stdout += chunk.toString("utf-8");
70669
+ });
70670
+ proc.stderr?.on("data", (chunk) => {
70671
+ stderr += chunk.toString("utf-8");
70672
+ });
70673
+ proc.on(
70674
+ "error",
70675
+ (err) => rejectProc(new DockerRunnerError(`${cmd} pull ${image} failed: ${err.message}`))
70676
+ );
70677
+ proc.on("close", (code) => {
70678
+ if (code === 0) {
70679
+ resolveProc();
70680
+ return;
70681
+ }
70682
+ const detail = stderr.trim() || stdout.trim() || "(no output)";
70683
+ rejectProc(new DockerRunnerError(`docker pull ${image} exited with code ${code}: ${detail}`));
70684
+ });
70685
+ });
70647
70686
  }
70648
70687
  async function runDetached(opts) {
70649
70688
  const args = ["run", "-d", "--rm"];
@@ -71358,8 +71397,8 @@ function discoverHttpApiRoute(logicalId, resource, template, stackName) {
71358
71397
  );
71359
71398
  }
71360
71399
  }
71361
- const routeKey2 = props["RouteKey"];
71362
- if (typeof routeKey2 !== "string" || routeKey2.length === 0) {
71400
+ const routeKey = props["RouteKey"];
71401
+ if (typeof routeKey !== "string" || routeKey.length === 0) {
71363
71402
  throw new Error(
71364
71403
  `${stackName}/${logicalId} (AWS::ApiGatewayV2::Route): RouteKey must be a string`
71365
71404
  );
@@ -71395,7 +71434,7 @@ function discoverHttpApiRoute(logicalId, resource, template, stackName) {
71395
71434
  integrationProps["IntegrationUri"],
71396
71435
  `${stackName}/${integrationLogicalId}.IntegrationUri`
71397
71436
  );
71398
- const { method, pathPattern } = parseRouteKey(routeKey2);
71437
+ const { method, pathPattern } = parseRouteKey(routeKey);
71399
71438
  return [
71400
71439
  {
71401
71440
  method,
@@ -71510,14 +71549,14 @@ function parseHttpApiTargetIntegration(target, location) {
71510
71549
  )}).`
71511
71550
  );
71512
71551
  }
71513
- function parseRouteKey(routeKey2) {
71514
- if (routeKey2 === "$default") {
71552
+ function parseRouteKey(routeKey) {
71553
+ if (routeKey === "$default") {
71515
71554
  return { method: "ANY", pathPattern: "$default" };
71516
71555
  }
71517
- const m = /^([A-Za-z]+)\s+(\S+)$/.exec(routeKey2);
71556
+ const m = /^([A-Za-z]+)\s+(\S+)$/.exec(routeKey);
71518
71557
  if (!m) {
71519
71558
  throw new Error(
71520
- `RouteKey '${routeKey2}' is malformed: expected '<METHOD> <path>' (e.g. 'GET /items/{id}') or '$default'.`
71559
+ `RouteKey '${routeKey}' is malformed: expected '<METHOD> <path>' (e.g. 'GET /items/{id}') or '$default'.`
71521
71560
  );
71522
71561
  }
71523
71562
  return { method: m[1].toUpperCase(), pathPattern: m[2] };
@@ -71575,9 +71614,10 @@ function createContainerPool(specs, options) {
71575
71614
  `Starting container ${name} for ${spec.lambda.logicalId} on ${spec.containerHost}:${hostPort}`
71576
71615
  );
71577
71616
  const optMount = spec.optDir ? [{ hostPath: spec.optDir, containerPath: "/opt", readOnly: true }] : [];
71617
+ const containerCodePath = resolveRuntimeCodeMountPath(spec.lambda.runtime);
71578
71618
  const containerId = await runDetached({
71579
71619
  image,
71580
- mounts: [{ hostPath: spec.codeDir, containerPath: "/var/task", readOnly: true }],
71620
+ mounts: [{ hostPath: spec.codeDir, containerPath: containerCodePath, readOnly: true }],
71581
71621
  extraMounts: optMount,
71582
71622
  env: spec.env,
71583
71623
  cmd: [spec.lambda.handler],
@@ -71857,10 +71897,10 @@ function buildHttpApiV2Event(req, ctx, opts = {}) {
71857
71897
  const contentType = headers["content-type"] ?? "";
71858
71898
  const { body, isBase64Encoded } = encodeBody(req.body, contentType);
71859
71899
  const now = opts.now ? opts.now() : /* @__PURE__ */ new Date();
71860
- const routeKey2 = ctx.route.pathPattern === "$default" ? "$default" : `${ctx.route.method} ${ctx.route.pathPattern}`;
71900
+ const routeKey = ctx.route.pathPattern === "$default" ? "$default" : `${ctx.route.method} ${ctx.route.pathPattern}`;
71861
71901
  const event = {
71862
71902
  version: "2.0",
71863
- routeKey: routeKey2,
71903
+ routeKey,
71864
71904
  rawPath,
71865
71905
  rawQueryString,
71866
71906
  cookies,
@@ -71884,7 +71924,7 @@ function buildHttpApiV2Event(req, ctx, opts = {}) {
71884
71924
  userAgent
71885
71925
  },
71886
71926
  requestId: randomUUID(),
71887
- routeKey: routeKey2,
71927
+ routeKey,
71888
71928
  stage: ctx.route.stage,
71889
71929
  time: formatRequestTime(now),
71890
71930
  timeEpoch: now.getTime(),
@@ -73788,6 +73828,74 @@ function writeError(res, statusCode, body = '{"message":"Internal server error"}
73788
73828
  res.end(body);
73789
73829
  }
73790
73830
 
73831
+ // src/local/api-server-grouping.ts
73832
+ function groupRoutesByServer(routes) {
73833
+ const order = [];
73834
+ const byKey = /* @__PURE__ */ new Map();
73835
+ for (const rwa of routes) {
73836
+ const r = rwa.route;
73837
+ let serverKey;
73838
+ let kind;
73839
+ let identifier;
73840
+ let displayName;
73841
+ if (r.source === "function-url") {
73842
+ identifier = r.lambdaLogicalId;
73843
+ serverKey = `function-url:${identifier}`;
73844
+ kind = "function-url";
73845
+ displayName = `${identifier} (Function URL)`;
73846
+ } else if (r.source === "http-api") {
73847
+ identifier = r.apiLogicalId ?? "<unknown>";
73848
+ serverKey = `http-api:${identifier}`;
73849
+ kind = "http-api";
73850
+ displayName = `${identifier} (HTTP API v2)`;
73851
+ } else {
73852
+ identifier = r.apiLogicalId ?? "<unknown>";
73853
+ serverKey = `rest-v1:${identifier}`;
73854
+ kind = "rest-v1";
73855
+ displayName = `${identifier} (REST API v1)`;
73856
+ }
73857
+ const existing = byKey.get(serverKey);
73858
+ if (existing) {
73859
+ existing.routes.push(rwa);
73860
+ } else {
73861
+ byKey.set(serverKey, { displayName, kind, identifier, routes: [rwa] });
73862
+ order.push(serverKey);
73863
+ }
73864
+ }
73865
+ return order.map((key) => {
73866
+ const entry = byKey.get(key);
73867
+ return {
73868
+ serverKey: key,
73869
+ displayName: entry.displayName,
73870
+ kind: entry.kind,
73871
+ identifier: entry.identifier,
73872
+ routes: entry.routes
73873
+ };
73874
+ });
73875
+ }
73876
+ function filterRoutesByApiIdentifier(routes, identifier) {
73877
+ return routes.filter((rwa) => {
73878
+ const r = rwa.route;
73879
+ if (r.source === "function-url") {
73880
+ return r.lambdaLogicalId === identifier;
73881
+ }
73882
+ return r.apiLogicalId === identifier;
73883
+ });
73884
+ }
73885
+ function availableApiIdentifiers(routes) {
73886
+ const seen = /* @__PURE__ */ new Set();
73887
+ const out = [];
73888
+ for (const rwa of routes) {
73889
+ const r = rwa.route;
73890
+ const id = r.source === "function-url" ? r.lambdaLogicalId : r.apiLogicalId ?? "<unknown>";
73891
+ if (!seen.has(id)) {
73892
+ seen.add(id);
73893
+ out.push(id);
73894
+ }
73895
+ }
73896
+ return out;
73897
+ }
73898
+
73791
73899
  // src/local/stage-resolver.ts
73792
73900
  function buildStageMap(template, stageOverride) {
73793
73901
  const out = /* @__PURE__ */ new Map();
@@ -75679,92 +75787,6 @@ function createFileWatcher(options) {
75679
75787
  };
75680
75788
  }
75681
75789
 
75682
- // src/local/reload-orchestrator.ts
75683
- function createReloadOrchestrator(deps) {
75684
- const logger = getLogger().child("start-api-reload");
75685
- let chain = Promise.resolve();
75686
- return {
75687
- reload() {
75688
- const next = chain.then(() => runOneReload(deps, logger));
75689
- chain = next.catch(() => void 0);
75690
- return next;
75691
- }
75692
- };
75693
- }
75694
- async function runOneReload(deps, logger) {
75695
- const previousState = deps.getServerState();
75696
- const start = Date.now();
75697
- let material;
75698
- try {
75699
- material = await deps.synthesizeAndBuild();
75700
- } catch (err) {
75701
- const reason = err instanceof Error ? err.message : String(err);
75702
- logger.warn(`cdk synth failed during reload; keeping previous version. (${reason})`);
75703
- return { ok: false, reason, added: [], removed: [], rebuiltLambdas: [] };
75704
- }
75705
- const oldRoutes = previousState.routes;
75706
- const newRoutes = material.routes;
75707
- const oldKeys = new Set(oldRoutes.map((r) => routeKey(r.route)));
75708
- const newKeys = new Set(newRoutes.map((r) => routeKey(r.route)));
75709
- const added = newRoutes.filter((r) => !oldKeys.has(routeKey(r.route)));
75710
- const removed = oldRoutes.filter((r) => !newKeys.has(routeKey(r.route)));
75711
- const previousSpecs = pickSpecsFromState(previousState);
75712
- const rebuiltLambdas = [];
75713
- for (const [logicalId, newSpec] of material.specs) {
75714
- const oldSpec = previousSpecs.get(logicalId);
75715
- if (!oldSpec)
75716
- continue;
75717
- if (specSignature(oldSpec) !== specSignature(newSpec)) {
75718
- rebuiltLambdas.push(logicalId);
75719
- }
75720
- }
75721
- const newPool = deps.buildPool(material.specs);
75722
- Object.defineProperty(newPool, "__cdkdSpecs", {
75723
- value: material.specs,
75724
- enumerable: false,
75725
- configurable: true
75726
- });
75727
- const newState = {
75728
- routes: material.routes,
75729
- pool: newPool,
75730
- corsConfigByApiId: material.corsConfigByApiId
75731
- };
75732
- deps.setServerState(newState);
75733
- void previousState.pool.dispose().catch(
75734
- (err) => logger.debug(
75735
- `Previous pool dispose() failed: ${err instanceof Error ? err.message : String(err)}`
75736
- )
75737
- );
75738
- const elapsed = Date.now() - start;
75739
- logger.info(
75740
- `Reloaded in ${elapsed}ms: +${added.length} route(s), -${removed.length} route(s), ${rebuiltLambdas.length} Lambda(s) rebuilt.`
75741
- );
75742
- return {
75743
- ok: true,
75744
- added,
75745
- removed,
75746
- rebuiltLambdas,
75747
- newState
75748
- };
75749
- }
75750
- function routeKey(route) {
75751
- return [route.method, route.pathPattern, route.lambdaLogicalId, route.source, route.apiVersion].map((s) => String(s)).join("|");
75752
- }
75753
- function specSignature(spec) {
75754
- return JSON.stringify({
75755
- codeDir: spec.codeDir,
75756
- env: spec.env,
75757
- handler: spec.lambda.handler,
75758
- runtime: spec.lambda.runtime,
75759
- containerHost: spec.containerHost,
75760
- debugPort: spec.debugPort ?? null
75761
- });
75762
- }
75763
- function pickSpecsFromState(state) {
75764
- const tagged = state.pool.__cdkdSpecs;
75765
- return tagged ?? /* @__PURE__ */ new Map();
75766
- }
75767
-
75768
75790
  // src/local/authorizer-cache.ts
75769
75791
  function createAuthorizerCache(opts = {}) {
75770
75792
  const now = opts.now ?? (() => Date.now());
@@ -75870,7 +75892,17 @@ async function localStartApiCommand(options) {
75870
75892
  }
75871
75893
  }
75872
75894
  attachStageContext(routes, stageMap);
75873
- const routesWithAuth = attachAuthorizers(targetStacks, routes);
75895
+ let routesWithAuth = attachAuthorizers(targetStacks, routes);
75896
+ if (options.api) {
75897
+ const filtered = filterRoutesByApiIdentifier(routesWithAuth, options.api);
75898
+ if (filtered.length === 0) {
75899
+ const available = availableApiIdentifiers(routesWithAuth).join(", ") || "(none)";
75900
+ throw new Error(
75901
+ `--api '${options.api}' did not match any discovered API. Available identifiers: ${available}.`
75902
+ );
75903
+ }
75904
+ routesWithAuth = filtered;
75905
+ }
75874
75906
  const corsConfigByApiId = /* @__PURE__ */ new Map();
75875
75907
  for (const stack of targetStacks) {
75876
75908
  const m = buildCorsConfigByApiId(stack.template);
@@ -75923,88 +75955,91 @@ async function localStartApiCommand(options) {
75923
75955
  return [...assetPaths];
75924
75956
  };
75925
75957
  const initialMaterial = await synthesizeAndBuild();
75926
- const initialPool = buildPool(initialMaterial.specs);
75927
75958
  lastAssetPaths.value = computeAssetPaths(initialMaterial.specs);
75928
75959
  await prewarmJwks(initialMaterial.routes, jwksCache);
75929
75960
  warnVpcConfigLambdas(initialMaterial.routes, initialMaterial.stacks ?? []);
75930
- if (options.warm) {
75931
- logger.info(`Pre-warming ${initialMaterial.specs.size} container(s)...`);
75932
- const handles = await Promise.allSettled(
75933
- [...initialMaterial.specs.keys()].map((id) => initialPool.acquire(id))
75934
- );
75935
- for (const result of handles) {
75936
- if (result.status === "fulfilled") {
75937
- initialPool.release(result.value);
75938
- } else {
75939
- logger.warn(
75940
- `Pre-warm failed for one Lambda (cold start cost will apply on first request): ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
75941
- );
75942
- }
75943
- }
75944
- }
75945
75961
  let maxTimeoutSec = 0;
75946
75962
  for (const spec of initialMaterial.specs.values()) {
75947
75963
  if (spec.lambda.timeoutSec > maxTimeoutSec)
75948
75964
  maxTimeoutSec = spec.lambda.timeoutSec;
75949
75965
  }
75950
75966
  const rieTimeoutMs = Math.max(3e4, maxTimeoutSec * 2 * 1e3);
75951
- const port = parseInt(options.port, 10);
75952
- if (!Number.isFinite(port) || port < 0 || port > 65535) {
75967
+ const basePort = parseInt(options.port, 10);
75968
+ if (!Number.isFinite(basePort) || basePort < 0 || basePort > 65535) {
75953
75969
  throw new Error(`--port must be 0..65535 (got ${options.port}).`);
75954
75970
  }
75955
- const initialState = {
75956
- routes: initialMaterial.routes,
75957
- pool: initialPool,
75958
- corsConfigByApiId: initialMaterial.corsConfigByApiId
75959
- };
75960
- const server = await startApiServer({
75961
- state: initialState,
75962
- rieTimeoutMs,
75963
- host: options.host,
75964
- port,
75965
- authorizerCache,
75966
- jwksCache,
75967
- jwksWarnedUrls
75968
- });
75969
- printRouteTable(initialMaterial.routes);
75971
+ const initialGroups = groupRoutesByServer(initialMaterial.routes);
75972
+ const servers = [];
75973
+ let nextPort = basePort;
75974
+ for (const group of initialGroups) {
75975
+ const groupSpecs = filterSpecsForGroup(group, initialMaterial.specs);
75976
+ const groupPool = buildPool(groupSpecs);
75977
+ const groupState = {
75978
+ routes: group.routes,
75979
+ pool: groupPool,
75980
+ corsConfigByApiId: initialMaterial.corsConfigByApiId
75981
+ };
75982
+ if (options.warm) {
75983
+ logger.info(`Pre-warming ${groupSpecs.size} container(s) for ${group.displayName}...`);
75984
+ const handles = await Promise.allSettled(
75985
+ [...groupSpecs.keys()].map((id) => groupPool.acquire(id))
75986
+ );
75987
+ for (const result of handles) {
75988
+ if (result.status === "fulfilled") {
75989
+ groupPool.release(result.value);
75990
+ } else {
75991
+ logger.warn(
75992
+ `Pre-warm failed for one Lambda in ${group.displayName} (cold start cost will apply on first request): ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
75993
+ );
75994
+ }
75995
+ }
75996
+ }
75997
+ const started = await startApiServer({
75998
+ state: groupState,
75999
+ rieTimeoutMs,
76000
+ host: options.host,
76001
+ // Increment per server; basePort=0 leaves every server on auto-alloc.
76002
+ port: basePort === 0 ? 0 : nextPort,
76003
+ authorizerCache,
76004
+ jwksCache,
76005
+ jwksWarnedUrls
76006
+ });
76007
+ servers.push({ group, server: started });
76008
+ if (basePort !== 0)
76009
+ nextPort += 1;
76010
+ }
76011
+ printPerServerRouteTables(servers);
75970
76012
  logger.info(
75971
76013
  `Per-Lambda concurrency: ${perLambdaConcurrency} (override with --per-lambda-concurrency)`
75972
76014
  );
75973
- process.stdout.write(`Server listening on http://${server.host}:${server.port}
75974
- `);
76015
+ for (const { group, server } of servers) {
76016
+ process.stdout.write(
76017
+ `Server listening on http://${server.host}:${server.port} (${group.displayName})
76018
+ `
76019
+ );
76020
+ }
75975
76021
  process.stdout.write("^C to stop and clean up containers.\n");
75976
76022
  let watcher;
75977
- let orchestrator;
76023
+ let reloadChain = Promise.resolve();
75978
76024
  if (options.watch) {
75979
- orchestrator = createReloadOrchestrator({
75980
- synthesizeAndBuild,
75981
- buildPool,
75982
- setServerState: server.setServerState,
75983
- getServerState: server.getServerState
75984
- });
75985
76025
  const initialWatchPaths = [options.output, ...lastAssetPaths.value];
75986
76026
  watcher = createFileWatcher({
75987
76027
  paths: initialWatchPaths,
75988
76028
  onChange: () => {
75989
- if (!orchestrator)
75990
- return;
75991
76029
  logger.info("Detected file change; reloading...");
75992
- void orchestrator.reload().then((result) => {
75993
- if (result.ok && watcher && result.newState) {
75994
- const taggedSpecs = result.newState.pool.__cdkdSpecs;
75995
- if (taggedSpecs) {
75996
- lastAssetPaths.value = computeAssetPaths(taggedSpecs);
75997
- }
75998
- watcher.update([options.output, ...lastAssetPaths.value]);
75999
- if (result.added.length > 0 || result.removed.length > 0) {
76000
- printRouteTable(result.newState.routes);
76001
- }
76002
- }
76003
- }).catch((err) => {
76004
- logger.warn(
76005
- `Reload failed: ${err instanceof Error ? err.message : String(err)}. Keeping previous version.`
76006
- );
76007
- });
76030
+ const next = reloadChain.then(
76031
+ () => reloadAllServers({
76032
+ synthesizeAndBuild,
76033
+ servers,
76034
+ buildPool,
76035
+ computeAssetPaths,
76036
+ lastAssetPaths,
76037
+ watcher,
76038
+ output: options.output,
76039
+ logger
76040
+ })
76041
+ );
76042
+ reloadChain = next.catch(() => void 0);
76008
76043
  }
76009
76044
  });
76010
76045
  logger.info(`Watching ${options.output} (and ${lastAssetPaths.value.length} asset dir(s))`);
@@ -76031,16 +76066,28 @@ async function localStartApiCommand(options) {
76031
76066
  logger.warn(`watcher.close() failed: ${err instanceof Error ? err.message : String(err)}`);
76032
76067
  }
76033
76068
  }
76034
- try {
76035
- await server.close();
76036
- } catch (err) {
76037
- logger.warn(`server.close() failed: ${err instanceof Error ? err.message : String(err)}`);
76038
- }
76039
- try {
76040
- await server.getServerState().pool.dispose();
76041
- } catch (err) {
76042
- logger.warn(`pool.dispose() failed: ${err instanceof Error ? err.message : String(err)}`);
76043
- }
76069
+ await Promise.allSettled(
76070
+ servers.map(async ({ server, group }) => {
76071
+ try {
76072
+ await server.close();
76073
+ } catch (err) {
76074
+ logger.warn(
76075
+ `server.close() failed for ${group.displayName}: ${err instanceof Error ? err.message : String(err)}`
76076
+ );
76077
+ }
76078
+ })
76079
+ );
76080
+ await Promise.allSettled(
76081
+ servers.map(async ({ server, group }) => {
76082
+ try {
76083
+ await server.getServerState().pool.dispose();
76084
+ } catch (err) {
76085
+ logger.warn(
76086
+ `pool.dispose() failed for ${group.displayName}: ${err instanceof Error ? err.message : String(err)}`
76087
+ );
76088
+ }
76089
+ })
76090
+ );
76044
76091
  for (const dir of inlineTmpDirs) {
76045
76092
  try {
76046
76093
  rmSync2(dir, { recursive: true, force: true });
@@ -76412,6 +76459,91 @@ function parsePerLambdaConcurrency(raw) {
76412
76459
  }
76413
76460
  return parsed;
76414
76461
  }
76462
+ function filterSpecsForGroup(group, allSpecs) {
76463
+ const ids = /* @__PURE__ */ new Set();
76464
+ for (const rwa of group.routes) {
76465
+ ids.add(rwa.route.lambdaLogicalId);
76466
+ const auth = rwa.authorizer;
76467
+ if (auth && (auth.kind === "lambda-token" || auth.kind === "lambda-request")) {
76468
+ ids.add(auth.lambdaLogicalId);
76469
+ }
76470
+ }
76471
+ const out = /* @__PURE__ */ new Map();
76472
+ for (const id of ids) {
76473
+ const spec = allSpecs.get(id);
76474
+ if (spec)
76475
+ out.set(id, spec);
76476
+ }
76477
+ return out;
76478
+ }
76479
+ function printPerServerRouteTables(servers) {
76480
+ for (const { group, server } of servers) {
76481
+ process.stdout.write(`
76482
+ ${group.displayName} (http://${server.host}:${server.port})
76483
+ `);
76484
+ printRouteTable(group.routes);
76485
+ }
76486
+ }
76487
+ async function reloadAllServers(args) {
76488
+ const {
76489
+ synthesizeAndBuild,
76490
+ servers,
76491
+ buildPool,
76492
+ computeAssetPaths,
76493
+ lastAssetPaths,
76494
+ watcher,
76495
+ output,
76496
+ logger
76497
+ } = args;
76498
+ let material;
76499
+ try {
76500
+ material = await synthesizeAndBuild();
76501
+ } catch (err) {
76502
+ logger.warn(
76503
+ `cdk synth failed during reload; keeping previous version. (${err instanceof Error ? err.message : String(err)})`
76504
+ );
76505
+ return;
76506
+ }
76507
+ const newGroups = groupRoutesByServer(material.routes);
76508
+ const newByKey = new Map(newGroups.map((g) => [g.serverKey, g]));
76509
+ const oldKeys = new Set(servers.map((s) => s.group.serverKey));
76510
+ const newKeys = new Set(newByKey.keys());
76511
+ const added = [...newKeys].filter((k) => !oldKeys.has(k));
76512
+ const removed = [...oldKeys].filter((k) => !newKeys.has(k));
76513
+ if (added.length > 0) {
76514
+ logger.warn(
76515
+ `Reload detected new API surface(s): ${added.join(", ")}. Restart 'cdkd local start-api' to serve them.`
76516
+ );
76517
+ }
76518
+ if (removed.length > 0) {
76519
+ logger.warn(
76520
+ `Reload detected removed API surface(s): ${removed.join(", ")}. Their servers will keep serving stale routes until restart.`
76521
+ );
76522
+ }
76523
+ for (const booted of servers) {
76524
+ const group = newByKey.get(booted.group.serverKey);
76525
+ if (!group)
76526
+ continue;
76527
+ const groupSpecs = filterSpecsForGroup(group, material.specs);
76528
+ const newPool = buildPool(groupSpecs);
76529
+ const newState = {
76530
+ routes: group.routes,
76531
+ pool: newPool,
76532
+ corsConfigByApiId: material.corsConfigByApiId
76533
+ };
76534
+ const previousState = booted.server.setServerState(newState);
76535
+ void previousState.pool.dispose().catch((err) => {
76536
+ logger.debug(
76537
+ `Previous pool dispose() failed for ${group.displayName}: ${err instanceof Error ? err.message : String(err)}`
76538
+ );
76539
+ });
76540
+ }
76541
+ lastAssetPaths.value = computeAssetPaths(material.specs);
76542
+ if (watcher) {
76543
+ watcher.update([output, ...lastAssetPaths.value]);
76544
+ }
76545
+ printPerServerRouteTables(servers);
76546
+ }
76415
76547
  function parseDebugPort(raw) {
76416
76548
  const parsed = parseInt(raw, 10);
76417
76549
  if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
@@ -76434,8 +76566,8 @@ function createLocalStartApiCommand() {
76434
76566
  ).addOption(new Option7("--no-pull", "Skip docker pull (cached image)")).addOption(
76435
76567
  new Option7(
76436
76568
  "--container-host <host>",
76437
- "Hostname/IP the container reaches the host on"
76438
- ).default("host.docker.internal")
76569
+ "IP the host uses to bind/probe the RIE port (must be a numeric IP \u2014 `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1."
76570
+ ).default("127.0.0.1")
76439
76571
  ).addOption(
76440
76572
  new Option7(
76441
76573
  "--debug-port-base <port>",
@@ -76461,6 +76593,11 @@ function createLocalStartApiCommand() {
76461
76593
  "--stage <name>",
76462
76594
  "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 \u2014 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."
76463
76595
  )
76596
+ ).addOption(
76597
+ new Option7(
76598
+ "--api <id>",
76599
+ "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)."
76600
+ )
76464
76601
  ).action(withErrorHandling(localStartApiCommand));
76465
76602
  [...commonOptions, ...appOptions, ...contextOptions].forEach((opt) => startApi.addOption(opt));
76466
76603
  startApi.addOption(deprecatedRegionOption);
@@ -76670,9 +76807,10 @@ async function resolveZipImagePlan(lambda, options) {
76670
76807
  const image = resolveRuntimeImage(lambda.runtime);
76671
76808
  await pullImage(image, options.pull === false);
76672
76809
  const layerPlan = materializeLambdaLayers2(lambda.layers);
76810
+ const containerCodePath = resolveRuntimeCodeMountPath(lambda.runtime);
76673
76811
  return {
76674
76812
  image,
76675
- mounts: [{ hostPath: codeDir, containerPath: "/var/task", readOnly: true }],
76813
+ mounts: [{ hostPath: codeDir, containerPath: containerCodePath, readOnly: true }],
76676
76814
  extraMounts: layerPlan.mount ? [layerPlan.mount] : [],
76677
76815
  cmd: [lambda.handler],
76678
76816
  ...inlineTmpDir !== void 0 && { inlineTmpDir },
@@ -77045,7 +77183,7 @@ function reorderArgs(argv) {
77045
77183
  }
77046
77184
  async function main() {
77047
77185
  const program = new Command16();
77048
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.79.0");
77186
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.81.0");
77049
77187
  program.addCommand(createBootstrapCommand());
77050
77188
  program.addCommand(createSynthCommand());
77051
77189
  program.addCommand(createListCommand());