@emulates/docker 0.0.0-stage → 0.1.1
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/API_EVIDENCE.md +392 -0
- package/CHANGELOG.md +9 -0
- package/DISCOVERY.md +54 -0
- package/ORACLE.md +85 -0
- package/README.md +446 -2
- package/SUPPORT.md +23 -0
- package/dist/chunk-NSXRVOHP.js +4089 -0
- package/dist/chunk-NSXRVOHP.js.map +7 -0
- package/dist/chunk-ZO7PO2NA.js +629 -0
- package/dist/chunk-ZO7PO2NA.js.map +7 -0
- package/dist/cli.js +805 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +1306 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +7 -0
- package/dist/server.d.ts +1804 -0
- package/dist/server.js +12 -0
- package/dist/server.js.map +7 -0
- package/evidence/engine-29.8.0-api-1.52.json +302 -0
- package/openapi.yaml +1188 -0
- package/package.json +121 -4
- package/scripts/docker-oracle.mjs +276 -0
- package/scripts/oracle-client.mjs +229 -0
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DockerInputError,
|
|
3
|
+
annotateResponse,
|
|
4
|
+
booleanQuery,
|
|
5
|
+
createRuntime,
|
|
6
|
+
isRunning,
|
|
7
|
+
jsonRes
|
|
8
|
+
} from "./chunk-NSXRVOHP.js";
|
|
9
|
+
|
|
10
|
+
// src/attach.ts
|
|
11
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
12
|
+
import { STATUS_CODES } from "node:http";
|
|
13
|
+
|
|
14
|
+
// src/streams.ts
|
|
15
|
+
var limit = (value, name) => {
|
|
16
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 16 * 1024 * 1024)
|
|
17
|
+
throw new RangeError(`${name} must be 1..16777216`);
|
|
18
|
+
return value;
|
|
19
|
+
};
|
|
20
|
+
var createAttachStreams = (options = {}) => {
|
|
21
|
+
const chunkBytes = limit(options.frameChunkBytes ?? 64 * 1024, "frameChunkBytes");
|
|
22
|
+
const maxQueued = limit(options.maxQueuedBytes ?? 1024 * 1024, "maxQueuedBytes");
|
|
23
|
+
const maxStdin = limit(options.maxStdinBytes ?? 1024 * 1024, "maxStdinBytes");
|
|
24
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
25
|
+
let sequence = 0;
|
|
26
|
+
const connect = (selection, socket, runtime) => {
|
|
27
|
+
const { api, containerId, namespace, branch } = selection;
|
|
28
|
+
let closed = false, outputClosed = false, retired = false, inputEnded = false, queuedBytes = 0, inputSize = 0;
|
|
29
|
+
let input = [];
|
|
30
|
+
let tail = Promise.resolve();
|
|
31
|
+
let releaseReady = (_ready) => {
|
|
32
|
+
};
|
|
33
|
+
const ready = new Promise((resolve) => {
|
|
34
|
+
releaseReady = resolve;
|
|
35
|
+
});
|
|
36
|
+
const valid = () => !closed && api.generation === selection.generation;
|
|
37
|
+
const closeInput = () => {
|
|
38
|
+
inputEnded = true;
|
|
39
|
+
if (!valid() || retired || !selection.stdin || !selection.stdinOnce) return;
|
|
40
|
+
const c = api.state.containers.get(containerId);
|
|
41
|
+
if (c && !c.stdinClosed) {
|
|
42
|
+
api.state.containers.update(containerId, { ...c, stdinClosed: true });
|
|
43
|
+
runtime.checkpoint(namespace, branch);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
let detachInvalidation = () => {
|
|
47
|
+
}, detachExit = () => {
|
|
48
|
+
};
|
|
49
|
+
const release = () => {
|
|
50
|
+
if (closed) return;
|
|
51
|
+
closed = true;
|
|
52
|
+
outputClosed = true;
|
|
53
|
+
releaseReady(false);
|
|
54
|
+
sessions.delete(handle);
|
|
55
|
+
detachInvalidation();
|
|
56
|
+
detachExit();
|
|
57
|
+
socket.off("data", onData);
|
|
58
|
+
socket.off("end", onEnd);
|
|
59
|
+
};
|
|
60
|
+
const cancel = () => {
|
|
61
|
+
closeInput();
|
|
62
|
+
release();
|
|
63
|
+
input = [];
|
|
64
|
+
inputSize = 0;
|
|
65
|
+
socket.destroy();
|
|
66
|
+
};
|
|
67
|
+
const send = (bytes) => new Promise((resolve, reject) => {
|
|
68
|
+
const close = () => {
|
|
69
|
+
socket.off("close", close);
|
|
70
|
+
reject(new Error("Attachment closed"));
|
|
71
|
+
};
|
|
72
|
+
if (!valid() || socket.destroyed) {
|
|
73
|
+
close();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
socket.once("close", close);
|
|
77
|
+
socket.write(bytes, (error) => {
|
|
78
|
+
socket.off("close", close);
|
|
79
|
+
if (error) reject(error);
|
|
80
|
+
else resolve();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
const onData = (chunk) => {
|
|
84
|
+
if (!valid() || retired) return;
|
|
85
|
+
if (!selection.stdin || api.state.containers.get(containerId)?.stdinClosed) return;
|
|
86
|
+
if (inputSize + chunk.length > maxStdin) {
|
|
87
|
+
cancel();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
input.push(Buffer.from(chunk));
|
|
91
|
+
inputSize += chunk.length;
|
|
92
|
+
};
|
|
93
|
+
const onEnd = () => {
|
|
94
|
+
closeInput();
|
|
95
|
+
if (selection.stdin && !selection.stdinOnce) void handle.end().catch(cancel);
|
|
96
|
+
};
|
|
97
|
+
const finish = async () => {
|
|
98
|
+
await tail;
|
|
99
|
+
if (valid() && !socket.destroyed) socket.end();
|
|
100
|
+
};
|
|
101
|
+
const handle = {
|
|
102
|
+
id: ++sequence,
|
|
103
|
+
containerId,
|
|
104
|
+
namespace,
|
|
105
|
+
branch,
|
|
106
|
+
get closed() {
|
|
107
|
+
return closed;
|
|
108
|
+
},
|
|
109
|
+
get stdinClosed() {
|
|
110
|
+
return inputEnded || api.state.containers.get(containerId)?.stdinClosed === true;
|
|
111
|
+
},
|
|
112
|
+
get queuedBytes() {
|
|
113
|
+
return queuedBytes;
|
|
114
|
+
},
|
|
115
|
+
async write(channel, data) {
|
|
116
|
+
if (!valid() || outputClosed) throw new Error("Attachment closed");
|
|
117
|
+
if (channel !== "stdout" && channel !== "stderr")
|
|
118
|
+
throw new TypeError("Unknown output channel");
|
|
119
|
+
if (!selection[channel]) return;
|
|
120
|
+
const size = data.byteLength + 8;
|
|
121
|
+
if (size + queuedBytes > maxQueued)
|
|
122
|
+
throw new RangeError("Attachment output queue limit exceeded");
|
|
123
|
+
const frame = Buffer.allocUnsafe(size);
|
|
124
|
+
frame[0] = channel === "stdout" ? 1 : 2;
|
|
125
|
+
frame.fill(0, 1, 4);
|
|
126
|
+
frame.writeUInt32BE(data.byteLength, 4);
|
|
127
|
+
frame.set(data, 8);
|
|
128
|
+
queuedBytes += size;
|
|
129
|
+
const result = tail.then(async () => {
|
|
130
|
+
if (!await ready || !valid()) throw new Error("Attachment closed");
|
|
131
|
+
for (let at = 0; at < frame.length; at += chunkBytes)
|
|
132
|
+
await send(frame.subarray(at, at + chunkBytes));
|
|
133
|
+
});
|
|
134
|
+
tail = result.catch(() => {
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
await result;
|
|
138
|
+
} finally {
|
|
139
|
+
queuedBytes -= size;
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
takeStdin() {
|
|
143
|
+
const result = Buffer.concat(input, inputSize);
|
|
144
|
+
input = [];
|
|
145
|
+
inputSize = 0;
|
|
146
|
+
return result;
|
|
147
|
+
},
|
|
148
|
+
async end() {
|
|
149
|
+
if (!valid() || outputClosed) return;
|
|
150
|
+
outputClosed = true;
|
|
151
|
+
closeInput();
|
|
152
|
+
const c = api.state.containers.get(containerId);
|
|
153
|
+
if (selection.stdinOnce && c && isRunning(c)) return;
|
|
154
|
+
await finish();
|
|
155
|
+
},
|
|
156
|
+
cancel
|
|
157
|
+
};
|
|
158
|
+
socket.once("close", () => {
|
|
159
|
+
if (valid()) closeInput();
|
|
160
|
+
release();
|
|
161
|
+
});
|
|
162
|
+
detachInvalidation = api.onInvalidate(cancel);
|
|
163
|
+
detachExit = api.lifecycle.onExit((id) => {
|
|
164
|
+
if (id === containerId) {
|
|
165
|
+
retired = true;
|
|
166
|
+
input = [];
|
|
167
|
+
inputSize = 0;
|
|
168
|
+
inputEnded = true;
|
|
169
|
+
outputClosed = true;
|
|
170
|
+
void finish().catch(cancel);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
sessions.add(handle);
|
|
174
|
+
if (!valid()) {
|
|
175
|
+
cancel();
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
handle,
|
|
179
|
+
start(head) {
|
|
180
|
+
if (!valid()) return;
|
|
181
|
+
socket.on("data", onData);
|
|
182
|
+
socket.once("end", onEnd);
|
|
183
|
+
if (head.length) onData(head);
|
|
184
|
+
releaseReady(true);
|
|
185
|
+
socket.resume();
|
|
186
|
+
if (!selection.stdin && !selection.stdout && !selection.stderr)
|
|
187
|
+
void handle.end().catch(cancel);
|
|
188
|
+
},
|
|
189
|
+
cancel
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
return { connect, list: () => [...sessions] };
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/attach.ts
|
|
196
|
+
var header = Buffer.from(
|
|
197
|
+
"HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.multiplexed-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n"
|
|
198
|
+
);
|
|
199
|
+
var createAttachHandshake = (options = {}, streamOptions = {}) => {
|
|
200
|
+
const streams = createAttachStreams(streamOptions);
|
|
201
|
+
const chunkBytes = options.chunkBytes ?? header.length;
|
|
202
|
+
if (!Number.isSafeInteger(chunkBytes) || chunkBytes < 1 || chunkBytes > 4096)
|
|
203
|
+
throw new RangeError("attach chunkBytes must be 1..4096");
|
|
204
|
+
if ((options.initialStreamBytes?.byteLength ?? 0) > 1024 * 1024)
|
|
205
|
+
throw new RangeError("initialStreamBytes exceeds 1 MiB");
|
|
206
|
+
const first = Buffer.from(options.initialStreamBytes ?? []);
|
|
207
|
+
const admission = new AsyncLocalStorage();
|
|
208
|
+
const prepare = (api, request) => {
|
|
209
|
+
const context = admission.getStore();
|
|
210
|
+
if (!context) return jsonRes(501, { message: "Emulates: attach requires Node HTTP upgrade" });
|
|
211
|
+
const url = new URL(request.url);
|
|
212
|
+
if (!booleanQuery(url, "stream") || booleanQuery(url, "logs") || url.searchParams.has("detachKeys") || [...url.searchParams.keys()].some(
|
|
213
|
+
(key) => !["stream", "logs", "stdin", "stdout", "stderr"].includes(key)
|
|
214
|
+
))
|
|
215
|
+
return jsonRes(501, { message: "Emulates: attach mode is not implemented" });
|
|
216
|
+
const match = /^\/containers\/([^/]+)\/attach$/.exec(url.pathname);
|
|
217
|
+
if (!match?.[1]) return jsonRes(404, { message: "page not found" });
|
|
218
|
+
let name;
|
|
219
|
+
try {
|
|
220
|
+
name = decodeURIComponent(match[1]);
|
|
221
|
+
} catch {
|
|
222
|
+
return jsonRes(400, { message: "invalid container name encoding" });
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const c = api.state.find(name);
|
|
226
|
+
if (c.status === "paused")
|
|
227
|
+
throw new DockerInputError(
|
|
228
|
+
409,
|
|
229
|
+
`container ${name} is paused, unpause the container before attach`
|
|
230
|
+
);
|
|
231
|
+
if (c.status === "restarting")
|
|
232
|
+
throw new DockerInputError(
|
|
233
|
+
409,
|
|
234
|
+
`container ${name} is restarting, wait until the container is running`
|
|
235
|
+
);
|
|
236
|
+
if (c.config?.Tty === true)
|
|
237
|
+
return jsonRes(501, { message: "Emulates: TTY attach is not implemented" });
|
|
238
|
+
context.selection = {
|
|
239
|
+
api,
|
|
240
|
+
generation: api.generation,
|
|
241
|
+
containerId: c.id,
|
|
242
|
+
namespace: request.headers.get("x-emulates-namespace") ?? "default",
|
|
243
|
+
branch: request.headers.get("x-emulates-branch") ?? "main",
|
|
244
|
+
stdin: booleanQuery(url, "stdin") && c.config?.OpenStdin === true,
|
|
245
|
+
stdinOnce: c.config?.StdinOnce === true,
|
|
246
|
+
stdout: booleanQuery(url, "stdout"),
|
|
247
|
+
stderr: booleanQuery(url, "stderr")
|
|
248
|
+
};
|
|
249
|
+
return annotateResponse(new Response(null), { wireStatus: 101, ids: { containerId: c.id } });
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error instanceof DockerInputError)
|
|
252
|
+
return new Response(`${error.message}\r
|
|
253
|
+
`, {
|
|
254
|
+
status: error.status,
|
|
255
|
+
headers: { "content-type": "application/vnd.docker.raw-stream" }
|
|
256
|
+
});
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
const write = (socket, bytes) => new Promise((resolve) => {
|
|
261
|
+
const finish = (ok) => {
|
|
262
|
+
socket.off("close", closed);
|
|
263
|
+
resolve(ok);
|
|
264
|
+
};
|
|
265
|
+
const closed = () => finish(false);
|
|
266
|
+
socket.once("close", closed);
|
|
267
|
+
if (socket.destroyed) {
|
|
268
|
+
finish(false);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
socket.write(bytes, (error) => finish(!error));
|
|
272
|
+
});
|
|
273
|
+
const upgrade = async (runtime, req, socket, head) => {
|
|
274
|
+
socket.on("error", () => socket.destroy());
|
|
275
|
+
socket.pause();
|
|
276
|
+
try {
|
|
277
|
+
if (req.method !== "POST" || req.headers.upgrade?.toLowerCase() !== "tcp" || !req.headers.connection?.toLowerCase().split(/\s*,\s*/).includes("upgrade") || Number(req.headers["content-length"] ?? 0) !== 0 || req.headers["transfer-encoding"] !== void 0) {
|
|
278
|
+
await write(
|
|
279
|
+
socket,
|
|
280
|
+
Buffer.from(
|
|
281
|
+
"HTTP/1.1 501 Not Implemented\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
socket.end();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const target = new URL((req.url ?? "/").replace(/^\/+/, "/"), "http://docker.mock");
|
|
288
|
+
if (!/^(?:\/__admin\/ns\/[^/]+)?(?:\/v[0-9]+\.[0-9]+)?\/containers\/[^/]+\/attach$/.test(
|
|
289
|
+
target.pathname
|
|
290
|
+
)) {
|
|
291
|
+
await write(
|
|
292
|
+
socket,
|
|
293
|
+
Buffer.from("HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n")
|
|
294
|
+
);
|
|
295
|
+
socket.end();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const headers = new Headers();
|
|
299
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
300
|
+
const name = req.rawHeaders[i], value = req.rawHeaders[i + 1];
|
|
301
|
+
if (name && value !== void 0) headers.append(name, value);
|
|
302
|
+
}
|
|
303
|
+
const request = new Request(
|
|
304
|
+
new URL((req.url ?? "/").replace(/^\/+/, "/"), "http://docker.mock"),
|
|
305
|
+
{ method: "POST", headers }
|
|
306
|
+
);
|
|
307
|
+
const context = {};
|
|
308
|
+
const response = await admission.run(context, () => runtime.fetch(request));
|
|
309
|
+
if (socket.destroyed) return;
|
|
310
|
+
if (!context.selection || response.status !== 200) {
|
|
311
|
+
const body = Buffer.from(await response.arrayBuffer());
|
|
312
|
+
const type = response.headers.get("content-type") ?? "application/json";
|
|
313
|
+
await write(
|
|
314
|
+
socket,
|
|
315
|
+
Buffer.concat([
|
|
316
|
+
Buffer.from(
|
|
317
|
+
`HTTP/1.1 ${response.status} ${STATUS_CODES[response.status] ?? "Error"}\r
|
|
318
|
+
Content-Type: ${type}\r
|
|
319
|
+
Connection: close\r
|
|
320
|
+
Content-Length: ${body.length}\r
|
|
321
|
+
\r
|
|
322
|
+
`
|
|
323
|
+
),
|
|
324
|
+
body
|
|
325
|
+
])
|
|
326
|
+
);
|
|
327
|
+
socket.end();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const session = streams.connect(context.selection, socket, runtime);
|
|
331
|
+
for (let offset = 0; offset < header.length; offset += chunkBytes) {
|
|
332
|
+
const end = Math.min(offset + chunkBytes, header.length);
|
|
333
|
+
const chunk = header.subarray(offset, end);
|
|
334
|
+
if (!await write(socket, end === header.length ? Buffer.concat([chunk, first]) : chunk))
|
|
335
|
+
return;
|
|
336
|
+
if (end < header.length) await new Promise((resolve) => setImmediate(resolve));
|
|
337
|
+
}
|
|
338
|
+
session.start(head);
|
|
339
|
+
} catch {
|
|
340
|
+
socket.destroy();
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
return { prepare, upgrade, attachments: streams.list };
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// src/transport.ts
|
|
347
|
+
import { lstat } from "node:fs/promises";
|
|
348
|
+
import { createServer } from "node:http";
|
|
349
|
+
import { isAbsolute } from "node:path";
|
|
350
|
+
var positive = (value, name) => {
|
|
351
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
352
|
+
throw new RangeError(`${name} must be a positive integer`);
|
|
353
|
+
return value;
|
|
354
|
+
};
|
|
355
|
+
var listenDocker = async (runtime, options, upgrade) => {
|
|
356
|
+
const maxBody = positive(options.maxBodyBytes ?? 1024 * 1024, "maxBodyBytes");
|
|
357
|
+
const bodyTimeout = positive(options.bodyTimeoutMs ?? 3e4, "bodyTimeoutMs");
|
|
358
|
+
const maxConnections = positive(options.maxConnections ?? 128, "maxConnections");
|
|
359
|
+
const path = options.socketPath;
|
|
360
|
+
if (path !== void 0) {
|
|
361
|
+
if (!isAbsolute(path) || path.includes("\0") || Buffer.byteLength(path) > 103)
|
|
362
|
+
throw new Error("socketPath must be an absolute Unix path of at most 103 bytes");
|
|
363
|
+
if (options.port !== void 0 || options.host !== void 0)
|
|
364
|
+
throw new Error("socketPath cannot be combined with port or host");
|
|
365
|
+
const existing = await lstat(path).then(
|
|
366
|
+
() => true,
|
|
367
|
+
(error) => {
|
|
368
|
+
if (error.code === "ENOENT") return false;
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
if (existing) throw new Error("Refusing existing socketPath");
|
|
373
|
+
}
|
|
374
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
375
|
+
const handle = async (req, res) => {
|
|
376
|
+
const abort = new AbortController();
|
|
377
|
+
const onClose = () => {
|
|
378
|
+
if (!res.writableFinished) abort.abort();
|
|
379
|
+
};
|
|
380
|
+
res.once("close", onClose);
|
|
381
|
+
const reject = (status) => {
|
|
382
|
+
res.writeHead(status, { "content-type": "application/json", connection: "close" });
|
|
383
|
+
res.end(JSON.stringify({ message: `Emulates transport rejected request (${status})` }));
|
|
384
|
+
};
|
|
385
|
+
try {
|
|
386
|
+
if (Number(req.headers["content-length"]) > maxBody) {
|
|
387
|
+
reject(413);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const body = await new Promise((resolve, rejectBody) => {
|
|
391
|
+
const chunks = [];
|
|
392
|
+
let size = 0;
|
|
393
|
+
const cleanup = () => {
|
|
394
|
+
clearTimeout(timer);
|
|
395
|
+
req.off("data", data);
|
|
396
|
+
req.off("end", end);
|
|
397
|
+
req.off("error", error);
|
|
398
|
+
abort.signal.removeEventListener("abort", canceled);
|
|
399
|
+
};
|
|
400
|
+
const canceled = () => {
|
|
401
|
+
cleanup();
|
|
402
|
+
rejectBody(new Error("Request disconnected"));
|
|
403
|
+
};
|
|
404
|
+
const error = (e) => {
|
|
405
|
+
cleanup();
|
|
406
|
+
rejectBody(e);
|
|
407
|
+
};
|
|
408
|
+
const end = () => {
|
|
409
|
+
cleanup();
|
|
410
|
+
resolve(Buffer.concat(chunks, size));
|
|
411
|
+
};
|
|
412
|
+
const data = (chunk) => {
|
|
413
|
+
size += chunk.length;
|
|
414
|
+
if (size > maxBody) {
|
|
415
|
+
cleanup();
|
|
416
|
+
req.pause();
|
|
417
|
+
reject(413);
|
|
418
|
+
resolve(void 0);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
chunks.push(chunk);
|
|
422
|
+
};
|
|
423
|
+
const timer = setTimeout(() => {
|
|
424
|
+
cleanup();
|
|
425
|
+
req.pause();
|
|
426
|
+
reject(408);
|
|
427
|
+
resolve(void 0);
|
|
428
|
+
}, bodyTimeout);
|
|
429
|
+
req.on("data", data);
|
|
430
|
+
req.once("end", end);
|
|
431
|
+
req.once("error", error);
|
|
432
|
+
abort.signal.addEventListener("abort", canceled, { once: true });
|
|
433
|
+
if (abort.signal.aborted) canceled();
|
|
434
|
+
});
|
|
435
|
+
if (body === void 0 || abort.signal.aborted) return;
|
|
436
|
+
const headers = new Headers();
|
|
437
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
438
|
+
const name = req.rawHeaders[i], value = req.rawHeaders[i + 1];
|
|
439
|
+
if (name !== void 0 && value !== void 0) headers.append(name, value);
|
|
440
|
+
}
|
|
441
|
+
const method = req.method ?? "GET";
|
|
442
|
+
const request = new Request(
|
|
443
|
+
new URL((req.url ?? "/").replace(/^\/+/, "/"), "http://docker.mock"),
|
|
444
|
+
{
|
|
445
|
+
method,
|
|
446
|
+
headers,
|
|
447
|
+
signal: abort.signal,
|
|
448
|
+
...method !== "GET" && method !== "HEAD" && body.length ? { body: new Uint8Array(body) } : {}
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
const response = await runtime.fetch(request);
|
|
452
|
+
if (abort.signal.aborted) {
|
|
453
|
+
await response.body?.cancel();
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const outgoing = Object.fromEntries(response.headers);
|
|
457
|
+
const cookies = response.headers.getSetCookie();
|
|
458
|
+
if (cookies.length) outgoing["set-cookie"] = cookies;
|
|
459
|
+
res.writeHead(response.status, outgoing);
|
|
460
|
+
if (!response.body) {
|
|
461
|
+
res.end();
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
res.flushHeaders();
|
|
465
|
+
const reader = response.body.getReader();
|
|
466
|
+
const cancel = () => {
|
|
467
|
+
void reader.cancel().catch(() => {
|
|
468
|
+
});
|
|
469
|
+
};
|
|
470
|
+
abort.signal.addEventListener("abort", cancel, { once: true });
|
|
471
|
+
try {
|
|
472
|
+
while (!abort.signal.aborted) {
|
|
473
|
+
const { done, value } = await reader.read();
|
|
474
|
+
if (done) break;
|
|
475
|
+
if (!res.write(value))
|
|
476
|
+
await new Promise((resolve) => {
|
|
477
|
+
const finish = () => {
|
|
478
|
+
res.off("drain", finish);
|
|
479
|
+
res.off("close", finish);
|
|
480
|
+
resolve();
|
|
481
|
+
};
|
|
482
|
+
res.once("drain", finish);
|
|
483
|
+
res.once("close", finish);
|
|
484
|
+
if (res.destroyed) finish();
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
if (!res.destroyed) res.end();
|
|
488
|
+
} finally {
|
|
489
|
+
abort.signal.removeEventListener("abort", cancel);
|
|
490
|
+
reader.releaseLock();
|
|
491
|
+
}
|
|
492
|
+
} catch (error) {
|
|
493
|
+
if (error?.code === "EMULATES_DROP" || abort.signal.aborted || res.headersSent)
|
|
494
|
+
res.destroy();
|
|
495
|
+
else reject(500);
|
|
496
|
+
} finally {
|
|
497
|
+
res.off("close", onClose);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
const busy = /* @__PURE__ */ new WeakSet();
|
|
501
|
+
const server = createServer(
|
|
502
|
+
{
|
|
503
|
+
maxHeaderSize: 16 * 1024,
|
|
504
|
+
headersTimeout: 3e4,
|
|
505
|
+
requestTimeout: 3e4,
|
|
506
|
+
keepAliveTimeout: 5e3
|
|
507
|
+
},
|
|
508
|
+
(req, res) => {
|
|
509
|
+
if (req.socket.destroyed || busy.has(req.socket)) {
|
|
510
|
+
req.socket.destroy();
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
busy.add(req.socket);
|
|
514
|
+
const release = () => {
|
|
515
|
+
busy.delete(req.socket);
|
|
516
|
+
res.off("finish", release);
|
|
517
|
+
res.off("close", release);
|
|
518
|
+
};
|
|
519
|
+
res.once("finish", release);
|
|
520
|
+
res.once("close", release);
|
|
521
|
+
void handle(req, res);
|
|
522
|
+
}
|
|
523
|
+
);
|
|
524
|
+
if (upgrade)
|
|
525
|
+
server.on("upgrade", (req, socket, head) => {
|
|
526
|
+
if (socket.destroyed || busy.has(socket) || !sockets.has(socket)) {
|
|
527
|
+
socket.destroy();
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
busy.add(socket);
|
|
531
|
+
void upgrade(req, socket, head);
|
|
532
|
+
});
|
|
533
|
+
server.on("connection", (socket) => {
|
|
534
|
+
if (sockets.size >= maxConnections) {
|
|
535
|
+
socket.destroy();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
sockets.add(socket);
|
|
539
|
+
socket.once("close", () => sockets.delete(socket));
|
|
540
|
+
});
|
|
541
|
+
server.once("close", () => runtime.close());
|
|
542
|
+
const host = options.host ?? "127.0.0.1";
|
|
543
|
+
await new Promise((resolve, reject) => {
|
|
544
|
+
const error = (error2) => {
|
|
545
|
+
server.off("listening", ready);
|
|
546
|
+
reject(error2);
|
|
547
|
+
};
|
|
548
|
+
const ready = () => {
|
|
549
|
+
server.off("error", error);
|
|
550
|
+
resolve();
|
|
551
|
+
};
|
|
552
|
+
server.once("error", error);
|
|
553
|
+
server.once("listening", ready);
|
|
554
|
+
if (path !== void 0) server.listen(path);
|
|
555
|
+
else server.listen(options.port ?? 0, host);
|
|
556
|
+
});
|
|
557
|
+
const address = server.address();
|
|
558
|
+
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
559
|
+
let closing;
|
|
560
|
+
return {
|
|
561
|
+
server,
|
|
562
|
+
host,
|
|
563
|
+
port,
|
|
564
|
+
...path !== void 0 ? { socketPath: path } : {},
|
|
565
|
+
url: path === void 0 ? `http://${host.includes(":") ? `[${host}]` : host}:${port}` : "http://docker.mock",
|
|
566
|
+
close: () => closing ??= new Promise((resolve, reject) => {
|
|
567
|
+
runtime.close();
|
|
568
|
+
server.close((error) => {
|
|
569
|
+
if (error && error.code !== "ERR_SERVER_NOT_RUNNING")
|
|
570
|
+
reject(error);
|
|
571
|
+
else resolve();
|
|
572
|
+
});
|
|
573
|
+
for (const socket of sockets) socket.destroy();
|
|
574
|
+
})
|
|
575
|
+
};
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// src/server.ts
|
|
579
|
+
var DEFAULT_PORT = 8826;
|
|
580
|
+
var createServer2 = async (options = {}) => {
|
|
581
|
+
const {
|
|
582
|
+
port,
|
|
583
|
+
host,
|
|
584
|
+
socketPath,
|
|
585
|
+
maxBodyBytes,
|
|
586
|
+
bodyTimeoutMs,
|
|
587
|
+
maxConnections,
|
|
588
|
+
attachHandshake,
|
|
589
|
+
attachStreams,
|
|
590
|
+
...rest
|
|
591
|
+
} = options;
|
|
592
|
+
const attach = createAttachHandshake(attachHandshake, attachStreams);
|
|
593
|
+
const runtime = createRuntime({ ...rest, onAttach: attach.prepare });
|
|
594
|
+
try {
|
|
595
|
+
const listening = await listenDocker(
|
|
596
|
+
runtime,
|
|
597
|
+
{
|
|
598
|
+
...port !== void 0 ? { port } : {},
|
|
599
|
+
...host !== void 0 ? { host } : {},
|
|
600
|
+
...socketPath !== void 0 ? { socketPath } : {},
|
|
601
|
+
...maxBodyBytes !== void 0 ? { maxBodyBytes } : {},
|
|
602
|
+
...bodyTimeoutMs !== void 0 ? { bodyTimeoutMs } : {},
|
|
603
|
+
...maxConnections !== void 0 ? { maxConnections } : {}
|
|
604
|
+
},
|
|
605
|
+
(req, socket, head) => attach.upgrade(runtime, req, socket, head)
|
|
606
|
+
);
|
|
607
|
+
return { ...listening, runtime, attachments: attach.attachments };
|
|
608
|
+
} catch (error) {
|
|
609
|
+
runtime.close();
|
|
610
|
+
throw error;
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
var serveTarget = {
|
|
614
|
+
name: "docker",
|
|
615
|
+
defaultPort: DEFAULT_PORT,
|
|
616
|
+
create: (_values, common) => createRuntime({
|
|
617
|
+
...common.adminKey !== void 0 ? { adminKey: common.adminKey } : {},
|
|
618
|
+
...common.seed !== void 0 ? { seed: common.seed } : {},
|
|
619
|
+
...common.onLog ? { onLog: common.onLog } : {}
|
|
620
|
+
}),
|
|
621
|
+
banner: () => ["Docker lifecycle: explicit simulated completion; no image execution or attach"]
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
export {
|
|
625
|
+
DEFAULT_PORT,
|
|
626
|
+
createServer2 as createServer,
|
|
627
|
+
serveTarget
|
|
628
|
+
};
|
|
629
|
+
//# sourceMappingURL=chunk-ZO7PO2NA.js.map
|