cdk-local 0.11.3 → 0.13.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.
@@ -164,15 +164,15 @@ async function applyRoleArnIfSet(opts) {
164
164
  /**
165
165
  * Base error class for cdk-local
166
166
  */
167
- var CdkdError = class CdkdError extends Error {
167
+ var CdkLocalError = class CdkLocalError extends Error {
168
168
  code;
169
169
  cause;
170
170
  constructor(message, code, cause) {
171
171
  super(message);
172
172
  this.code = code;
173
173
  this.cause = cause;
174
- this.name = "CdkdError";
175
- Object.setPrototypeOf(this, CdkdError.prototype);
174
+ this.name = "CdkLocalError";
175
+ Object.setPrototypeOf(this, CdkLocalError.prototype);
176
176
  }
177
177
  };
178
178
  /**
@@ -181,12 +181,10 @@ var CdkdError = class CdkdError extends Error {
181
181
  * Surfaces the stderr captured from `docker build` so the user can
182
182
  * re-run the same command directly to debug Dockerfile syntax errors
183
183
  * or missing build context. Used by `src/local/docker-image-builder.ts`
184
- * (PR 5) for container Lambdas; the parallel `AssetError` covers the
185
- * `cdkd publish-assets` / `cdkd deploy` build path. Kept distinct from
186
- * `AssetError` so `cdkl invoke` failures don't show up under the
187
- * "asset" error class.
184
+ * for container Lambdas. Kept distinct from a generic asset/build error
185
+ * so `cdkl invoke` failures don't show up under an unrelated error class.
188
186
  */
189
- var LocalInvokeBuildError = class LocalInvokeBuildError extends CdkdError {
187
+ var LocalInvokeBuildError = class LocalInvokeBuildError extends CdkLocalError {
190
188
  constructor(message, cause) {
191
189
  super(message, "LOCAL_INVOKE_BUILD_ERROR", cause);
192
190
  this.name = "LocalInvokeBuildError";
@@ -200,14 +198,13 @@ var LocalInvokeBuildError = class LocalInvokeBuildError extends CdkdError {
200
198
  * unrecognized `AuthType` (anything other than `'NONE'` / `'AWS_IAM'`),
201
199
  * or an unsupported intrinsic function in `IntegrationUri`. (Lambda::Url
202
200
  * with `InvokeMode: RESPONSE_STREAM` is a normal route dispatched via
203
- * the streaming protocol — #467. Lambda::Url with `AuthType: 'AWS_IAM'`
204
- * is a normal route verified through the SigV4 pipeline — #621.)
201
+ * the streaming protocol. Lambda::Url with `AuthType: 'AWS_IAM'`
202
+ * is a normal route verified through the SigV4 pipeline.)
205
203
  *
206
- * The message names every offending route and points the user at the
207
- * deferred follow-up PR (8b for authorizers, etc.). Hard-error at
208
- * discovery so the server never starts in a half-working state.
204
+ * The message names every offending route. Hard-error at discovery so
205
+ * the server never starts in a half-working state.
209
206
  */
210
- var RouteDiscoveryError = class RouteDiscoveryError extends CdkdError {
207
+ var RouteDiscoveryError = class RouteDiscoveryError extends CdkLocalError {
211
208
  constructor(message, cause) {
212
209
  super(message, "ROUTE_DISCOVERY_ERROR", cause);
213
210
  this.name = "RouteDiscoveryError";
@@ -215,13 +212,13 @@ var RouteDiscoveryError = class RouteDiscoveryError extends CdkdError {
215
212
  }
216
213
  };
217
214
  /**
218
- * Signals a `cdkl start-service` orchestration failure (Phase 2
219
- * of #262 — `AWS::ECS::Service` emulator). Distinct from
220
- * `LocalRunTaskError` because the service runner has its own lifecycle
221
- * (long-running replica pool, restart-on-exit), so a failure inside it
222
- * carries different operator semantics than a one-shot task failure.
215
+ * Signals a `cdkl start-service` orchestration failure
216
+ * (`AWS::ECS::Service` emulator). The service runner has its own
217
+ * lifecycle (long-running replica pool, restart-on-exit), so a failure
218
+ * inside it carries different operator semantics than a one-shot task
219
+ * failure.
223
220
  */
224
- var LocalStartServiceError = class LocalStartServiceError extends CdkdError {
221
+ var LocalStartServiceError = class LocalStartServiceError extends CdkLocalError {
225
222
  constructor(message, cause) {
226
223
  super(message, "LOCAL_START_SERVICE_ERROR", cause);
227
224
  this.name = "LocalStartServiceError";
@@ -231,14 +228,14 @@ var LocalStartServiceError = class LocalStartServiceError extends CdkdError {
231
228
  /**
232
229
  * Check if error is a cdk-local error
233
230
  */
234
- function isCdkdError(error) {
235
- return error instanceof CdkdError;
231
+ function isCdkLocalError(error) {
232
+ return error instanceof CdkLocalError;
236
233
  }
237
234
  /**
238
235
  * Format error for display
239
236
  */
240
237
  function formatError(error) {
241
- if (isCdkdError(error)) {
238
+ if (isCdkLocalError(error)) {
242
239
  let message = `${error.name}: ${error.message}`;
243
240
  if (error.cause) message += `\nCaused by: ${error.cause.message}`;
244
241
  return message;
@@ -249,20 +246,21 @@ function formatError(error) {
249
246
  /**
250
247
  * Global error handler
251
248
  *
252
- * Default exit code is 1 (general error). `PartialFailureError`
253
- * overrides it to 2 so callers can distinguish "command crashed /
254
- * unauthorized / bad arguments" from "command completed but some
255
- * resources are still in an error state, re-run to clean up".
249
+ * Default exit code is 1 (general error). A {@link CdkLocalError}
250
+ * subclass may override it by declaring a custom `exitCode` field so
251
+ * callers can distinguish "command crashed / unauthorized / bad
252
+ * arguments" from "command completed but some resources are still in an
253
+ * error state, re-run to clean up".
256
254
  *
257
- * A {@link CdkdError} subclass may set `silent = true` to suppress the
258
- * default `logger.error` line — used by `cdkd drift` where the command
259
- * has already printed a richer report and only needs the exit code.
255
+ * A {@link CdkLocalError} subclass may set `silent = true` to suppress
256
+ * the default `logger.error` line — used when the command has already
257
+ * printed a richer report and only needs the exit code.
260
258
  */
261
259
  function handleError(error) {
262
260
  const logger = getLogger();
263
- if (!(error instanceof CdkdError && error.silent)) logger.error(formatError(error));
261
+ if (!(error instanceof CdkLocalError && error.silent)) logger.error(formatError(error));
264
262
  if (error instanceof Error && error.stack) logger.debug("Stack trace:", error.stack);
265
- const customExitCode = error instanceof CdkdError ? error.exitCode : void 0;
263
+ const customExitCode = error instanceof CdkLocalError ? error.exitCode : void 0;
266
264
  const exitCode = typeof customExitCode === "number" ? customExitCode : 1;
267
265
  process.exit(exitCode);
268
266
  }
@@ -413,6 +411,9 @@ function loadCdkJson() {
413
411
  return null;
414
412
  }
415
413
  }
414
+ function normalizeGlobList(value) {
415
+ return (typeof value === "string" ? [value] : Array.isArray(value) ? value : []).filter((entry) => typeof entry === "string" && entry.length > 0);
416
+ }
416
417
  /**
417
418
  * Resolve the `--app` option from CLI, `CDKL_APP` env var, or `cdk.json`.
418
419
  *
@@ -430,6 +431,26 @@ function resolveApp(cliApp) {
430
431
  if (envApp) return envApp;
431
432
  return loadCdkJson()?.app ?? void 0;
432
433
  }
434
+ /**
435
+ * Resolve the `cdk.json` `watch` block, mirroring `cdk watch`'s
436
+ * include / exclude semantics for `cdkl start-api --watch` source-tree
437
+ * watching.
438
+ *
439
+ * Defaults when the keys are absent: `include` -> `['**']` (watch the
440
+ * whole app directory), `exclude` -> `[]`. Unlike `cdk watch`, a missing
441
+ * `watch` block is NOT an error — `--watch` still works against the
442
+ * defaults. The caller layers in mandatory excludes (the synth output
443
+ * directory, `node_modules`, `.git`) so re-synth writes never re-trigger
444
+ * a reload and large noise directories are pruned.
445
+ */
446
+ function resolveWatchConfig() {
447
+ const watch = loadCdkJson()?.watch;
448
+ const include = normalizeGlobList(watch?.include);
449
+ return {
450
+ include: include.length > 0 ? include : ["**"],
451
+ exclude: normalizeGlobList(watch?.exclude)
452
+ };
453
+ }
433
454
 
434
455
  //#endregion
435
456
  //#region src/cli/cdk-path.ts
@@ -1297,6 +1318,12 @@ function resolveImageIntrinsicAny(node, resources, context) {
1297
1318
  if (resources[logicalId]?.Type !== "AWS::ECR::Repository") return void 0;
1298
1319
  const cached = context?.stateResources?.[logicalId]?.attributes?.[attr];
1299
1320
  if (typeof cached === "string" && cached.length > 0) return cached;
1321
+ const physicalId = context?.stateResources?.[logicalId]?.physicalId;
1322
+ const p = context?.pseudoParameters;
1323
+ if (physicalId && p?.region && p.accountId) {
1324
+ if (attr === "Arn" && p.partition) return `arn:${p.partition}:ecr:${p.region}:${p.accountId}:repository/${physicalId}`;
1325
+ if (attr === "RepositoryUri" && p.urlSuffix) return `${p.accountId}.dkr.ecr.${p.region}.${p.urlSuffix}/${physicalId}`;
1326
+ }
1300
1327
  return;
1301
1328
  }
1302
1329
  if (intrinsic === "Fn::Split") {
@@ -5763,6 +5790,99 @@ function createLocalInvokeCommand(opts = {}) {
5763
5790
  return invoke;
5764
5791
  }
5765
5792
 
5793
+ //#endregion
5794
+ //#region src/utils/glob-match.ts
5795
+ /**
5796
+ * Normalize a glob and split it into path-segment tokens. Returns
5797
+ * `null` for an empty pattern (callers skip it). A slash-free pattern is
5798
+ * prefixed with a `**` token so it matches its basename at any depth.
5799
+ */
5800
+ function compilePattern(glob) {
5801
+ const g = glob.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
5802
+ if (g === "") return null;
5803
+ if (!g.includes("/")) return ["**", g];
5804
+ return g.split("/");
5805
+ }
5806
+ /**
5807
+ * Linear wildcard match for ONE path segment (no `/`). `*` matches any
5808
+ * run of characters, `?` matches exactly one. Classic two-pointer
5809
+ * algorithm with single backtrack pointer — O(len(pat) * len(str)),
5810
+ * never exponential.
5811
+ */
5812
+ function matchSegment(pat, str) {
5813
+ let s = 0;
5814
+ let p = 0;
5815
+ let starP = -1;
5816
+ let starS = 0;
5817
+ while (s < str.length) {
5818
+ const pc = pat[p];
5819
+ if (p < pat.length && (pc === str[s] || pc === "?")) {
5820
+ s++;
5821
+ p++;
5822
+ } else if (pc === "*") {
5823
+ starP = p;
5824
+ starS = s;
5825
+ p++;
5826
+ } else if (starP !== -1) {
5827
+ p = starP + 1;
5828
+ starS++;
5829
+ s = starS;
5830
+ } else return false;
5831
+ }
5832
+ while (pat[p] === "*") p++;
5833
+ return p === pat.length;
5834
+ }
5835
+ /**
5836
+ * Match a compiled pattern's segment tokens against a path's segments. A
5837
+ * `**` token matches zero or more path segments; every other token
5838
+ * matches exactly one segment via {@link matchSegment}. The pattern
5839
+ * matches when it consumes a PREFIX of the path (the contents rule), so
5840
+ * a directory pattern also matches everything beneath it. Single
5841
+ * backtrack pointer over `**` — O(patSegs * pathSegs), never
5842
+ * exponential.
5843
+ */
5844
+ function matchSegments(pat, path) {
5845
+ let pi = 0;
5846
+ let si = 0;
5847
+ let starPi = -1;
5848
+ let starSi = 0;
5849
+ while (si < path.length) {
5850
+ if (pi === pat.length) return true;
5851
+ if (pat[pi] === "**") {
5852
+ starPi = pi;
5853
+ starSi = si;
5854
+ pi++;
5855
+ } else if (matchSegment(pat[pi], path[si])) {
5856
+ pi++;
5857
+ si++;
5858
+ } else if (starPi !== -1) {
5859
+ pi = starPi + 1;
5860
+ starSi++;
5861
+ si = starSi;
5862
+ } else return false;
5863
+ }
5864
+ if (pi === pat.length) return true;
5865
+ while (pi < pat.length && pat[pi] === "**") pi++;
5866
+ return pi === pat.length;
5867
+ }
5868
+ /**
5869
+ * Compile a list of globs into a single matcher. The returned predicate
5870
+ * is `true` when the (relative, POSIX-normalized) path matches ANY
5871
+ * pattern. An empty or all-invalid pattern list yields a matcher that
5872
+ * never matches.
5873
+ */
5874
+ function createGlobMatcher(patterns) {
5875
+ const compiled = [];
5876
+ for (const p of patterns) {
5877
+ const segs = compilePattern(p);
5878
+ if (segs) compiled.push(segs);
5879
+ }
5880
+ return (relPath) => {
5881
+ const pathSegs = relPath.replace(/\\/g, "/").split("/");
5882
+ return compiled.some((pat) => matchSegments(pat, pathSegs));
5883
+ };
5884
+ }
5885
+
5766
5886
  //#endregion
5767
5887
  //#region src/local/intrinsic-lambda-arn.ts
5768
5888
  /**
@@ -14310,7 +14430,8 @@ function createFileWatcher(options) {
14310
14430
  const watcher = chokidar.watch([...options.paths], {
14311
14431
  ignoreInitial,
14312
14432
  followSymlinks: false,
14313
- ignorePermissionErrors: true
14433
+ ignorePermissionErrors: true,
14434
+ ...options.ignored && { ignored: options.ignored }
14314
14435
  });
14315
14436
  let timer = null;
14316
14437
  let closed = false;
@@ -14328,34 +14449,24 @@ function createFileWatcher(options) {
14328
14449
  }, debounceMs);
14329
14450
  timer.unref?.();
14330
14451
  };
14331
- watcher.on("add", fire);
14332
- watcher.on("change", fire);
14333
- watcher.on("unlink", fire);
14452
+ const onEvent = (path) => {
14453
+ if (options.shouldTrigger && !options.shouldTrigger(path)) return;
14454
+ fire();
14455
+ };
14456
+ watcher.on("add", onEvent);
14457
+ watcher.on("change", onEvent);
14458
+ watcher.on("unlink", onEvent);
14334
14459
  watcher.on("error", (err) => {
14335
14460
  logger.debug(`chokidar error: ${err instanceof Error ? err.message : String(err)}. Continuing.`);
14336
14461
  });
14337
- let currentPaths = new Set(options.paths);
14338
- return {
14339
- update: (paths) => {
14340
- if (closed) return;
14341
- const next = new Set(paths);
14342
- const toAdd = [];
14343
- const toRemove = [];
14344
- for (const p of next) if (!currentPaths.has(p)) toAdd.push(p);
14345
- for (const p of currentPaths) if (!next.has(p)) toRemove.push(p);
14346
- if (toAdd.length > 0) watcher.add(toAdd);
14347
- if (toRemove.length > 0) watcher.unwatch(toRemove);
14348
- currentPaths = next;
14349
- },
14350
- close: async () => {
14351
- closed = true;
14352
- if (timer) {
14353
- clearTimeout(timer);
14354
- timer = null;
14355
- }
14356
- await watcher.close();
14462
+ return { close: async () => {
14463
+ closed = true;
14464
+ if (timer) {
14465
+ clearTimeout(timer);
14466
+ timer = null;
14357
14467
  }
14358
- };
14468
+ await watcher.close();
14469
+ } };
14359
14470
  }
14360
14471
 
14361
14472
  //#endregion
@@ -14429,7 +14540,6 @@ async function localStartApiCommand(target, options, extraStateProviders) {
14429
14540
  const inlineTmpDirs = /* @__PURE__ */ new Set();
14430
14541
  const layerTmpDirs = /* @__PURE__ */ new Set();
14431
14542
  let profileCredsFile;
14432
- const lastAssetPaths = { value: [] };
14433
14543
  const authorizerCache = createAuthorizerCache();
14434
14544
  const jwksCache = createJwksCache();
14435
14545
  const jwksWarnedUrls = /* @__PURE__ */ new Set();
@@ -14558,30 +14668,7 @@ async function localStartApiCommand(target, options, extraStateProviders) {
14558
14668
  });
14559
14669
  return pool;
14560
14670
  };
14561
- /**
14562
- * Compute the watched-asset list from a spec map. Pure helper —
14563
- * keeps the side-effect (`lastAssetPaths.value = ...`) confined to
14564
- * the post-swap call sites (initial boot + post-reload). For ZIP
14565
- * Lambdas `codeDir` is either the unzipped asset directory or the
14566
- * inline-code tmpdir; both are watch-worthy. IMAGE Lambdas
14567
- * (`kind: 'image'`) don't have a host-side bind-mount source — the
14568
- * code is baked into the docker image at build time. Their build
14569
- * context (Dockerfile + source directory) is rebuilt on every
14570
- * reload via `synthesizeAndBuild` → `buildContainerSpec` →
14571
- * `resolveContainerImageForStartApi`, so a source edit DOES trigger
14572
- * rebuild AND the deterministic `image` tag changes — but watching
14573
- * the build-context dir explicitly here is deferred to a follow-up
14574
- * (the watched-asset list is currently sourced from `cdk.out/`
14575
- * which transitively covers most container-Lambda asset dirs since
14576
- * `cdk synth` re-stages them on every synth call).
14577
- */
14578
- const computeAssetPaths = (specs) => {
14579
- const assetPaths = /* @__PURE__ */ new Set();
14580
- for (const spec of specs.values()) if (spec.kind === "zip") assetPaths.add(spec.codeDir);
14581
- return [...assetPaths];
14582
- };
14583
14671
  const initialMaterial = await synthesizeAndBuild();
14584
- lastAssetPaths.value = computeAssetPaths(initialMaterial.specs);
14585
14672
  await prewarmJwks(initialMaterial.routes, jwksCache);
14586
14673
  warnVpcConfigLambdas(initialMaterial.routes, initialMaterial.stacks ?? []);
14587
14674
  sigV4CredentialsLoader = defaultCredentialsLoader();
@@ -14722,23 +14809,27 @@ async function localStartApiCommand(target, options, extraStateProviders) {
14722
14809
  let watcher;
14723
14810
  let reloadChain = Promise.resolve();
14724
14811
  if (options.watch) {
14812
+ const watchRoot = process.cwd();
14813
+ const { ignored, shouldTrigger, excludePatterns } = createWatchPredicates({
14814
+ watchRoot,
14815
+ output: options.output,
14816
+ watchConfig: resolveWatchConfig()
14817
+ });
14725
14818
  watcher = createFileWatcher({
14726
- paths: [options.output, ...lastAssetPaths.value],
14819
+ paths: [watchRoot],
14820
+ ignored,
14821
+ shouldTrigger,
14727
14822
  onChange: () => {
14728
- logger.info("Detected file change; reloading...");
14823
+ logger.info("Detected source change; reloading...");
14729
14824
  reloadChain = reloadChain.then(() => reloadAllServers({
14730
14825
  synthesizeAndBuild,
14731
14826
  servers,
14732
14827
  buildPool,
14733
- computeAssetPaths,
14734
- lastAssetPaths,
14735
- watcher,
14736
- output: options.output,
14737
14828
  logger
14738
14829
  })).catch(() => void 0);
14739
14830
  }
14740
14831
  });
14741
- logger.info(`Watching ${options.output} (and ${lastAssetPaths.value.length} asset dir(s))`);
14832
+ logger.info(`Watching ${watchRoot} for source changes (excluding ${excludePatterns.join(", ")}).`);
14742
14833
  }
14743
14834
  let shutdownStarted = false;
14744
14835
  let firstSignal;
@@ -14840,6 +14931,52 @@ async function localStartApiCommand(target, options, extraStateProviders) {
14840
14931
  await new Promise(() => void 0);
14841
14932
  }
14842
14933
  /**
14934
+ * Build the `--watch` file-watcher predicates for a source tree rooted
14935
+ * at `watchRoot` (the synth working directory).
14936
+ *
14937
+ * The synth output directory is always excluded so the reload's own
14938
+ * re-synth writes never re-trigger the watcher (no loop); `node_modules`
14939
+ * / `.git` are excluded for traversal cost. `cdk.json` `watch.exclude`
14940
+ * globs layer on top, and `watch.include` gates which changes fire a
14941
+ * reload. The output dir is only added as a glob when it lives UNDER the
14942
+ * watch root — when it equals the root (`''`) or sits outside it
14943
+ * (`..`-prefixed), a watcher rooted at `watchRoot` never traverses it,
14944
+ * so no exclude entry is needed (and a `''` / `..` glob is meaningless).
14945
+ *
14946
+ * `@internal` exported for unit tests (the exclude/include composition +
14947
+ * the output-dir relativization edge cases).
14948
+ */
14949
+ function createWatchPredicates(args) {
14950
+ const { watchRoot, output, watchConfig } = args;
14951
+ const toRel = (abs) => path.relative(watchRoot, abs).split(path.sep).join("/");
14952
+ const outputRel = toRel(path.resolve(watchRoot, output));
14953
+ const excludePatterns = [
14954
+ ...outputRel !== "" && !outputRel.startsWith("..") ? [outputRel] : [],
14955
+ "node_modules",
14956
+ ".git",
14957
+ ...watchConfig.exclude
14958
+ ];
14959
+ const excludeMatcher = createGlobMatcher(excludePatterns);
14960
+ const includeMatcher = createGlobMatcher(watchConfig.include);
14961
+ const ignored = (absPath) => {
14962
+ const rel = toRel(absPath);
14963
+ if (rel === "") return false;
14964
+ if (rel.startsWith("..")) return true;
14965
+ return excludeMatcher(rel);
14966
+ };
14967
+ const shouldTrigger = (absPath) => {
14968
+ const rel = toRel(absPath);
14969
+ if (rel === "" || rel.startsWith("..")) return false;
14970
+ if (excludeMatcher(rel)) return false;
14971
+ return includeMatcher(rel);
14972
+ };
14973
+ return {
14974
+ ignored,
14975
+ shouldTrigger,
14976
+ excludePatterns
14977
+ };
14978
+ }
14979
+ /**
14843
14980
  * Match the `--stack` pattern (or single-stack auto-detect) to a list
14844
14981
  * of stacks the route-discovery walks. Mirrors the deploy/diff matcher
14845
14982
  * routing rules.
@@ -15741,7 +15878,7 @@ function warnSsrfRiskyIntegrations(routes, logger) {
15741
15878
  * server restart in v1.
15742
15879
  */
15743
15880
  async function reloadAllServers(args) {
15744
- const { synthesizeAndBuild, servers, buildPool, computeAssetPaths, lastAssetPaths, watcher, output, logger } = args;
15881
+ const { synthesizeAndBuild, servers, buildPool, logger } = args;
15745
15882
  let material;
15746
15883
  try {
15747
15884
  material = await synthesizeAndBuild();
@@ -15772,8 +15909,6 @@ async function reloadAllServers(args) {
15772
15909
  logger.debug(`Previous pool dispose() failed for ${group.displayName}: ${err instanceof Error ? err.message : String(err)}`);
15773
15910
  });
15774
15911
  }
15775
- lastAssetPaths.value = computeAssetPaths(material.specs);
15776
- if (watcher) watcher.update([output, ...lastAssetPaths.value]);
15777
15912
  printPerServerRouteTables(servers);
15778
15913
  const allRoutes = servers.flatMap((s) => s.group.routes.map((r) => r.route));
15779
15914
  warnUnsupportedRoutes(allRoutes, logger);
@@ -15957,7 +16092,7 @@ function resolveMtlsConfig(options) {
15957
16092
  */
15958
16093
  function createLocalStartApiCommand(opts = {}) {
15959
16094
  setEmbedConfig(opts.embedConfig);
15960
- 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("--all-stacks", "Serve every stack's API in a multi-stack app (each API on its own port) instead of erroring out. Mutually exclusive with a positional target, --stack, and an explicit --from-cfn-stack <name>; the bare --from-cfn-stack flag stays compatible (binds each routed stack to its own CFn stack).").default(false)).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when 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("--strict-sigv4", "Opt-in: DENY AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id — e.g. a federated / Cognito Identity Pool / cross-account signer — OR no local AWS credentials configured) instead of the default warn-and-pass. DEFAULT off: cdk-local warn-and-passes unverifiable IAM requests with a placeholder principalId so local dev exercises app logic without reproducing an auth boundary it cannot fully emulate. OAC-fronted Function URLs always warn-and-pass regardless.").default(false)).action(withErrorHandling(async (target, options) => {
16095
+ 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("--all-stacks", "Serve every stack's API in a multi-stack app (each API on its own port) instead of erroring out. Mutually exclusive with a positional target, --stack, and an explicit --from-cfn-stack <name>; the bare --from-cfn-stack flag stays compatible (binds each routed stack to its own CFn stack).").default(false)).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when the CDK app's source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <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("--strict-sigv4", "Opt-in: DENY AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id — e.g. a federated / Cognito Identity Pool / cross-account signer — OR no local AWS credentials configured) instead of the default warn-and-pass. DEFAULT off: cdk-local warn-and-passes unverifiable IAM requests with a placeholder principalId so local dev exercises app logic without reproducing an auth boundary it cannot fully emulate. OAC-fronted Function URLs always warn-and-pass regardless.").default(false)).action(withErrorHandling(async (target, options) => {
15961
16096
  await localStartApiCommand(target, options, opts.extraStateProviders);
15962
16097
  }));
15963
16098
  [
@@ -18540,5 +18675,5 @@ function createLocalStartServiceCommand(opts = {}) {
18540
18675
  }
18541
18676
 
18542
18677
  //#endregion
18543
- export { resolveCfnFallbackRegion as C, CfnLocalStateProvider as E, rejectExplicitCfnStackWithMultipleStacks as S, resolveCfnStackName as T, resolveLambdaArnIntrinsic as _, resolveSelectionExpression as a, createLocalStateProvider as b, translateLambdaResponse as c, buildRestV1Event as d, discoverWebSocketApis as f, pickRefLogicalId as g, discoverRoutes as h, createLocalStartApiCommand as i, applyAuthorizerOverlay as l, parseSelectionExpressionPath as m, getContainerNetworkIp as n, resolveServiceIntegrationParameters as o, discoverWebSocketApisOrThrow as p, createLocalRunTaskCommand as r, matchRoute as s, createLocalStartServiceCommand as t, buildHttpApiV2Event as u, createLocalInvokeCommand as v, resolveCfnRegion as w, isCfnFlagPresent as x, LocalStateSourceError as y };
18544
- //# sourceMappingURL=local-start-service-Oxi200hs.js.map
18678
+ export { LocalStateSourceError as A, discoverWebSocketApis as C, pickRefLogicalId as D, discoverRoutes as E, resolveCfnRegion as F, resolveCfnStackName as I, CfnLocalStateProvider as L, isCfnFlagPresent as M, rejectExplicitCfnStackWithMultipleStacks as N, resolveLambdaArnIntrinsic as O, resolveCfnFallbackRegion as P, buildRestV1Event as S, parseSelectionExpressionPath as T, invokeTokenAuthorizer as _, createAuthorizerCache as a, applyAuthorizerOverlay as b, buildCognitoJwksUrl as c, verifyCognitoJwt as d, verifyJwtAuthorizer as f, invokeRequestAuthorizer as g, evaluateCachedLambdaPolicy as h, createLocalStartApiCommand as i, createLocalStateProvider as j, createLocalInvokeCommand as k, buildJwksUrlFromIssuer as l, computeRequestIdentityHash as m, getContainerNetworkIp as n, resolveSelectionExpression as o, buildMethodArn as p, createLocalRunTaskCommand as r, resolveServiceIntegrationParameters as s, createLocalStartServiceCommand as t, createJwksCache as u, matchRoute as v, discoverWebSocketApisOrThrow as w, buildHttpApiV2Event as x, translateLambdaResponse as y };
18679
+ //# sourceMappingURL=local-start-service-CorTpgvY.js.map