@go-to-k/cdkd 0.80.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/README.md +13 -2
- package/dist/cli.js +302 -175
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.81.0.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.80.0.tgz +0 -0
package/README.md
CHANGED
|
@@ -584,12 +584,16 @@ containers `cdkd local invoke` uses. Modeled on `sam local start-api`
|
|
|
584
584
|
but reusing cdkd's synthesis / route-discovery plumbing.
|
|
585
585
|
|
|
586
586
|
```bash
|
|
587
|
-
# Auto-allocate
|
|
587
|
+
# Auto-allocate one port PER discovered API (printed at startup)
|
|
588
588
|
cdkd local start-api
|
|
589
589
|
|
|
590
|
-
# Pin to port 3000
|
|
590
|
+
# Pin the FIRST server to port 3000; subsequent APIs get 3001, 3002, ...
|
|
591
591
|
cdkd local start-api --port 3000
|
|
592
592
|
|
|
593
|
+
# Restrict to a single API by its CDK logical id (HTTP API / REST API logical
|
|
594
|
+
# id, or the backing Lambda's logical id for Function URLs)
|
|
595
|
+
cdkd local start-api --api MyAdminApi
|
|
596
|
+
|
|
593
597
|
# Pre-warm one container per Lambda at server boot — eliminates first-request cold start
|
|
594
598
|
cdkd local start-api --warm
|
|
595
599
|
|
|
@@ -606,6 +610,13 @@ cdkd local start-api --watch
|
|
|
606
610
|
cdkd local start-api --stage prod
|
|
607
611
|
```
|
|
608
612
|
|
|
613
|
+
**One server per API** (since v0.81): every discovered API surface gets its
|
|
614
|
+
own HTTP server on its own port, so authorizers, CORS configs, and stage
|
|
615
|
+
variables stay scoped to the owning API and never bleed across APIs that
|
|
616
|
+
happen to share a path. `cdkd local start-api` prints one
|
|
617
|
+
`Server listening on http://<host>:<port> (<API> (<kind>))` line per
|
|
618
|
+
server at startup; pass `--api <id>` to launch only one of them.
|
|
619
|
+
|
|
609
620
|
Scope: REST v1 + HTTP API + Function URL with AWS_PROXY integrations.
|
|
610
621
|
Authorizers (Lambda TOKEN/REQUEST + Cognito User Pool + HTTP v2 JWT),
|
|
611
622
|
VPC-config Lambda warnings, CORS preflight, hot reload, and stage
|
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
|
|
44854
|
-
if (!apiId || !
|
|
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:
|
|
44865
|
+
RouteKey: routeKey,
|
|
44866
44866
|
Target: properties["Target"],
|
|
44867
44867
|
AuthorizationType: properties["AuthorizationType"],
|
|
44868
44868
|
AuthorizerId: properties["AuthorizerId"]
|
|
@@ -70651,8 +70651,38 @@ async function pullImage(image, skipPull) {
|
|
|
70651
70651
|
logger.debug(`Skipping docker pull for ${image} (--no-pull)`);
|
|
70652
70652
|
return;
|
|
70653
70653
|
}
|
|
70654
|
-
|
|
70655
|
-
|
|
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
|
+
});
|
|
70656
70686
|
}
|
|
70657
70687
|
async function runDetached(opts) {
|
|
70658
70688
|
const args = ["run", "-d", "--rm"];
|
|
@@ -71367,8 +71397,8 @@ function discoverHttpApiRoute(logicalId, resource, template, stackName) {
|
|
|
71367
71397
|
);
|
|
71368
71398
|
}
|
|
71369
71399
|
}
|
|
71370
|
-
const
|
|
71371
|
-
if (typeof
|
|
71400
|
+
const routeKey = props["RouteKey"];
|
|
71401
|
+
if (typeof routeKey !== "string" || routeKey.length === 0) {
|
|
71372
71402
|
throw new Error(
|
|
71373
71403
|
`${stackName}/${logicalId} (AWS::ApiGatewayV2::Route): RouteKey must be a string`
|
|
71374
71404
|
);
|
|
@@ -71404,7 +71434,7 @@ function discoverHttpApiRoute(logicalId, resource, template, stackName) {
|
|
|
71404
71434
|
integrationProps["IntegrationUri"],
|
|
71405
71435
|
`${stackName}/${integrationLogicalId}.IntegrationUri`
|
|
71406
71436
|
);
|
|
71407
|
-
const { method, pathPattern } = parseRouteKey(
|
|
71437
|
+
const { method, pathPattern } = parseRouteKey(routeKey);
|
|
71408
71438
|
return [
|
|
71409
71439
|
{
|
|
71410
71440
|
method,
|
|
@@ -71519,14 +71549,14 @@ function parseHttpApiTargetIntegration(target, location) {
|
|
|
71519
71549
|
)}).`
|
|
71520
71550
|
);
|
|
71521
71551
|
}
|
|
71522
|
-
function parseRouteKey(
|
|
71523
|
-
if (
|
|
71552
|
+
function parseRouteKey(routeKey) {
|
|
71553
|
+
if (routeKey === "$default") {
|
|
71524
71554
|
return { method: "ANY", pathPattern: "$default" };
|
|
71525
71555
|
}
|
|
71526
|
-
const m = /^([A-Za-z]+)\s+(\S+)$/.exec(
|
|
71556
|
+
const m = /^([A-Za-z]+)\s+(\S+)$/.exec(routeKey);
|
|
71527
71557
|
if (!m) {
|
|
71528
71558
|
throw new Error(
|
|
71529
|
-
`RouteKey '${
|
|
71559
|
+
`RouteKey '${routeKey}' is malformed: expected '<METHOD> <path>' (e.g. 'GET /items/{id}') or '$default'.`
|
|
71530
71560
|
);
|
|
71531
71561
|
}
|
|
71532
71562
|
return { method: m[1].toUpperCase(), pathPattern: m[2] };
|
|
@@ -71867,10 +71897,10 @@ function buildHttpApiV2Event(req, ctx, opts = {}) {
|
|
|
71867
71897
|
const contentType = headers["content-type"] ?? "";
|
|
71868
71898
|
const { body, isBase64Encoded } = encodeBody(req.body, contentType);
|
|
71869
71899
|
const now = opts.now ? opts.now() : /* @__PURE__ */ new Date();
|
|
71870
|
-
const
|
|
71900
|
+
const routeKey = ctx.route.pathPattern === "$default" ? "$default" : `${ctx.route.method} ${ctx.route.pathPattern}`;
|
|
71871
71901
|
const event = {
|
|
71872
71902
|
version: "2.0",
|
|
71873
|
-
routeKey
|
|
71903
|
+
routeKey,
|
|
71874
71904
|
rawPath,
|
|
71875
71905
|
rawQueryString,
|
|
71876
71906
|
cookies,
|
|
@@ -71894,7 +71924,7 @@ function buildHttpApiV2Event(req, ctx, opts = {}) {
|
|
|
71894
71924
|
userAgent
|
|
71895
71925
|
},
|
|
71896
71926
|
requestId: randomUUID(),
|
|
71897
|
-
routeKey
|
|
71927
|
+
routeKey,
|
|
71898
71928
|
stage: ctx.route.stage,
|
|
71899
71929
|
time: formatRequestTime(now),
|
|
71900
71930
|
timeEpoch: now.getTime(),
|
|
@@ -73798,6 +73828,74 @@ function writeError(res, statusCode, body = '{"message":"Internal server error"}
|
|
|
73798
73828
|
res.end(body);
|
|
73799
73829
|
}
|
|
73800
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
|
+
|
|
73801
73899
|
// src/local/stage-resolver.ts
|
|
73802
73900
|
function buildStageMap(template, stageOverride) {
|
|
73803
73901
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -75689,92 +75787,6 @@ function createFileWatcher(options) {
|
|
|
75689
75787
|
};
|
|
75690
75788
|
}
|
|
75691
75789
|
|
|
75692
|
-
// src/local/reload-orchestrator.ts
|
|
75693
|
-
function createReloadOrchestrator(deps) {
|
|
75694
|
-
const logger = getLogger().child("start-api-reload");
|
|
75695
|
-
let chain = Promise.resolve();
|
|
75696
|
-
return {
|
|
75697
|
-
reload() {
|
|
75698
|
-
const next = chain.then(() => runOneReload(deps, logger));
|
|
75699
|
-
chain = next.catch(() => void 0);
|
|
75700
|
-
return next;
|
|
75701
|
-
}
|
|
75702
|
-
};
|
|
75703
|
-
}
|
|
75704
|
-
async function runOneReload(deps, logger) {
|
|
75705
|
-
const previousState = deps.getServerState();
|
|
75706
|
-
const start = Date.now();
|
|
75707
|
-
let material;
|
|
75708
|
-
try {
|
|
75709
|
-
material = await deps.synthesizeAndBuild();
|
|
75710
|
-
} catch (err) {
|
|
75711
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
75712
|
-
logger.warn(`cdk synth failed during reload; keeping previous version. (${reason})`);
|
|
75713
|
-
return { ok: false, reason, added: [], removed: [], rebuiltLambdas: [] };
|
|
75714
|
-
}
|
|
75715
|
-
const oldRoutes = previousState.routes;
|
|
75716
|
-
const newRoutes = material.routes;
|
|
75717
|
-
const oldKeys = new Set(oldRoutes.map((r) => routeKey(r.route)));
|
|
75718
|
-
const newKeys = new Set(newRoutes.map((r) => routeKey(r.route)));
|
|
75719
|
-
const added = newRoutes.filter((r) => !oldKeys.has(routeKey(r.route)));
|
|
75720
|
-
const removed = oldRoutes.filter((r) => !newKeys.has(routeKey(r.route)));
|
|
75721
|
-
const previousSpecs = pickSpecsFromState(previousState);
|
|
75722
|
-
const rebuiltLambdas = [];
|
|
75723
|
-
for (const [logicalId, newSpec] of material.specs) {
|
|
75724
|
-
const oldSpec = previousSpecs.get(logicalId);
|
|
75725
|
-
if (!oldSpec)
|
|
75726
|
-
continue;
|
|
75727
|
-
if (specSignature(oldSpec) !== specSignature(newSpec)) {
|
|
75728
|
-
rebuiltLambdas.push(logicalId);
|
|
75729
|
-
}
|
|
75730
|
-
}
|
|
75731
|
-
const newPool = deps.buildPool(material.specs);
|
|
75732
|
-
Object.defineProperty(newPool, "__cdkdSpecs", {
|
|
75733
|
-
value: material.specs,
|
|
75734
|
-
enumerable: false,
|
|
75735
|
-
configurable: true
|
|
75736
|
-
});
|
|
75737
|
-
const newState = {
|
|
75738
|
-
routes: material.routes,
|
|
75739
|
-
pool: newPool,
|
|
75740
|
-
corsConfigByApiId: material.corsConfigByApiId
|
|
75741
|
-
};
|
|
75742
|
-
deps.setServerState(newState);
|
|
75743
|
-
void previousState.pool.dispose().catch(
|
|
75744
|
-
(err) => logger.debug(
|
|
75745
|
-
`Previous pool dispose() failed: ${err instanceof Error ? err.message : String(err)}`
|
|
75746
|
-
)
|
|
75747
|
-
);
|
|
75748
|
-
const elapsed = Date.now() - start;
|
|
75749
|
-
logger.info(
|
|
75750
|
-
`Reloaded in ${elapsed}ms: +${added.length} route(s), -${removed.length} route(s), ${rebuiltLambdas.length} Lambda(s) rebuilt.`
|
|
75751
|
-
);
|
|
75752
|
-
return {
|
|
75753
|
-
ok: true,
|
|
75754
|
-
added,
|
|
75755
|
-
removed,
|
|
75756
|
-
rebuiltLambdas,
|
|
75757
|
-
newState
|
|
75758
|
-
};
|
|
75759
|
-
}
|
|
75760
|
-
function routeKey(route) {
|
|
75761
|
-
return [route.method, route.pathPattern, route.lambdaLogicalId, route.source, route.apiVersion].map((s) => String(s)).join("|");
|
|
75762
|
-
}
|
|
75763
|
-
function specSignature(spec) {
|
|
75764
|
-
return JSON.stringify({
|
|
75765
|
-
codeDir: spec.codeDir,
|
|
75766
|
-
env: spec.env,
|
|
75767
|
-
handler: spec.lambda.handler,
|
|
75768
|
-
runtime: spec.lambda.runtime,
|
|
75769
|
-
containerHost: spec.containerHost,
|
|
75770
|
-
debugPort: spec.debugPort ?? null
|
|
75771
|
-
});
|
|
75772
|
-
}
|
|
75773
|
-
function pickSpecsFromState(state) {
|
|
75774
|
-
const tagged = state.pool.__cdkdSpecs;
|
|
75775
|
-
return tagged ?? /* @__PURE__ */ new Map();
|
|
75776
|
-
}
|
|
75777
|
-
|
|
75778
75790
|
// src/local/authorizer-cache.ts
|
|
75779
75791
|
function createAuthorizerCache(opts = {}) {
|
|
75780
75792
|
const now = opts.now ?? (() => Date.now());
|
|
@@ -75880,7 +75892,17 @@ async function localStartApiCommand(options) {
|
|
|
75880
75892
|
}
|
|
75881
75893
|
}
|
|
75882
75894
|
attachStageContext(routes, stageMap);
|
|
75883
|
-
|
|
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
|
+
}
|
|
75884
75906
|
const corsConfigByApiId = /* @__PURE__ */ new Map();
|
|
75885
75907
|
for (const stack of targetStacks) {
|
|
75886
75908
|
const m = buildCorsConfigByApiId(stack.template);
|
|
@@ -75933,88 +75955,91 @@ async function localStartApiCommand(options) {
|
|
|
75933
75955
|
return [...assetPaths];
|
|
75934
75956
|
};
|
|
75935
75957
|
const initialMaterial = await synthesizeAndBuild();
|
|
75936
|
-
const initialPool = buildPool(initialMaterial.specs);
|
|
75937
75958
|
lastAssetPaths.value = computeAssetPaths(initialMaterial.specs);
|
|
75938
75959
|
await prewarmJwks(initialMaterial.routes, jwksCache);
|
|
75939
75960
|
warnVpcConfigLambdas(initialMaterial.routes, initialMaterial.stacks ?? []);
|
|
75940
|
-
if (options.warm) {
|
|
75941
|
-
logger.info(`Pre-warming ${initialMaterial.specs.size} container(s)...`);
|
|
75942
|
-
const handles = await Promise.allSettled(
|
|
75943
|
-
[...initialMaterial.specs.keys()].map((id) => initialPool.acquire(id))
|
|
75944
|
-
);
|
|
75945
|
-
for (const result of handles) {
|
|
75946
|
-
if (result.status === "fulfilled") {
|
|
75947
|
-
initialPool.release(result.value);
|
|
75948
|
-
} else {
|
|
75949
|
-
logger.warn(
|
|
75950
|
-
`Pre-warm failed for one Lambda (cold start cost will apply on first request): ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
|
|
75951
|
-
);
|
|
75952
|
-
}
|
|
75953
|
-
}
|
|
75954
|
-
}
|
|
75955
75961
|
let maxTimeoutSec = 0;
|
|
75956
75962
|
for (const spec of initialMaterial.specs.values()) {
|
|
75957
75963
|
if (spec.lambda.timeoutSec > maxTimeoutSec)
|
|
75958
75964
|
maxTimeoutSec = spec.lambda.timeoutSec;
|
|
75959
75965
|
}
|
|
75960
75966
|
const rieTimeoutMs = Math.max(3e4, maxTimeoutSec * 2 * 1e3);
|
|
75961
|
-
const
|
|
75962
|
-
if (!Number.isFinite(
|
|
75967
|
+
const basePort = parseInt(options.port, 10);
|
|
75968
|
+
if (!Number.isFinite(basePort) || basePort < 0 || basePort > 65535) {
|
|
75963
75969
|
throw new Error(`--port must be 0..65535 (got ${options.port}).`);
|
|
75964
75970
|
}
|
|
75965
|
-
const
|
|
75966
|
-
|
|
75967
|
-
|
|
75968
|
-
|
|
75969
|
-
|
|
75970
|
-
|
|
75971
|
-
|
|
75972
|
-
|
|
75973
|
-
|
|
75974
|
-
|
|
75975
|
-
|
|
75976
|
-
|
|
75977
|
-
|
|
75978
|
-
|
|
75979
|
-
|
|
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);
|
|
75980
76012
|
logger.info(
|
|
75981
76013
|
`Per-Lambda concurrency: ${perLambdaConcurrency} (override with --per-lambda-concurrency)`
|
|
75982
76014
|
);
|
|
75983
|
-
|
|
75984
|
-
|
|
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
|
+
}
|
|
75985
76021
|
process.stdout.write("^C to stop and clean up containers.\n");
|
|
75986
76022
|
let watcher;
|
|
75987
|
-
let
|
|
76023
|
+
let reloadChain = Promise.resolve();
|
|
75988
76024
|
if (options.watch) {
|
|
75989
|
-
orchestrator = createReloadOrchestrator({
|
|
75990
|
-
synthesizeAndBuild,
|
|
75991
|
-
buildPool,
|
|
75992
|
-
setServerState: server.setServerState,
|
|
75993
|
-
getServerState: server.getServerState
|
|
75994
|
-
});
|
|
75995
76025
|
const initialWatchPaths = [options.output, ...lastAssetPaths.value];
|
|
75996
76026
|
watcher = createFileWatcher({
|
|
75997
76027
|
paths: initialWatchPaths,
|
|
75998
76028
|
onChange: () => {
|
|
75999
|
-
if (!orchestrator)
|
|
76000
|
-
return;
|
|
76001
76029
|
logger.info("Detected file change; reloading...");
|
|
76002
|
-
|
|
76003
|
-
|
|
76004
|
-
|
|
76005
|
-
|
|
76006
|
-
|
|
76007
|
-
|
|
76008
|
-
|
|
76009
|
-
|
|
76010
|
-
|
|
76011
|
-
|
|
76012
|
-
}
|
|
76013
|
-
|
|
76014
|
-
|
|
76015
|
-
`Reload failed: ${err instanceof Error ? err.message : String(err)}. Keeping previous version.`
|
|
76016
|
-
);
|
|
76017
|
-
});
|
|
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);
|
|
76018
76043
|
}
|
|
76019
76044
|
});
|
|
76020
76045
|
logger.info(`Watching ${options.output} (and ${lastAssetPaths.value.length} asset dir(s))`);
|
|
@@ -76041,16 +76066,28 @@ async function localStartApiCommand(options) {
|
|
|
76041
76066
|
logger.warn(`watcher.close() failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
76042
76067
|
}
|
|
76043
76068
|
}
|
|
76044
|
-
|
|
76045
|
-
|
|
76046
|
-
|
|
76047
|
-
|
|
76048
|
-
|
|
76049
|
-
|
|
76050
|
-
|
|
76051
|
-
|
|
76052
|
-
|
|
76053
|
-
|
|
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
|
+
);
|
|
76054
76091
|
for (const dir of inlineTmpDirs) {
|
|
76055
76092
|
try {
|
|
76056
76093
|
rmSync2(dir, { recursive: true, force: true });
|
|
@@ -76422,6 +76459,91 @@ function parsePerLambdaConcurrency(raw) {
|
|
|
76422
76459
|
}
|
|
76423
76460
|
return parsed;
|
|
76424
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
|
+
}
|
|
76425
76547
|
function parseDebugPort(raw) {
|
|
76426
76548
|
const parsed = parseInt(raw, 10);
|
|
76427
76549
|
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
|
|
@@ -76444,8 +76566,8 @@ function createLocalStartApiCommand() {
|
|
|
76444
76566
|
).addOption(new Option7("--no-pull", "Skip docker pull (cached image)")).addOption(
|
|
76445
76567
|
new Option7(
|
|
76446
76568
|
"--container-host <host>",
|
|
76447
|
-
"
|
|
76448
|
-
).default("
|
|
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")
|
|
76449
76571
|
).addOption(
|
|
76450
76572
|
new Option7(
|
|
76451
76573
|
"--debug-port-base <port>",
|
|
@@ -76471,6 +76593,11 @@ function createLocalStartApiCommand() {
|
|
|
76471
76593
|
"--stage <name>",
|
|
76472
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."
|
|
76473
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
|
+
)
|
|
76474
76601
|
).action(withErrorHandling(localStartApiCommand));
|
|
76475
76602
|
[...commonOptions, ...appOptions, ...contextOptions].forEach((opt) => startApi.addOption(opt));
|
|
76476
76603
|
startApi.addOption(deprecatedRegionOption);
|
|
@@ -77056,7 +77183,7 @@ function reorderArgs(argv) {
|
|
|
77056
77183
|
}
|
|
77057
77184
|
async function main() {
|
|
77058
77185
|
const program = new Command16();
|
|
77059
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
77186
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.81.0");
|
|
77060
77187
|
program.addCommand(createBootstrapCommand());
|
|
77061
77188
|
program.addCommand(createSynthCommand());
|
|
77062
77189
|
program.addCommand(createListCommand());
|