rightsize 0.1.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.
Files changed (216) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +4 -0
  3. package/README.md +255 -0
  4. package/dist/backend-docker/backend.d.ts +53 -0
  5. package/dist/backend-docker/backend.d.ts.map +1 -0
  6. package/dist/backend-docker/backend.js +336 -0
  7. package/dist/backend-docker/backend.js.map +1 -0
  8. package/dist/backend-docker/client.d.ts +82 -0
  9. package/dist/backend-docker/client.d.ts.map +1 -0
  10. package/dist/backend-docker/client.js +190 -0
  11. package/dist/backend-docker/client.js.map +1 -0
  12. package/dist/backend-docker/frames.d.ts +44 -0
  13. package/dist/backend-docker/frames.d.ts.map +1 -0
  14. package/dist/backend-docker/frames.js +105 -0
  15. package/dist/backend-docker/frames.js.map +1 -0
  16. package/dist/backend-docker/index.d.ts +5 -0
  17. package/dist/backend-docker/index.d.ts.map +1 -0
  18. package/dist/backend-docker/index.js +20 -0
  19. package/dist/backend-docker/index.js.map +1 -0
  20. package/dist/backend-docker/json.d.ts +13 -0
  21. package/dist/backend-docker/json.d.ts.map +1 -0
  22. package/dist/backend-docker/json.js +47 -0
  23. package/dist/backend-docker/json.js.map +1 -0
  24. package/dist/backend-docker/labels.d.ts +11 -0
  25. package/dist/backend-docker/labels.d.ts.map +1 -0
  26. package/dist/backend-docker/labels.js +13 -0
  27. package/dist/backend-docker/labels.js.map +1 -0
  28. package/dist/backend-docker/port-conflict.d.ts +9 -0
  29. package/dist/backend-docker/port-conflict.d.ts.map +1 -0
  30. package/dist/backend-docker/port-conflict.js +12 -0
  31. package/dist/backend-docker/port-conflict.js.map +1 -0
  32. package/dist/backend-docker/provider.d.ts +22 -0
  33. package/dist/backend-docker/provider.d.ts.map +1 -0
  34. package/dist/backend-docker/provider.js +36 -0
  35. package/dist/backend-docker/provider.js.map +1 -0
  36. package/dist/backend-msb/backend.d.ts +169 -0
  37. package/dist/backend-msb/backend.d.ts.map +1 -0
  38. package/dist/backend-msb/backend.js +664 -0
  39. package/dist/backend-msb/backend.js.map +1 -0
  40. package/dist/backend-msb/commands.d.ts +28 -0
  41. package/dist/backend-msb/commands.d.ts.map +1 -0
  42. package/dist/backend-msb/commands.js +67 -0
  43. package/dist/backend-msb/commands.js.map +1 -0
  44. package/dist/backend-msb/exec-tunnel.d.ts +33 -0
  45. package/dist/backend-msb/exec-tunnel.d.ts.map +1 -0
  46. package/dist/backend-msb/exec-tunnel.js +163 -0
  47. package/dist/backend-msb/exec-tunnel.js.map +1 -0
  48. package/dist/backend-msb/follow-replay.d.ts +14 -0
  49. package/dist/backend-msb/follow-replay.d.ts.map +1 -0
  50. package/dist/backend-msb/follow-replay.js +20 -0
  51. package/dist/backend-msb/follow-replay.js.map +1 -0
  52. package/dist/backend-msb/image-cache.d.ts +25 -0
  53. package/dist/backend-msb/image-cache.d.ts.map +1 -0
  54. package/dist/backend-msb/image-cache.js +27 -0
  55. package/dist/backend-msb/image-cache.js.map +1 -0
  56. package/dist/backend-msb/index.d.ts +6 -0
  57. package/dist/backend-msb/index.d.ts.map +1 -0
  58. package/dist/backend-msb/index.js +21 -0
  59. package/dist/backend-msb/index.js.map +1 -0
  60. package/dist/backend-msb/invoke.d.ts +24 -0
  61. package/dist/backend-msb/invoke.d.ts.map +1 -0
  62. package/dist/backend-msb/invoke.js +73 -0
  63. package/dist/backend-msb/invoke.js.map +1 -0
  64. package/dist/backend-msb/ls-json.d.ts +23 -0
  65. package/dist/backend-msb/ls-json.d.ts.map +1 -0
  66. package/dist/backend-msb/ls-json.js +113 -0
  67. package/dist/backend-msb/ls-json.js.map +1 -0
  68. package/dist/backend-msb/network-links.d.ts +7 -0
  69. package/dist/backend-msb/network-links.d.ts.map +1 -0
  70. package/dist/backend-msb/network-links.js +31 -0
  71. package/dist/backend-msb/network-links.js.map +1 -0
  72. package/dist/backend-msb/platform.d.ts +29 -0
  73. package/dist/backend-msb/platform.d.ts.map +1 -0
  74. package/dist/backend-msb/platform.js +128 -0
  75. package/dist/backend-msb/platform.js.map +1 -0
  76. package/dist/backend-msb/port-conflict.d.ts +10 -0
  77. package/dist/backend-msb/port-conflict.d.ts.map +1 -0
  78. package/dist/backend-msb/port-conflict.js +16 -0
  79. package/dist/backend-msb/port-conflict.js.map +1 -0
  80. package/dist/backend-msb/provider.d.ts +10 -0
  81. package/dist/backend-msb/provider.d.ts.map +1 -0
  82. package/dist/backend-msb/provider.js +40 -0
  83. package/dist/backend-msb/provider.js.map +1 -0
  84. package/dist/backend-msb/provisioner.d.ts +19 -0
  85. package/dist/backend-msb/provisioner.d.ts.map +1 -0
  86. package/dist/backend-msb/provisioner.js +332 -0
  87. package/dist/backend-msb/provisioner.js.map +1 -0
  88. package/dist/backend-msb/reaper.d.ts +10 -0
  89. package/dist/backend-msb/reaper.d.ts.map +1 -0
  90. package/dist/backend-msb/reaper.js +73 -0
  91. package/dist/backend-msb/reaper.js.map +1 -0
  92. package/dist/core/backend.d.ts +98 -0
  93. package/dist/core/backend.d.ts.map +1 -0
  94. package/dist/core/backend.js +2 -0
  95. package/dist/core/backend.js.map +1 -0
  96. package/dist/core/backends.d.ts +37 -0
  97. package/dist/core/backends.d.ts.map +1 -0
  98. package/dist/core/backends.js +127 -0
  99. package/dist/core/backends.js.map +1 -0
  100. package/dist/core/cleanup.d.ts +37 -0
  101. package/dist/core/cleanup.d.ts.map +1 -0
  102. package/dist/core/cleanup.js +86 -0
  103. package/dist/core/cleanup.js.map +1 -0
  104. package/dist/core/errors.d.ts +51 -0
  105. package/dist/core/errors.d.ts.map +1 -0
  106. package/dist/core/errors.js +66 -0
  107. package/dist/core/errors.js.map +1 -0
  108. package/dist/core/free-ports.d.ts +23 -0
  109. package/dist/core/free-ports.d.ts.map +1 -0
  110. package/dist/core/free-ports.js +63 -0
  111. package/dist/core/free-ports.js.map +1 -0
  112. package/dist/core/generic-container.d.ts +145 -0
  113. package/dist/core/generic-container.d.ts.map +1 -0
  114. package/dist/core/generic-container.js +385 -0
  115. package/dist/core/generic-container.js.map +1 -0
  116. package/dist/core/model.d.ts +60 -0
  117. package/dist/core/model.d.ts.map +1 -0
  118. package/dist/core/model.js +2 -0
  119. package/dist/core/model.js.map +1 -0
  120. package/dist/core/mountable-file.d.ts +25 -0
  121. package/dist/core/mountable-file.d.ts.map +1 -0
  122. package/dist/core/mountable-file.js +64 -0
  123. package/dist/core/mountable-file.js.map +1 -0
  124. package/dist/core/network.d.ts +58 -0
  125. package/dist/core/network.d.ts.map +1 -0
  126. package/dist/core/network.js +84 -0
  127. package/dist/core/network.js.map +1 -0
  128. package/dist/core/run-id.d.ts +18 -0
  129. package/dist/core/run-id.d.ts.map +1 -0
  130. package/dist/core/run-id.js +18 -0
  131. package/dist/core/run-id.js.map +1 -0
  132. package/dist/core/wait.d.ts +59 -0
  133. package/dist/core/wait.d.ts.map +1 -0
  134. package/dist/core/wait.js +239 -0
  135. package/dist/core/wait.js.map +1 -0
  136. package/dist/index.d.ts +26 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +22 -0
  139. package/dist/index.js.map +1 -0
  140. package/dist/modules/arango.d.ts +14 -0
  141. package/dist/modules/arango.d.ts.map +1 -0
  142. package/dist/modules/arango.js +37 -0
  143. package/dist/modules/arango.js.map +1 -0
  144. package/dist/modules/clickhouse.d.ts +37 -0
  145. package/dist/modules/clickhouse.d.ts.map +1 -0
  146. package/dist/modules/clickhouse.js +69 -0
  147. package/dist/modules/clickhouse.js.map +1 -0
  148. package/dist/modules/flink.d.ts +65 -0
  149. package/dist/modules/flink.d.ts.map +1 -0
  150. package/dist/modules/flink.js +125 -0
  151. package/dist/modules/flink.js.map +1 -0
  152. package/dist/modules/floci.d.ts +46 -0
  153. package/dist/modules/floci.d.ts.map +1 -0
  154. package/dist/modules/floci.js +62 -0
  155. package/dist/modules/floci.js.map +1 -0
  156. package/dist/modules/index.d.ts +31 -0
  157. package/dist/modules/index.d.ts.map +1 -0
  158. package/dist/modules/index.js +31 -0
  159. package/dist/modules/index.js.map +1 -0
  160. package/dist/modules/kafka.d.ts +11 -0
  161. package/dist/modules/kafka.d.ts.map +1 -0
  162. package/dist/modules/kafka.js +41 -0
  163. package/dist/modules/kafka.js.map +1 -0
  164. package/dist/modules/keycloak.d.ts +40 -0
  165. package/dist/modules/keycloak.d.ts.map +1 -0
  166. package/dist/modules/keycloak.js +67 -0
  167. package/dist/modules/keycloak.js.map +1 -0
  168. package/dist/modules/mariadb.d.ts +56 -0
  169. package/dist/modules/mariadb.d.ts.map +1 -0
  170. package/dist/modules/mariadb.js +89 -0
  171. package/dist/modules/mariadb.js.map +1 -0
  172. package/dist/modules/memcached.d.ts +24 -0
  173. package/dist/modules/memcached.d.ts.map +1 -0
  174. package/dist/modules/memcached.js +105 -0
  175. package/dist/modules/memcached.js.map +1 -0
  176. package/dist/modules/mongodb.d.ts +19 -0
  177. package/dist/modules/mongodb.d.ts.map +1 -0
  178. package/dist/modules/mongodb.js +69 -0
  179. package/dist/modules/mongodb.js.map +1 -0
  180. package/dist/modules/mysql.d.ts +61 -0
  181. package/dist/modules/mysql.d.ts.map +1 -0
  182. package/dist/modules/mysql.js +101 -0
  183. package/dist/modules/mysql.js.map +1 -0
  184. package/dist/modules/neo4j.d.ts +60 -0
  185. package/dist/modules/neo4j.d.ts.map +1 -0
  186. package/dist/modules/neo4j.js +83 -0
  187. package/dist/modules/neo4j.js.map +1 -0
  188. package/dist/modules/pinot.d.ts +30 -0
  189. package/dist/modules/pinot.d.ts.map +1 -0
  190. package/dist/modules/pinot.js +49 -0
  191. package/dist/modules/pinot.js.map +1 -0
  192. package/dist/modules/postgres.d.ts +29 -0
  193. package/dist/modules/postgres.d.ts.map +1 -0
  194. package/dist/modules/postgres.js +73 -0
  195. package/dist/modules/postgres.js.map +1 -0
  196. package/dist/modules/rabbitmq.d.ts +46 -0
  197. package/dist/modules/rabbitmq.d.ts.map +1 -0
  198. package/dist/modules/rabbitmq.js +68 -0
  199. package/dist/modules/rabbitmq.js.map +1 -0
  200. package/dist/modules/redis.d.ts +15 -0
  201. package/dist/modules/redis.d.ts.map +1 -0
  202. package/dist/modules/redis.js +24 -0
  203. package/dist/modules/redis.js.map +1 -0
  204. package/dist/modules/redpanda.d.ts +27 -0
  205. package/dist/modules/redpanda.d.ts.map +1 -0
  206. package/dist/modules/redpanda.js +58 -0
  207. package/dist/modules/redpanda.js.map +1 -0
  208. package/dist/modules/spring-cloud-config.d.ts +18 -0
  209. package/dist/modules/spring-cloud-config.d.ts.map +1 -0
  210. package/dist/modules/spring-cloud-config.js +28 -0
  211. package/dist/modules/spring-cloud-config.js.map +1 -0
  212. package/dist/modules/wiremock.d.ts +21 -0
  213. package/dist/modules/wiremock.d.ts.map +1 -0
  214. package/dist/modules/wiremock.js +32 -0
  215. package/dist/modules/wiremock.js.map +1 -0
  216. package/package.json +104 -0
@@ -0,0 +1,336 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { BackendError, PortBindConflictError } from "../core/errors.js";
3
+ import { RunId } from "../core/run-id.js";
4
+ import { DockerClient } from "./client.js";
5
+ import { FrameDemuxer, LineAssembler } from "./frames.js";
6
+ import { extractIds, extractNumber, extractString } from "./json.js";
7
+ import { isPortBindConflictMessage } from "./port-conflict.js";
8
+ import { labelFilterQuery, RUN_ID_LABEL_KEY } from "./labels.js";
9
+ const STOP_TIMEOUT_SECS = 10;
10
+ function encodeQueryValue(s) {
11
+ return encodeURIComponent(s);
12
+ }
13
+ /** `image` split into `[repository, tag]` for `POST /images/create?fromImage=&tag=`; a tag-less reference defaults to `latest`, matching Docker's own convention. */
14
+ function splitRepoTag(image) {
15
+ if (image.includes("@")) {
16
+ return [image, ""];
17
+ }
18
+ // The tag separator is the LAST colon after the last slash, so a registry
19
+ // host:port prefix (`localhost:5000/redis`) isn't mistaken for one.
20
+ const slashIdx = image.lastIndexOf("/") + 1;
21
+ const relColon = image.slice(slashIdx).lastIndexOf(":");
22
+ if (relColon === -1) {
23
+ return [image, "latest"];
24
+ }
25
+ const colon = slashIdx + relColon;
26
+ return [image.slice(0, colon), image.slice(colon + 1)];
27
+ }
28
+ /** Builds the `POST /containers/create` JSON body: port bindings pinned to `127.0.0.1`, read-only/read-write binds, the `host.docker.internal` extra host, and the run-id label. */
29
+ function buildCreateBody(spec) {
30
+ const exposedPorts = {};
31
+ const portBindings = {};
32
+ for (const p of spec.ports) {
33
+ const key = `${p.guestPort}/tcp`;
34
+ exposedPorts[key] = {};
35
+ portBindings[key] = [{ HostIp: "127.0.0.1", HostPort: String(p.hostPort) }];
36
+ }
37
+ const binds = spec.mounts.map((m) => `${m.hostPath}:${m.guestPath}:${m.readOnly ? "ro" : "rw"}`);
38
+ const body = {
39
+ Image: spec.image,
40
+ Env: spec.env.map(([k, v]) => `${k}=${v}`),
41
+ ExposedPorts: exposedPorts,
42
+ Labels: { [RUN_ID_LABEL_KEY]: spec.runId },
43
+ HostConfig: {
44
+ PortBindings: portBindings,
45
+ Binds: binds,
46
+ ExtraHosts: ["host.docker.internal:host-gateway"],
47
+ },
48
+ };
49
+ if (spec.command !== undefined) {
50
+ body.Cmd = [...spec.command];
51
+ }
52
+ if (spec.memoryLimitMb !== undefined) {
53
+ body.HostConfig.Memory = spec.memoryLimitMb * 1024 * 1024;
54
+ }
55
+ return body;
56
+ }
57
+ async function drainFrames(body, onStdout, onStderr) {
58
+ const demuxer = new FrameDemuxer();
59
+ for await (const chunk of body) {
60
+ const frames = demuxer.push(chunk);
61
+ for (const frame of frames) {
62
+ if (frame.streamType === "stdout") {
63
+ onStdout(frame.payload);
64
+ }
65
+ else if (frame.streamType === "stderr") {
66
+ onStderr(frame.payload);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ /**
72
+ * Drives the Docker daemon over `DockerClient`. This is also the
73
+ * correctness oracle other backends are checked against, since Docker
74
+ * enforces semantics (read-only mounts, native networks) microsandbox only
75
+ * emulates.
76
+ *
77
+ * Unlike the msb backend, a `SandboxHandle` here carries no companion
78
+ * mutable state keyed by id: every operation is a stateless HTTP call
79
+ * against the daemon-assigned container id already on the handle, so
80
+ * there's nothing to look up in a side table.
81
+ */
82
+ export class DockerBackend {
83
+ client;
84
+ /** `"docker"` — matched against `RIGHTSIZE_BACKEND`. */
85
+ name = "docker";
86
+ supportsNativeNetworks = true;
87
+ networkIds = new Map();
88
+ constructor(client) {
89
+ this.client = client;
90
+ }
91
+ /** Exposed for the transport regression test: the socket path this backend's client actually dials. */
92
+ socketPathForTest() {
93
+ return this.client.getSocketPath();
94
+ }
95
+ async pullIfMissing(image) {
96
+ const inspectPath = `/images/${encodeQueryValue(image)}/json`;
97
+ const inspect = await this.client.request("GET", inspectPath);
98
+ if (inspect.status === 200) {
99
+ return;
100
+ }
101
+ const [repo, tag] = splitRepoTag(image);
102
+ const pullPath = `/images/create?fromImage=${encodeQueryValue(repo)}&tag=${encodeQueryValue(tag)}`;
103
+ const resp = await this.client.request("POST", pullPath);
104
+ if (resp.status >= 400) {
105
+ throw new BackendError(`docker could not pull image '${image}' (HTTP ${resp.status}): ${resp.body.toString()}`);
106
+ }
107
+ }
108
+ async connectNetwork(containerId, networkId, aliases) {
109
+ const body = JSON.stringify({ Container: containerId, EndpointConfig: { Aliases: [...aliases] } });
110
+ const path = `/networks/${networkId}/connect`;
111
+ const resp = await this.client.request("POST", path, body);
112
+ if (resp.status >= 400) {
113
+ throw new BackendError(`docker could not connect container ${containerId} to network ${networkId} (HTTP ${resp.status}): ${resp.body.toString()}`);
114
+ }
115
+ }
116
+ async ensureNetworkGetId(networkId) {
117
+ const cached = this.networkIds.get(networkId);
118
+ if (cached !== undefined) {
119
+ return cached;
120
+ }
121
+ const filters = JSON.stringify({ name: [networkId] });
122
+ const listPath = `/networks?filters=${encodeQueryValue(filters)}`;
123
+ const list = await this.client.request("GET", listPath);
124
+ if (list.status === 200) {
125
+ const ids = extractIds(list.body.toString());
126
+ if (ids.length > 0 && ids[0] !== undefined) {
127
+ this.networkIds.set(networkId, ids[0]);
128
+ return ids[0];
129
+ }
130
+ }
131
+ const createBody = JSON.stringify({ Name: networkId });
132
+ const created = await this.client.request("POST", "/networks/create", createBody);
133
+ if (created.status >= 400) {
134
+ throw new BackendError(`docker could not create network '${networkId}' (HTTP ${created.status}): ${created.body.toString()}`);
135
+ }
136
+ const id = extractString(created.body.toString(), "Id");
137
+ if (id === undefined) {
138
+ throw new BackendError(`docker's network-create response for '${networkId}' had no Id field (body: ${created.body.toString()})`);
139
+ }
140
+ this.networkIds.set(networkId, id);
141
+ return id;
142
+ }
143
+ async create(spec) {
144
+ await this.pullIfMissing(spec.image);
145
+ const body = JSON.stringify(buildCreateBody(spec));
146
+ const path = `/containers/create?name=${encodeQueryValue(spec.name)}`;
147
+ const resp = await this.client.request("POST", path, body);
148
+ if (resp.status >= 400) {
149
+ throw new BackendError(`docker could not create container '${spec.name}' (HTTP ${resp.status}): ${resp.body.toString()}`);
150
+ }
151
+ const id = extractString(resp.body.toString(), "Id");
152
+ if (id === undefined) {
153
+ throw new BackendError(`docker's container-create response for '${spec.name}' had no Id field (body: ${resp.body.toString()})`);
154
+ }
155
+ if (spec.networkId !== undefined) {
156
+ const daemonNetworkId = await this.ensureNetworkGetId(spec.networkId);
157
+ await this.connectNetwork(id, daemonNetworkId, spec.aliases);
158
+ }
159
+ return { id, spec };
160
+ }
161
+ async start(handle) {
162
+ const path = `/containers/${handle.id}/start`;
163
+ const resp = await this.client.request("POST", path);
164
+ if (resp.status === 204 || resp.status === 304) {
165
+ return; // 304 = already started; treated as success like the daemon intends.
166
+ }
167
+ const message = resp.body.toString();
168
+ if (resp.status === 500 && isPortBindConflictMessage(message)) {
169
+ throw new PortBindConflictError(`docker could not bind a host port for ${handle.id}: ${message}`);
170
+ }
171
+ throw new BackendError(`docker could not start container ${handle.id} (HTTP ${resp.status}): ${message}`);
172
+ }
173
+ async stop(handle) {
174
+ const path = `/containers/${handle.id}/stop?t=${STOP_TIMEOUT_SECS}`;
175
+ await this.client.request("POST", path).catch(() => { }); // best-effort
176
+ }
177
+ async remove(handle) {
178
+ const path = `/containers/${handle.id}?force=true`;
179
+ await this.client.request("DELETE", path).catch(() => { }); // best-effort
180
+ }
181
+ async exec(handle, cmd) {
182
+ const createBody = JSON.stringify({ AttachStdout: true, AttachStderr: true, Cmd: [...cmd] });
183
+ const createPath = `/containers/${handle.id}/exec`;
184
+ const created = await this.client.request("POST", createPath, createBody);
185
+ if (created.status >= 400) {
186
+ throw new BackendError(`docker could not create an exec for container ${handle.id} (HTTP ${created.status}): ${created.body.toString()}`);
187
+ }
188
+ const execId = extractString(created.body.toString(), "Id");
189
+ if (execId === undefined) {
190
+ throw new BackendError(`docker's exec-create response for container ${handle.id} had no Id field (body: ${created.body.toString()})`);
191
+ }
192
+ const startPath = `/exec/${execId}/start`;
193
+ const startBody = JSON.stringify({ Detach: false });
194
+ const { status, body } = await this.client.requestStream("POST", startPath, startBody);
195
+ if (status >= 400) {
196
+ throw new BackendError(`docker could not start exec ${execId} for container ${handle.id} (HTTP ${status})`);
197
+ }
198
+ const stdoutChunks = [];
199
+ const stderrChunks = [];
200
+ await drainFrames(body, (p) => stdoutChunks.push(p), (p) => stderrChunks.push(p));
201
+ const inspectPath = `/exec/${execId}/json`;
202
+ const inspected = await this.client.request("GET", inspectPath);
203
+ const exitCode = extractNumber(inspected.body.toString(), "ExitCode") ?? -1;
204
+ return {
205
+ exitCode,
206
+ stdout: Buffer.concat(stdoutChunks).toString(),
207
+ stderr: Buffer.concat(stderrChunks).toString(),
208
+ };
209
+ }
210
+ async logs(handle) {
211
+ const path = `/containers/${handle.id}/logs?stdout=1&stderr=1&tail=1000`;
212
+ const { status, body } = await this.client.requestStream("GET", path);
213
+ if (status >= 400) {
214
+ throw new BackendError(`docker could not fetch logs for container ${handle.id} (HTTP ${status})`);
215
+ }
216
+ const assembler = new LineAssembler();
217
+ let out = "";
218
+ await drainFrames(body, (p) => {
219
+ for (const line of assembler.feed(p.toString())) {
220
+ out += line + "\n";
221
+ }
222
+ }, (p) => {
223
+ for (const line of assembler.feed(p.toString())) {
224
+ out += line + "\n";
225
+ }
226
+ });
227
+ const tail = assembler.flush();
228
+ if (tail !== undefined) {
229
+ out += tail + "\n";
230
+ }
231
+ return out;
232
+ }
233
+ /** Docker's stream ends cleanly on its own once the workload stops — unlike msb's `logs -f`, so no watchdog is needed here. */
234
+ async followLogs(handle, consumer) {
235
+ const path = `/containers/${handle.id}/logs?stdout=1&stderr=1&follow=1&tail=all`;
236
+ const { status, body } = await this.client.requestStream("GET", path);
237
+ if (status >= 400) {
238
+ throw new BackendError(`docker could not follow logs for container ${handle.id} (HTTP ${status})`);
239
+ }
240
+ let closeRequested = false;
241
+ const demuxer = new FrameDemuxer();
242
+ const assembler = new LineAssembler();
243
+ const readerDone = (async () => {
244
+ try {
245
+ for await (const chunk of body) {
246
+ if (closeRequested) {
247
+ return;
248
+ }
249
+ for (const frame of demuxer.push(chunk)) {
250
+ if (frame.streamType !== "stdout" && frame.streamType !== "stderr") {
251
+ continue;
252
+ }
253
+ for (const line of assembler.feed(frame.payload.toString())) {
254
+ if (closeRequested) {
255
+ return;
256
+ }
257
+ consumer(line);
258
+ }
259
+ }
260
+ }
261
+ }
262
+ catch {
263
+ // Best-effort: a stream error just ends delivery.
264
+ return;
265
+ }
266
+ if (closeRequested) {
267
+ return; // stop delivery, never flush — an explicit close beat the stream to its own end.
268
+ }
269
+ const tail = assembler.flush();
270
+ if (tail !== undefined) {
271
+ consumer(tail);
272
+ }
273
+ })();
274
+ return {
275
+ close: async () => {
276
+ closeRequested = true;
277
+ body.destroy();
278
+ await readerDone.catch(() => { });
279
+ },
280
+ };
281
+ }
282
+ /** No-op: docker relies entirely on native networks (`ensureNetwork`/`create`'s connect step) — there is nothing to emulate here, unlike msb's exec-tunnel links. */
283
+ async installNetworkLinks(_handle, _links) { }
284
+ async ensureNetwork(networkId) {
285
+ await this.ensureNetworkGetId(networkId);
286
+ }
287
+ async removeNetwork(networkId) {
288
+ const daemonId = this.networkIds.get(networkId);
289
+ this.networkIds.delete(networkId);
290
+ if (daemonId !== undefined) {
291
+ await this.client.request("DELETE", `/networks/${daemonId}`).catch(() => { }); // best-effort
292
+ }
293
+ }
294
+ /** Removes every container carrying this run's label — the reaper safety net's normal-exit counterpart. */
295
+ async close() {
296
+ const filters = labelFilterQuery(RunId.value);
297
+ const path = `/containers/json?all=true&filters=${encodeQueryValue(filters)}`;
298
+ const listed = await this.client.request("GET", path).catch(() => undefined);
299
+ if (listed === undefined || listed.status !== 200) {
300
+ return; // best-effort: nothing more to do if even listing fails.
301
+ }
302
+ const ids = extractIds(listed.body.toString());
303
+ for (const id of ids) {
304
+ await this.client.request("DELETE", `/containers/${id}?force=true`).catch(() => { });
305
+ }
306
+ }
307
+ /**
308
+ * Synchronous, blocking teardown for the process-exit path. Node has no
309
+ * synchronous HTTP client, so this shells out to `curl --unix-socket` via
310
+ * `child_process.spawnSync` — curl ships on macOS and virtually every
311
+ * Linux CI image, and `spawnSync` genuinely blocks the exiting process the
312
+ * way the `"exit"` handler requires. If curl is unavailable, this is a
313
+ * silent no-op: the label-scoped orphan reaper each backend runs at
314
+ * startup (`close()`'s own reaping plus the next run's sweep) is the real
315
+ * safety net for that case, not this best-effort fast path.
316
+ */
317
+ cleanupSync(id) {
318
+ const socketPath = this.client.getSocketPath();
319
+ try {
320
+ spawnSync("curl", [
321
+ "--silent",
322
+ "--max-time",
323
+ "5",
324
+ "--unix-socket",
325
+ socketPath,
326
+ "-X",
327
+ "DELETE",
328
+ `http://localhost/containers/${id}?force=true`,
329
+ ]);
330
+ }
331
+ catch {
332
+ // Best-effort — see the doc above.
333
+ }
334
+ }
335
+ }
336
+ //# sourceMappingURL=backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/backend-docker/backend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ /**
3
+ * Bounds how many header bytes this client will accumulate before giving up.
4
+ * A well-behaved daemon's status line + header block is at most a few
5
+ * hundred bytes; `node:http` already enforces its own such limit internally,
6
+ * but this constant documents the ceiling this backend would apply if it
7
+ * were parsing headers by hand — kept here as the single source of truth
8
+ * other backend code can cite.
9
+ */
10
+ export declare const MAX_HEADER_BYTES: number;
11
+ /** A fully-buffered daemon response, returned by `DockerClient.request`. */
12
+ export interface DockerResponse {
13
+ /** The HTTP status code. */
14
+ readonly status: number;
15
+ /** The full response body. */
16
+ readonly body: Buffer;
17
+ }
18
+ /** A daemon response whose body is left as a live stream, returned by `DockerClient.requestStream` for `exec`/`logs` calls. */
19
+ export interface DockerStreamResponse {
20
+ /** The HTTP status code. */
21
+ readonly status: number;
22
+ /** Whether the daemon sent `Transfer-Encoding: chunked` (node:http already de-chunks it transparently either way). */
23
+ readonly chunked: boolean;
24
+ /** The `Content-Length` header value, when present and not chunked. */
25
+ readonly contentLength: number | undefined;
26
+ /** The raw, already-de-chunked response stream — `node:http` handles `Transfer-Encoding` transparently, so the frame demuxer only ever sees raw framed bytes. */
27
+ readonly body: IncomingMessage;
28
+ }
29
+ /**
30
+ * The pure seam `DockerClient.fromEnv` delegates to: given a `DOCKER_HOST`
31
+ * value (or `undefined`), returns the unix socket path this client should
32
+ * dial. A `tcp://`/`http://` `DOCKER_HOST` falls back to the default socket
33
+ * path rather than attempting a TCP connection — this client has no TCP
34
+ * transport at all (see the module docs on why: sharing an HTTP stack a
35
+ * consumer can bump is exactly the failure mode being avoided).
36
+ */
37
+ export declare function socketPathFromDockerHost(host: string | undefined): string;
38
+ /**
39
+ * A from-scratch HTTP client over the Docker daemon's unix socket, built on
40
+ * `node:http`'s `socketPath` option rather than a general-purpose Docker SDK
41
+ * (`dockerode`) — the point of hand-rolling is that this client can only
42
+ * ever dial a unix socket path, never a TCP host. A shared HTTP stack a
43
+ * consuming project also depends on has, on another runtime, been observed
44
+ * to misroute a Docker client onto TCP `localhost:2375` instead of the
45
+ * daemon's real unix socket; owning this client end-to-end makes that
46
+ * misrouting structurally impossible; see the standing regression test in
47
+ * `backend.test.ts`.
48
+ *
49
+ * Every daemon endpoint this backend calls returns either a small buffered
50
+ * JSON body (unary calls — `request`) or a stream of raw framed bytes
51
+ * (`exec`/`logs` — `requestStream`). Both go through `POST
52
+ * /exec/{id}/start`-style ordinary responses, never `/attach`'s connection
53
+ * hijack, so `node:http`'s `IncomingMessage` is sufficient for either case —
54
+ * no need to drop to a raw socket.
55
+ */
56
+ export declare class DockerClient {
57
+ private readonly socketPath;
58
+ private readonly connectTimeoutMs;
59
+ constructor(socketPath?: string, connectTimeoutMs?: number);
60
+ /** Builds a client honoring `DOCKER_HOST` (a `unix://` path, a bare path, or the daemon's default socket). */
61
+ static fromEnv(): DockerClient;
62
+ /** The unix socket path this client dials — never a TCP host. */
63
+ getSocketPath(): string;
64
+ /**
65
+ * One request whose entire response body is read into memory before
66
+ * returning — every daemon call this backend makes except
67
+ * `exec`/`logs` streaming (`requestStream`). Bounded by
68
+ * `RESPONSE_TIMEOUT_MS` so a wedged daemon fails this call with a named
69
+ * error instead of hanging the caller forever.
70
+ */
71
+ request(method: string, path: string, body?: string): Promise<DockerResponse>;
72
+ /**
73
+ * Issues a request and returns the status plus the still-open response
74
+ * stream, for callers (`exec`/`logs`, the frame demuxer) that consume a
75
+ * de-chunked body incrementally rather than buffering it all first.
76
+ * Deliberately NOT wrapped in `RESPONSE_TIMEOUT_MS` — `followLogs` streams
77
+ * for as long as the workload runs, by design.
78
+ */
79
+ requestStream(method: string, path: string, body?: string): Promise<DockerStreamResponse>;
80
+ private send;
81
+ }
82
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/backend-docker/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AA4BjD;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,QAAW,CAAC;AAEzC,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,4BAA4B;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,+HAA+H;AAC/H,MAAM,WAAW,oBAAoB;IACnC,4BAA4B;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sHAAsH;IACtH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,iKAAiK;IACjK,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAWzE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAK3B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBALhB,UAAU,GAAE,MAA6D,EAKzE,gBAAgB,GAAE,MAA2B;IAGhE,8GAA8G;IAC9G,MAAM,CAAC,OAAO,IAAI,YAAY;IAI9B,iEAAiE;IACjE,aAAa,IAAI,MAAM;IAIvB;;;;;;OAMG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAuBnF;;;;;;OAMG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAU/F,OAAO,CAAC,IAAI;CAkEb"}
@@ -0,0 +1,190 @@
1
+ import * as http from "node:http";
2
+ import { BackendError } from "../core/errors.js";
3
+ const DEFAULT_SOCKET_PATH = "/var/run/docker.sock";
4
+ /**
5
+ * Ceiling on one unary request/response cycle — connect, write, read
6
+ * headers, read the whole body. Deliberately NOT applied to streaming reads
7
+ * (`followLogs`): that call is meant to block for as long as the workload
8
+ * runs, so bounding it here would kill a perfectly healthy long-lived
9
+ * stream. Unary calls (create/start/stop/exec-inspect/logs/ls) get it
10
+ * because a wedged daemon must fail the caller promptly instead of hanging
11
+ * the whole test run.
12
+ */
13
+ const RESPONSE_TIMEOUT_MS = 600_000;
14
+ /**
15
+ * Ceiling on connecting to the daemon's unix socket and receiving response
16
+ * headers — the phase before `RESPONSE_TIMEOUT_MS`'s body-reading window
17
+ * even starts. Without this, a request whose connection or header phase
18
+ * never completes (an unresponsive daemon, a wedged socket) hangs `send()`
19
+ * forever with nothing else in this client ever getting the chance to time
20
+ * it out — `request()`'s own timer only starts once `send()` has already
21
+ * resolved with a response. Observed in practice: an intermittent hang here
22
+ * under Bun's `node:http` implementation, not merely a theoretical gap.
23
+ */
24
+ const CONNECT_TIMEOUT_MS = 30_000;
25
+ /**
26
+ * Bounds how many header bytes this client will accumulate before giving up.
27
+ * A well-behaved daemon's status line + header block is at most a few
28
+ * hundred bytes; `node:http` already enforces its own such limit internally,
29
+ * but this constant documents the ceiling this backend would apply if it
30
+ * were parsing headers by hand — kept here as the single source of truth
31
+ * other backend code can cite.
32
+ */
33
+ export const MAX_HEADER_BYTES = 8 * 1024;
34
+ /**
35
+ * The pure seam `DockerClient.fromEnv` delegates to: given a `DOCKER_HOST`
36
+ * value (or `undefined`), returns the unix socket path this client should
37
+ * dial. A `tcp://`/`http://` `DOCKER_HOST` falls back to the default socket
38
+ * path rather than attempting a TCP connection — this client has no TCP
39
+ * transport at all (see the module docs on why: sharing an HTTP stack a
40
+ * consumer can bump is exactly the failure mode being avoided).
41
+ */
42
+ export function socketPathFromDockerHost(host) {
43
+ if (host === undefined) {
44
+ return DEFAULT_SOCKET_PATH;
45
+ }
46
+ if (host.startsWith("unix://")) {
47
+ return host.slice("unix://".length);
48
+ }
49
+ if (host.startsWith("/")) {
50
+ return host;
51
+ }
52
+ return DEFAULT_SOCKET_PATH;
53
+ }
54
+ /**
55
+ * A from-scratch HTTP client over the Docker daemon's unix socket, built on
56
+ * `node:http`'s `socketPath` option rather than a general-purpose Docker SDK
57
+ * (`dockerode`) — the point of hand-rolling is that this client can only
58
+ * ever dial a unix socket path, never a TCP host. A shared HTTP stack a
59
+ * consuming project also depends on has, on another runtime, been observed
60
+ * to misroute a Docker client onto TCP `localhost:2375` instead of the
61
+ * daemon's real unix socket; owning this client end-to-end makes that
62
+ * misrouting structurally impossible; see the standing regression test in
63
+ * `backend.test.ts`.
64
+ *
65
+ * Every daemon endpoint this backend calls returns either a small buffered
66
+ * JSON body (unary calls — `request`) or a stream of raw framed bytes
67
+ * (`exec`/`logs` — `requestStream`). Both go through `POST
68
+ * /exec/{id}/start`-style ordinary responses, never `/attach`'s connection
69
+ * hijack, so `node:http`'s `IncomingMessage` is sufficient for either case —
70
+ * no need to drop to a raw socket.
71
+ */
72
+ export class DockerClient {
73
+ socketPath;
74
+ connectTimeoutMs;
75
+ constructor(socketPath = socketPathFromDockerHost(process.env["DOCKER_HOST"]),
76
+ // Test seam: production call sites never pass this, so every real client
77
+ // gets the real ceiling. A fixture that wants to prove the timeout path
78
+ // itself fires (without actually waiting 30s) constructs with a much
79
+ // smaller override instead.
80
+ connectTimeoutMs = CONNECT_TIMEOUT_MS) {
81
+ this.socketPath = socketPath;
82
+ this.connectTimeoutMs = connectTimeoutMs;
83
+ }
84
+ /** Builds a client honoring `DOCKER_HOST` (a `unix://` path, a bare path, or the daemon's default socket). */
85
+ static fromEnv() {
86
+ return new DockerClient(socketPathFromDockerHost(process.env["DOCKER_HOST"]));
87
+ }
88
+ /** The unix socket path this client dials — never a TCP host. */
89
+ getSocketPath() {
90
+ return this.socketPath;
91
+ }
92
+ /**
93
+ * One request whose entire response body is read into memory before
94
+ * returning — every daemon call this backend makes except
95
+ * `exec`/`logs` streaming (`requestStream`). Bounded by
96
+ * `RESPONSE_TIMEOUT_MS` so a wedged daemon fails this call with a named
97
+ * error instead of hanging the caller forever.
98
+ */
99
+ async request(method, path, body) {
100
+ const { res } = await this.send(method, path, body);
101
+ const chunks = [];
102
+ await new Promise((resolveBody, rejectBody) => {
103
+ const timer = setTimeout(() => {
104
+ res.destroy();
105
+ rejectBody(new BackendError(`${method} ${path} to the Docker daemon did not complete within ${RESPONSE_TIMEOUT_MS / 1000}s`));
106
+ }, RESPONSE_TIMEOUT_MS);
107
+ res.on("data", (chunk) => chunks.push(chunk));
108
+ res.on("end", () => {
109
+ clearTimeout(timer);
110
+ resolveBody();
111
+ });
112
+ res.on("error", (err) => {
113
+ clearTimeout(timer);
114
+ rejectBody(new BackendError(`reading a response from the Docker daemon: ${err.message}`));
115
+ });
116
+ });
117
+ return { status: res.statusCode ?? 0, body: Buffer.concat(chunks) };
118
+ }
119
+ /**
120
+ * Issues a request and returns the status plus the still-open response
121
+ * stream, for callers (`exec`/`logs`, the frame demuxer) that consume a
122
+ * de-chunked body incrementally rather than buffering it all first.
123
+ * Deliberately NOT wrapped in `RESPONSE_TIMEOUT_MS` — `followLogs` streams
124
+ * for as long as the workload runs, by design.
125
+ */
126
+ async requestStream(method, path, body) {
127
+ const { res } = await this.send(method, path, body);
128
+ const transferEncoding = res.headers["transfer-encoding"];
129
+ const chunked = typeof transferEncoding === "string" && transferEncoding.toLowerCase().includes("chunked");
130
+ const contentLengthHeader = res.headers["content-length"];
131
+ const contentLength = typeof contentLengthHeader === "string" && !chunked ? Number.parseInt(contentLengthHeader, 10) : undefined;
132
+ return { status: res.statusCode ?? 0, chunked, contentLength, body: res };
133
+ }
134
+ send(method, path, body) {
135
+ return new Promise((resolveSend, rejectSend) => {
136
+ const headers = {};
137
+ if (body !== undefined) {
138
+ headers["Content-Length"] = String(Buffer.byteLength(body));
139
+ headers["Content-Type"] = "application/json";
140
+ }
141
+ let settled = false;
142
+ const req = http.request({
143
+ socketPath: this.socketPath,
144
+ method,
145
+ path,
146
+ headers,
147
+ // No connection pooling: `http.globalAgent`'s default keep-alive
148
+ // behavior holds the underlying unix-socket connection open after
149
+ // the response completes, hoping to reuse it for a later request
150
+ // to the same socket path. Nothing in this client ever reuses a
151
+ // connection (every call opens its own), so a pooled socket just
152
+ // sits idle — which keeps a bare `node --test` process (or any
153
+ // short-lived script) alive well past its last container
154
+ // operation, since Node won't exit while a socket handle is open.
155
+ // `agent: false` makes every request its own unshared connection
156
+ // that node:http tears down as soon as the response ends.
157
+ agent: false,
158
+ }, (res) => {
159
+ if (settled) {
160
+ return;
161
+ }
162
+ settled = true;
163
+ clearTimeout(connectTimer);
164
+ resolveSend({ res });
165
+ });
166
+ const connectTimer = setTimeout(() => {
167
+ if (settled) {
168
+ return;
169
+ }
170
+ settled = true;
171
+ req.destroy();
172
+ rejectSend(new BackendError(`${method} ${path} to the Docker daemon at ${this.socketPath} did not connect/respond within ` +
173
+ `${this.connectTimeoutMs / 1000}s — is the daemon running and responsive?`));
174
+ }, this.connectTimeoutMs);
175
+ req.on("error", (err) => {
176
+ if (settled) {
177
+ return;
178
+ }
179
+ settled = true;
180
+ clearTimeout(connectTimer);
181
+ rejectSend(new BackendError(`could not connect to the Docker daemon at ${this.socketPath} — is Docker/Podman/Colima running? (${err.message})`));
182
+ });
183
+ if (body !== undefined) {
184
+ req.write(body);
185
+ }
186
+ req.end();
187
+ });
188
+ }
189
+ }
190
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/backend-docker/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,mBAAmB,GAAG,sBAAsB,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAEpC;;;;;;;;;GASG;AACH,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAAG,IAAI,CAAC;AAsBzC;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAwB;IAC/D,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,mBAAmB,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,YAAY;IAEJ;IAKA;IANnB,YACmB,aAAqB,wBAAwB,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC1F,yEAAyE;IACzE,wEAAwE;IACxE,qEAAqE;IACrE,4BAA4B;IACX,mBAA2B,kBAAkB;QAL7C,eAAU,GAAV,UAAU,CAA+D;QAKzE,qBAAgB,GAAhB,gBAAgB,CAA6B;IAC7D,CAAC;IAEJ,8GAA8G;IAC9G,MAAM,CAAC,OAAO;QACZ,OAAO,IAAI,YAAY,CAAC,wBAAwB,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,iEAAiE;IACjE,aAAa;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,MAAc,EAAE,IAAY,EAAE,IAAa;QACvD,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACpD,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,IAAI,OAAO,CAAO,CAAC,WAAW,EAAE,UAAU,EAAE,EAAE;YAClD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,UAAU,CACR,IAAI,YAAY,CAAC,GAAG,MAAM,IAAI,IAAI,iDAAiD,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAClH,CAAC;YACJ,CAAC,EAAE,mBAAmB,CAAC,CAAC;YACxB,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YACtD,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,WAAW,EAAE,CAAC;YAChB,CAAC,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACtB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,UAAU,CAAC,IAAI,YAAY,CAAC,8CAA8C,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC5F,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;IACtE,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,aAAa,CAAC,MAAc,EAAE,IAAY,EAAE,IAAa;QAC7D,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACpD,MAAM,gBAAgB,GAAG,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;QAC1D,MAAM,OAAO,GAAG,OAAO,gBAAgB,KAAK,QAAQ,IAAI,gBAAgB,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC3G,MAAM,mBAAmB,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC1D,MAAM,aAAa,GACjB,OAAO,mBAAmB,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7G,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,IAAI,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IAC5E,CAAC;IAEO,IAAI,CAAC,MAAc,EAAE,IAAY,EAAE,IAAa;QACtD,OAAO,IAAI,OAAO,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,EAAE;YAC7C,MAAM,OAAO,GAA2B,EAAE,CAAC;YAC3C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,OAAO,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC5D,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;YAC/C,CAAC;YACD,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CACtB;gBACE,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,MAAM;gBACN,IAAI;gBACJ,OAAO;gBACP,iEAAiE;gBACjE,kEAAkE;gBAClE,iEAAiE;gBACjE,gEAAgE;gBAChE,iEAAiE;gBACjE,+DAA+D;gBAC/D,yDAAyD;gBACzD,kEAAkE;gBAClE,iEAAiE;gBACjE,0DAA0D;gBAC1D,KAAK,EAAE,KAAK;aACb,EACD,CAAC,GAAG,EAAE,EAAE;gBACN,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO;gBACT,CAAC;gBACD,OAAO,GAAG,IAAI,CAAC;gBACf,YAAY,CAAC,YAAY,CAAC,CAAC;gBAC3B,WAAW,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;YACvB,CAAC,CACF,CAAC;YACF,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,EAAE;gBACnC,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO;gBACT,CAAC;gBACD,OAAO,GAAG,IAAI,CAAC;gBACf,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,UAAU,CACR,IAAI,YAAY,CACd,GAAG,MAAM,IAAI,IAAI,4BAA4B,IAAI,CAAC,UAAU,kCAAkC;oBAC5F,GAAG,IAAI,CAAC,gBAAgB,GAAG,IAAI,2CAA2C,CAC7E,CACF,CAAC;YACJ,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAC1B,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACtB,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO;gBACT,CAAC;gBACD,OAAO,GAAG,IAAI,CAAC;gBACf,YAAY,CAAC,YAAY,CAAC,CAAC;gBAC3B,UAAU,CACR,IAAI,YAAY,CACd,6CAA6C,IAAI,CAAC,UAAU,wCAAwC,GAAG,CAAC,OAAO,GAAG,CACnH,CACF,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAClB,CAAC;YACD,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}