@go-to-k/cdkd 0.137.1 → 0.137.3
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 +158 -9
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -41121,7 +41121,7 @@ function discoverOneApi(logicalId, resource, template, stackName) {
|
|
|
41121
41121
|
apiLogicalId: logicalId,
|
|
41122
41122
|
apiStackName: stackName,
|
|
41123
41123
|
declaredAt,
|
|
41124
|
-
...apiCdkPath !==
|
|
41124
|
+
...apiCdkPath !== void 0 && { apiCdkPath },
|
|
41125
41125
|
routeSelectionExpression,
|
|
41126
41126
|
stage,
|
|
41127
41127
|
routes,
|
|
@@ -41306,12 +41306,45 @@ function pickRefLogicalId$2(value) {
|
|
|
41306
41306
|
}
|
|
41307
41307
|
function readApiCdkPath(logicalId, template) {
|
|
41308
41308
|
const resource = template.Resources?.[logicalId];
|
|
41309
|
-
if (!resource) return
|
|
41310
|
-
|
|
41309
|
+
if (!resource) return void 0;
|
|
41310
|
+
const path = readCdkPath(resource);
|
|
41311
|
+
return path === "" ? void 0 : path;
|
|
41311
41312
|
}
|
|
41312
41313
|
|
|
41313
41314
|
//#endregion
|
|
41314
41315
|
//#region src/local/websocket-event.ts
|
|
41316
|
+
/**
|
|
41317
|
+
* AWS API Gateway WebSocket event-payload builders.
|
|
41318
|
+
*
|
|
41319
|
+
* Spec: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html
|
|
41320
|
+
*
|
|
41321
|
+
* Three event types — CONNECT / MESSAGE / DISCONNECT — each carry a
|
|
41322
|
+
* shared {@link WebSocketRequestContext} plus per-event fields.
|
|
41323
|
+
*
|
|
41324
|
+
* Fields cdkd populates locally vs mocks (matches design Q1 in
|
|
41325
|
+
* `docs/design/462-websocket-api.md`):
|
|
41326
|
+
*
|
|
41327
|
+
* | Field | Source |
|
|
41328
|
+
* |--------------------------------------|-----------------------------------------|
|
|
41329
|
+
* | `connectionId` | UUID v4 generated at `$connect` |
|
|
41330
|
+
* | `requestId` / `extendedRequestId` | Generated UUID per event |
|
|
41331
|
+
* | `messageId` (MESSAGE only) | Generated UUID per event |
|
|
41332
|
+
* | `requestTime` / `requestTimeEpoch` | `Date.now()` at build time |
|
|
41333
|
+
* | `connectedAt` | Captured at `$connect` |
|
|
41334
|
+
* | `stage` | Resolved Stage Name; `'local'` default |
|
|
41335
|
+
* | `apiId` | `'local'` (mock) |
|
|
41336
|
+
* | `domainName` | `'localhost'` (mock) |
|
|
41337
|
+
* | `identity.sourceIp` | `req.socket.remoteAddress` (real) |
|
|
41338
|
+
* | `identity.userAgent` | Upgrade `User-Agent` header (real) |
|
|
41339
|
+
* | `headers`/`queryStringParameters` | Parsed from upgrade `req` (real) |
|
|
41340
|
+
* | `authorizer` | `null` in v1 (deferred) |
|
|
41341
|
+
*
|
|
41342
|
+
* Per-event `eventType` / `routeKey` / `messageDirection` are fixed by
|
|
41343
|
+
* the lifecycle stage, NOT by the route's `routeKey` field — `routeKey`
|
|
41344
|
+
* carries which user-declared route fired ("$connect" / "$disconnect" /
|
|
41345
|
+
* "$default" / custom).
|
|
41346
|
+
*/
|
|
41347
|
+
const MOCK_ACCOUNT_ID$1 = "123456789012";
|
|
41315
41348
|
const MOCK_DOMAIN_NAME$1 = "localhost";
|
|
41316
41349
|
const MOCK_API_ID$1 = "local";
|
|
41317
41350
|
/**
|
|
@@ -41338,6 +41371,7 @@ function buildRequestContext(routeKey, eventType, connectionId, connectedAt, sta
|
|
|
41338
41371
|
apiId: MOCK_API_ID$1,
|
|
41339
41372
|
authorizer: null,
|
|
41340
41373
|
identity: {
|
|
41374
|
+
accountId: MOCK_ACCOUNT_ID$1,
|
|
41341
41375
|
sourceIp: snapshot.sourceIp ?? "127.0.0.1",
|
|
41342
41376
|
userAgent: snapshot.userAgent ?? ""
|
|
41343
41377
|
}
|
|
@@ -41740,6 +41774,16 @@ function attachWebSocketServer(opts) {
|
|
|
41740
41774
|
apisByPath.set(cfg.apiPath, cfg);
|
|
41741
41775
|
apiPaths.push(cfg.apiPath);
|
|
41742
41776
|
}
|
|
41777
|
+
const dispatchChainsByConnection = /* @__PURE__ */ new Map();
|
|
41778
|
+
const enqueueDispatch = (connectionId, work) => {
|
|
41779
|
+
const next = (dispatchChainsByConnection.get(connectionId) ?? Promise.resolve()).then(work);
|
|
41780
|
+
dispatchChainsByConnection.set(connectionId, next);
|
|
41781
|
+
next.finally(() => {
|
|
41782
|
+
if (dispatchChainsByConnection.get(connectionId) === next) dispatchChainsByConnection.delete(connectionId);
|
|
41783
|
+
});
|
|
41784
|
+
return next;
|
|
41785
|
+
};
|
|
41786
|
+
const inFlightDisconnects = /* @__PURE__ */ new Set();
|
|
41743
41787
|
const upgradeListener = (req, socket, head) => {
|
|
41744
41788
|
const pathOnly = (req.url ?? "/").split("?", 1)[0];
|
|
41745
41789
|
const cfg = apisByPath.get(pathOnly);
|
|
@@ -41819,7 +41863,7 @@ function attachWebSocketServer(opts) {
|
|
|
41819
41863
|
ws.on("message", (raw, isBinary) => {
|
|
41820
41864
|
const { body, isBase64Encoded } = bufferToBody(raw, isBinary);
|
|
41821
41865
|
logger.debug(`WebSocket message received for connection ${connectionId}: ${body.slice(0, 200)}`);
|
|
41822
|
-
dispatchMessage(connectionId, cfg, body, isBase64Encoded, handshakeSnapshot).catch((err) => {
|
|
41866
|
+
enqueueDispatch(connectionId, () => dispatchMessage(connectionId, cfg, body, isBase64Encoded, handshakeSnapshot).catch((err) => {
|
|
41823
41867
|
logger.error(`WebSocket message dispatch failed (connection ${connectionId}): ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
|
|
41824
41868
|
try {
|
|
41825
41869
|
ws.send(JSON.stringify({
|
|
@@ -41828,11 +41872,16 @@ function attachWebSocketServer(opts) {
|
|
|
41828
41872
|
requestId: randomUUID()
|
|
41829
41873
|
}));
|
|
41830
41874
|
} catch {}
|
|
41831
|
-
});
|
|
41875
|
+
}));
|
|
41832
41876
|
});
|
|
41833
41877
|
ws.on("close", (code, reason) => {
|
|
41834
|
-
|
|
41878
|
+
const reasonText = reason.toString("utf-8");
|
|
41879
|
+
const disconnectPromise = enqueueDispatch(connectionId, () => onDisconnect(connectionId, cfg, handshakeSnapshot, code, reasonText).catch((err) => {
|
|
41835
41880
|
logger.warn(`WebSocket $disconnect dispatch failed (connection ${connectionId}): ${err instanceof Error ? err.message : String(err)}`);
|
|
41881
|
+
}));
|
|
41882
|
+
inFlightDisconnects.add(disconnectPromise);
|
|
41883
|
+
disconnectPromise.finally(() => {
|
|
41884
|
+
inFlightDisconnects.delete(disconnectPromise);
|
|
41836
41885
|
});
|
|
41837
41886
|
});
|
|
41838
41887
|
ws.on("error", (err) => {
|
|
@@ -41840,9 +41889,9 @@ function attachWebSocketServer(opts) {
|
|
|
41840
41889
|
});
|
|
41841
41890
|
for (const frame of preVerdictFrames) {
|
|
41842
41891
|
const { body, isBase64Encoded } = bufferToBody(frame.raw, frame.isBinary);
|
|
41843
|
-
dispatchMessage(connectionId, cfg, body, isBase64Encoded, handshakeSnapshot).catch((err) => {
|
|
41892
|
+
enqueueDispatch(connectionId, () => dispatchMessage(connectionId, cfg, body, isBase64Encoded, handshakeSnapshot).catch((err) => {
|
|
41844
41893
|
logger.error(`WebSocket buffered-message dispatch failed (connection ${connectionId}): ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
|
|
41845
|
-
});
|
|
41894
|
+
}));
|
|
41846
41895
|
}
|
|
41847
41896
|
preVerdictFrames.length = 0;
|
|
41848
41897
|
};
|
|
@@ -41888,6 +41937,7 @@ function attachWebSocketServer(opts) {
|
|
|
41888
41937
|
});
|
|
41889
41938
|
await invokeRoute(disconnectRoute.targetLambdaLogicalId, event, opts.pool, rieTimeoutMs);
|
|
41890
41939
|
};
|
|
41940
|
+
const SHUTDOWN_DRAIN_MS = 5e3;
|
|
41891
41941
|
let closed = false;
|
|
41892
41942
|
return {
|
|
41893
41943
|
registry,
|
|
@@ -41910,6 +41960,11 @@ function attachWebSocketServer(opts) {
|
|
|
41910
41960
|
}, 5e3).unref();
|
|
41911
41961
|
}));
|
|
41912
41962
|
await Promise.all(closes);
|
|
41963
|
+
if (inFlightDisconnects.size > 0) {
|
|
41964
|
+
const drainStartCount = inFlightDisconnects.size;
|
|
41965
|
+
const drainComplete = Promise.allSettled(Array.from(inFlightDisconnects));
|
|
41966
|
+
if (await Promise.race([drainComplete.then(() => "complete"), new Promise((resolve) => setTimeout(() => resolve("timeout"), SHUTDOWN_DRAIN_MS).unref())]) === "timeout" && inFlightDisconnects.size > 0) logger.warn(`WebSocket graceful shutdown drained for ${SHUTDOWN_DRAIN_MS}ms; ${inFlightDisconnects.size}/${drainStartCount} $disconnect handlers still in flight — leaking past shutdown.`);
|
|
41967
|
+
}
|
|
41913
41968
|
await new Promise((resolve) => {
|
|
41914
41969
|
wss.close(() => resolve());
|
|
41915
41970
|
});
|
|
@@ -42074,6 +42129,95 @@ function safeDecode$1(s) {
|
|
|
42074
42129
|
}
|
|
42075
42130
|
}
|
|
42076
42131
|
|
|
42132
|
+
//#endregion
|
|
42133
|
+
//#region src/local/docker-version.ts
|
|
42134
|
+
/**
|
|
42135
|
+
* Lower bound for `--add-host=<name>:host-gateway` support. The
|
|
42136
|
+
* `host-gateway` magic alias was introduced in Docker 20.10 (October
|
|
42137
|
+
* 2020) and is the load-bearing primitive cdkd uses to let Lambda
|
|
42138
|
+
* containers reach the host's `cdkd local start-api` server on Linux
|
|
42139
|
+
* native dockerd. Without it, the AWS_ENDPOINT_URL_APIGATEWAYMANAGEMENTAPI
|
|
42140
|
+
* override fails with `ENOTFOUND host.docker.internal` at SDK-call time.
|
|
42141
|
+
*
|
|
42142
|
+
* Docker Desktop (macOS / Windows) ships `host.docker.internal` as
|
|
42143
|
+
* a built-in alias regardless of the engine version, but the probe
|
|
42144
|
+
* still fires there to keep the error path uniform — the `host-gateway`
|
|
42145
|
+
* flag itself is harmless on Docker Desktop.
|
|
42146
|
+
*
|
|
42147
|
+
* Issue #527 M2.
|
|
42148
|
+
*/
|
|
42149
|
+
const HOST_GATEWAY_MIN_VERSION = {
|
|
42150
|
+
major: 20,
|
|
42151
|
+
minor: 10,
|
|
42152
|
+
patch: 0
|
|
42153
|
+
};
|
|
42154
|
+
/**
|
|
42155
|
+
* Parse a Docker server version string (`20.10.21` / `24.0.7-rd` /
|
|
42156
|
+
* `27.3.1+podman` etc.) into a comparable `{major, minor, patch}` tuple.
|
|
42157
|
+
* Returns `null` on any unparseable input — the caller treats that as
|
|
42158
|
+
* "version unknown, skip the comparison and let the user proceed with
|
|
42159
|
+
* a warn" rather than hard-failing on a Docker-compatible CLI binary
|
|
42160
|
+
* that doesn't follow Docker's version-string conventions
|
|
42161
|
+
* (e.g. podman / finch).
|
|
42162
|
+
*/
|
|
42163
|
+
function parseDockerVersion(raw) {
|
|
42164
|
+
const trimmed = raw.trim();
|
|
42165
|
+
const match = /^(\d+)\.(\d+)(?:\.(\d+))?/.exec(trimmed);
|
|
42166
|
+
if (!match) return null;
|
|
42167
|
+
return {
|
|
42168
|
+
major: Number(match[1]),
|
|
42169
|
+
minor: Number(match[2]),
|
|
42170
|
+
patch: match[3] !== void 0 ? Number(match[3]) : 0
|
|
42171
|
+
};
|
|
42172
|
+
}
|
|
42173
|
+
/**
|
|
42174
|
+
* Compare two `ParsedDockerVersion` tuples. Returns negative when `a <
|
|
42175
|
+
* b`, zero when equal, positive when `a > b`. Patch-level differences
|
|
42176
|
+
* are part of the ordering so a future bump (e.g. 20.10.0 -> 20.10.5
|
|
42177
|
+
* to fix a CVE-related regression) can be expressed if needed.
|
|
42178
|
+
*/
|
|
42179
|
+
function compareDockerVersions(a, b) {
|
|
42180
|
+
if (a.major !== b.major) return a.major - b.major;
|
|
42181
|
+
if (a.minor !== b.minor) return a.minor - b.minor;
|
|
42182
|
+
return a.patch - b.patch;
|
|
42183
|
+
}
|
|
42184
|
+
/**
|
|
42185
|
+
* Probe the Docker server's version to gate the `--add-host=...:host-gateway`
|
|
42186
|
+
* mapping that WebSocket Lambda containers need to reach the host
|
|
42187
|
+
* server. Issued ONCE per `cdkd local start-api` invocation at WebSocket
|
|
42188
|
+
* attach time — HTTP-only / REST-only sessions skip the probe entirely.
|
|
42189
|
+
*
|
|
42190
|
+
* Throws when:
|
|
42191
|
+
* 1. The docker subprocess itself fails (binary missing, daemon down,
|
|
42192
|
+
* permission error) — the caller's catch surfaces the original
|
|
42193
|
+
* error so the user knows to install / start Docker.
|
|
42194
|
+
* 2. The probe succeeds but the parsed version is < the supported
|
|
42195
|
+
* minimum — caller decides whether to error or warn (the WebSocket
|
|
42196
|
+
* attach loop errors; HTTP-only sessions never call this).
|
|
42197
|
+
*
|
|
42198
|
+
* Implementation: `docker version --format '{{.Server.Version}}'`
|
|
42199
|
+
* returns the daemon's version (not the client's) so a brand-new
|
|
42200
|
+
* client against an old daemon is still caught.
|
|
42201
|
+
*/
|
|
42202
|
+
async function probeHostGatewaySupport() {
|
|
42203
|
+
const rawVersion = (await runDockerStreaming([
|
|
42204
|
+
"version",
|
|
42205
|
+
"--format",
|
|
42206
|
+
"{{.Server.Version}}"
|
|
42207
|
+
], { streamLive: false })).stdout.trim();
|
|
42208
|
+
const parsed = parseDockerVersion(rawVersion);
|
|
42209
|
+
if (rawVersion === "") return {
|
|
42210
|
+
rawVersion,
|
|
42211
|
+
parsed: null,
|
|
42212
|
+
supported: false
|
|
42213
|
+
};
|
|
42214
|
+
return {
|
|
42215
|
+
rawVersion,
|
|
42216
|
+
parsed,
|
|
42217
|
+
supported: parsed === null || compareDockerVersions(parsed, HOST_GATEWAY_MIN_VERSION) >= 0
|
|
42218
|
+
};
|
|
42219
|
+
}
|
|
42220
|
+
|
|
42077
42221
|
//#endregion
|
|
42078
42222
|
//#region src/local/vtl-engine.ts
|
|
42079
42223
|
/** Error thrown when a template references an unsupported VTL feature. */
|
|
@@ -48041,6 +48185,11 @@ async function localStartApiCommand(target, options) {
|
|
|
48041
48185
|
const wsServers = [];
|
|
48042
48186
|
const initialWsApis = initialMaterial.webSocketApis ?? [];
|
|
48043
48187
|
warnUnsupportedWebSocketApis(initialWsApis, logger);
|
|
48188
|
+
if (initialWsApis.filter((api) => !api.unsupported).length > 0) {
|
|
48189
|
+
const probe = await probeHostGatewaySupport();
|
|
48190
|
+
if (!probe.supported) throw new Error(`cdkd local start-api requires Docker ${HOST_GATEWAY_MIN_VERSION.major}.${HOST_GATEWAY_MIN_VERSION.minor}+ for WebSocket API support (--add-host=host.docker.internal:host-gateway needs the 20.10 host-gateway alias). Detected server version: ${probe.rawVersion || "<empty — daemon unreachable or output stripped>"}. Upgrade Docker, or remove the WebSocket API from this app to fall back to HTTP-only start-api.`);
|
|
48191
|
+
if (probe.parsed === null) logger.warn(`Docker server version "${probe.rawVersion}" did not match the canonical "<major>.<minor>" shape; assuming host-gateway support. If WebSocket containers fail to reach the local server, verify your Docker-compatible CLI honors --add-host=host.docker.internal:host-gateway.`);
|
|
48192
|
+
}
|
|
48044
48193
|
for (const api of initialWsApis) {
|
|
48045
48194
|
if (api.unsupported) continue;
|
|
48046
48195
|
const wsLambdaIds = new Set(api.routes.map((r) => r.targetLambdaLogicalId));
|
|
@@ -54015,7 +54164,7 @@ function reorderArgs(argv) {
|
|
|
54015
54164
|
*/
|
|
54016
54165
|
async function main() {
|
|
54017
54166
|
const program = new Command();
|
|
54018
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.137.
|
|
54167
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.137.3");
|
|
54019
54168
|
program.addCommand(createBootstrapCommand());
|
|
54020
54169
|
program.addCommand(createSynthCommand());
|
|
54021
54170
|
program.addCommand(createListCommand());
|