@go-to-k/cdkd 0.137.1 → 0.137.2

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
@@ -41740,6 +41740,16 @@ function attachWebSocketServer(opts) {
41740
41740
  apisByPath.set(cfg.apiPath, cfg);
41741
41741
  apiPaths.push(cfg.apiPath);
41742
41742
  }
41743
+ const dispatchChainsByConnection = /* @__PURE__ */ new Map();
41744
+ const enqueueDispatch = (connectionId, work) => {
41745
+ const next = (dispatchChainsByConnection.get(connectionId) ?? Promise.resolve()).then(work);
41746
+ dispatchChainsByConnection.set(connectionId, next);
41747
+ next.finally(() => {
41748
+ if (dispatchChainsByConnection.get(connectionId) === next) dispatchChainsByConnection.delete(connectionId);
41749
+ });
41750
+ return next;
41751
+ };
41752
+ const inFlightDisconnects = /* @__PURE__ */ new Set();
41743
41753
  const upgradeListener = (req, socket, head) => {
41744
41754
  const pathOnly = (req.url ?? "/").split("?", 1)[0];
41745
41755
  const cfg = apisByPath.get(pathOnly);
@@ -41819,7 +41829,7 @@ function attachWebSocketServer(opts) {
41819
41829
  ws.on("message", (raw, isBinary) => {
41820
41830
  const { body, isBase64Encoded } = bufferToBody(raw, isBinary);
41821
41831
  logger.debug(`WebSocket message received for connection ${connectionId}: ${body.slice(0, 200)}`);
41822
- dispatchMessage(connectionId, cfg, body, isBase64Encoded, handshakeSnapshot).catch((err) => {
41832
+ enqueueDispatch(connectionId, () => dispatchMessage(connectionId, cfg, body, isBase64Encoded, handshakeSnapshot).catch((err) => {
41823
41833
  logger.error(`WebSocket message dispatch failed (connection ${connectionId}): ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
41824
41834
  try {
41825
41835
  ws.send(JSON.stringify({
@@ -41828,11 +41838,16 @@ function attachWebSocketServer(opts) {
41828
41838
  requestId: randomUUID()
41829
41839
  }));
41830
41840
  } catch {}
41831
- });
41841
+ }));
41832
41842
  });
41833
41843
  ws.on("close", (code, reason) => {
41834
- onDisconnect(connectionId, cfg, handshakeSnapshot, code, reason.toString("utf-8")).catch((err) => {
41844
+ const reasonText = reason.toString("utf-8");
41845
+ const disconnectPromise = enqueueDispatch(connectionId, () => onDisconnect(connectionId, cfg, handshakeSnapshot, code, reasonText).catch((err) => {
41835
41846
  logger.warn(`WebSocket $disconnect dispatch failed (connection ${connectionId}): ${err instanceof Error ? err.message : String(err)}`);
41847
+ }));
41848
+ inFlightDisconnects.add(disconnectPromise);
41849
+ disconnectPromise.finally(() => {
41850
+ inFlightDisconnects.delete(disconnectPromise);
41836
41851
  });
41837
41852
  });
41838
41853
  ws.on("error", (err) => {
@@ -41840,9 +41855,9 @@ function attachWebSocketServer(opts) {
41840
41855
  });
41841
41856
  for (const frame of preVerdictFrames) {
41842
41857
  const { body, isBase64Encoded } = bufferToBody(frame.raw, frame.isBinary);
41843
- dispatchMessage(connectionId, cfg, body, isBase64Encoded, handshakeSnapshot).catch((err) => {
41858
+ enqueueDispatch(connectionId, () => dispatchMessage(connectionId, cfg, body, isBase64Encoded, handshakeSnapshot).catch((err) => {
41844
41859
  logger.error(`WebSocket buffered-message dispatch failed (connection ${connectionId}): ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
41845
- });
41860
+ }));
41846
41861
  }
41847
41862
  preVerdictFrames.length = 0;
41848
41863
  };
@@ -41888,6 +41903,7 @@ function attachWebSocketServer(opts) {
41888
41903
  });
41889
41904
  await invokeRoute(disconnectRoute.targetLambdaLogicalId, event, opts.pool, rieTimeoutMs);
41890
41905
  };
41906
+ const SHUTDOWN_DRAIN_MS = 5e3;
41891
41907
  let closed = false;
41892
41908
  return {
41893
41909
  registry,
@@ -41910,6 +41926,11 @@ function attachWebSocketServer(opts) {
41910
41926
  }, 5e3).unref();
41911
41927
  }));
41912
41928
  await Promise.all(closes);
41929
+ if (inFlightDisconnects.size > 0) {
41930
+ const drainStartCount = inFlightDisconnects.size;
41931
+ const drainComplete = Promise.allSettled(Array.from(inFlightDisconnects));
41932
+ 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.`);
41933
+ }
41913
41934
  await new Promise((resolve) => {
41914
41935
  wss.close(() => resolve());
41915
41936
  });
@@ -42074,6 +42095,95 @@ function safeDecode$1(s) {
42074
42095
  }
42075
42096
  }
42076
42097
 
42098
+ //#endregion
42099
+ //#region src/local/docker-version.ts
42100
+ /**
42101
+ * Lower bound for `--add-host=<name>:host-gateway` support. The
42102
+ * `host-gateway` magic alias was introduced in Docker 20.10 (October
42103
+ * 2020) and is the load-bearing primitive cdkd uses to let Lambda
42104
+ * containers reach the host's `cdkd local start-api` server on Linux
42105
+ * native dockerd. Without it, the AWS_ENDPOINT_URL_APIGATEWAYMANAGEMENTAPI
42106
+ * override fails with `ENOTFOUND host.docker.internal` at SDK-call time.
42107
+ *
42108
+ * Docker Desktop (macOS / Windows) ships `host.docker.internal` as
42109
+ * a built-in alias regardless of the engine version, but the probe
42110
+ * still fires there to keep the error path uniform — the `host-gateway`
42111
+ * flag itself is harmless on Docker Desktop.
42112
+ *
42113
+ * Issue #527 M2.
42114
+ */
42115
+ const HOST_GATEWAY_MIN_VERSION = {
42116
+ major: 20,
42117
+ minor: 10,
42118
+ patch: 0
42119
+ };
42120
+ /**
42121
+ * Parse a Docker server version string (`20.10.21` / `24.0.7-rd` /
42122
+ * `27.3.1+podman` etc.) into a comparable `{major, minor, patch}` tuple.
42123
+ * Returns `null` on any unparseable input — the caller treats that as
42124
+ * "version unknown, skip the comparison and let the user proceed with
42125
+ * a warn" rather than hard-failing on a Docker-compatible CLI binary
42126
+ * that doesn't follow Docker's version-string conventions
42127
+ * (e.g. podman / finch).
42128
+ */
42129
+ function parseDockerVersion(raw) {
42130
+ const trimmed = raw.trim();
42131
+ const match = /^(\d+)\.(\d+)(?:\.(\d+))?/.exec(trimmed);
42132
+ if (!match) return null;
42133
+ return {
42134
+ major: Number(match[1]),
42135
+ minor: Number(match[2]),
42136
+ patch: match[3] !== void 0 ? Number(match[3]) : 0
42137
+ };
42138
+ }
42139
+ /**
42140
+ * Compare two `ParsedDockerVersion` tuples. Returns negative when `a <
42141
+ * b`, zero when equal, positive when `a > b`. Patch-level differences
42142
+ * are part of the ordering so a future bump (e.g. 20.10.0 -> 20.10.5
42143
+ * to fix a CVE-related regression) can be expressed if needed.
42144
+ */
42145
+ function compareDockerVersions(a, b) {
42146
+ if (a.major !== b.major) return a.major - b.major;
42147
+ if (a.minor !== b.minor) return a.minor - b.minor;
42148
+ return a.patch - b.patch;
42149
+ }
42150
+ /**
42151
+ * Probe the Docker server's version to gate the `--add-host=...:host-gateway`
42152
+ * mapping that WebSocket Lambda containers need to reach the host
42153
+ * server. Issued ONCE per `cdkd local start-api` invocation at WebSocket
42154
+ * attach time — HTTP-only / REST-only sessions skip the probe entirely.
42155
+ *
42156
+ * Throws when:
42157
+ * 1. The docker subprocess itself fails (binary missing, daemon down,
42158
+ * permission error) — the caller's catch surfaces the original
42159
+ * error so the user knows to install / start Docker.
42160
+ * 2. The probe succeeds but the parsed version is < the supported
42161
+ * minimum — caller decides whether to error or warn (the WebSocket
42162
+ * attach loop errors; HTTP-only sessions never call this).
42163
+ *
42164
+ * Implementation: `docker version --format '{{.Server.Version}}'`
42165
+ * returns the daemon's version (not the client's) so a brand-new
42166
+ * client against an old daemon is still caught.
42167
+ */
42168
+ async function probeHostGatewaySupport() {
42169
+ const rawVersion = (await runDockerStreaming([
42170
+ "version",
42171
+ "--format",
42172
+ "{{.Server.Version}}"
42173
+ ], { streamLive: false })).stdout.trim();
42174
+ const parsed = parseDockerVersion(rawVersion);
42175
+ if (rawVersion === "") return {
42176
+ rawVersion,
42177
+ parsed: null,
42178
+ supported: false
42179
+ };
42180
+ return {
42181
+ rawVersion,
42182
+ parsed,
42183
+ supported: parsed === null || compareDockerVersions(parsed, HOST_GATEWAY_MIN_VERSION) >= 0
42184
+ };
42185
+ }
42186
+
42077
42187
  //#endregion
42078
42188
  //#region src/local/vtl-engine.ts
42079
42189
  /** Error thrown when a template references an unsupported VTL feature. */
@@ -48041,6 +48151,11 @@ async function localStartApiCommand(target, options) {
48041
48151
  const wsServers = [];
48042
48152
  const initialWsApis = initialMaterial.webSocketApis ?? [];
48043
48153
  warnUnsupportedWebSocketApis(initialWsApis, logger);
48154
+ if (initialWsApis.filter((api) => !api.unsupported).length > 0) {
48155
+ const probe = await probeHostGatewaySupport();
48156
+ 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.`);
48157
+ 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.`);
48158
+ }
48044
48159
  for (const api of initialWsApis) {
48045
48160
  if (api.unsupported) continue;
48046
48161
  const wsLambdaIds = new Set(api.routes.map((r) => r.targetLambdaLogicalId));
@@ -54015,7 +54130,7 @@ function reorderArgs(argv) {
54015
54130
  */
54016
54131
  async function main() {
54017
54132
  const program = new Command();
54018
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.137.1");
54133
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.137.2");
54019
54134
  program.addCommand(createBootstrapCommand());
54020
54135
  program.addCommand(createSynthCommand());
54021
54136
  program.addCommand(createListCommand());