@crvouga/mockingbird-service-mailosaur 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.
@@ -0,0 +1,567 @@
1
+ import {
2
+ createRuntime
3
+ } from "./chunk-C46JZZ56.js";
4
+
5
+ // src/server.ts
6
+ import { writeFileSync } from "node:fs";
7
+ import { readFile as readFile2 } from "node:fs/promises";
8
+ import {
9
+ connect,
10
+ createServer as createNetServer
11
+ } from "node:net";
12
+ import { createServer as createTlsServer } from "node:tls";
13
+
14
+ // ../../adapters/node/dist/cli.js
15
+ import { readFile } from "node:fs/promises";
16
+ import { parseArgs } from "node:util";
17
+
18
+ // ../../adapters/node/dist/serve.js
19
+ import { createServer } from "node:http";
20
+ var serve = async (api, options = {}) => {
21
+ const server = createServer(async (req, res) => {
22
+ const chunks = [];
23
+ for await (const chunk of req) {
24
+ chunks.push(Buffer.from(chunk));
25
+ }
26
+ const body = Buffer.concat(chunks);
27
+ const address = server.address();
28
+ const port = typeof address === "object" && address !== null ? address.port : void 0;
29
+ const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`;
30
+ const raw = req.url ?? "/";
31
+ const url = new URL(raw.replace(/^\/+/, "/"), base);
32
+ const method = req.method ?? "GET";
33
+ const init = { method, headers: req.headers };
34
+ if (method !== "GET" && method !== "HEAD" && body.length > 0) {
35
+ init.body = body;
36
+ }
37
+ const aborted = new AbortController();
38
+ res.once("close", () => {
39
+ if (!res.writableFinished)
40
+ aborted.abort();
41
+ });
42
+ init.signal = aborted.signal;
43
+ const request = new Request(url, init);
44
+ let response;
45
+ try {
46
+ response = await api.fetch(request);
47
+ } catch (error) {
48
+ if (error.code === "MOCKINGBIRD_DROP") {
49
+ req.socket.destroy();
50
+ return;
51
+ }
52
+ res.writeHead(500, { "content-type": "application/json" });
53
+ res.end(JSON.stringify({
54
+ error: {
55
+ type: "mockingbird_internal",
56
+ message: error instanceof Error ? error.message : String(error)
57
+ }
58
+ }));
59
+ return;
60
+ }
61
+ const headers = Object.fromEntries(response.headers);
62
+ const cookies = response.headers.getSetCookie();
63
+ if (cookies.length > 0)
64
+ headers["set-cookie"] = cookies;
65
+ if (!response.body) {
66
+ res.writeHead(response.status, headers);
67
+ res.end();
68
+ return;
69
+ }
70
+ res.writeHead(response.status, headers);
71
+ res.flushHeaders();
72
+ const reader = response.body.getReader();
73
+ try {
74
+ for (; ; ) {
75
+ const { done, value } = await reader.read();
76
+ if (done)
77
+ break;
78
+ if (!res.write(value))
79
+ await new Promise((resolve) => res.once("drain", resolve));
80
+ }
81
+ res.end();
82
+ } catch {
83
+ res.destroy();
84
+ } finally {
85
+ reader.releaseLock();
86
+ }
87
+ });
88
+ await new Promise((resolve, reject) => {
89
+ server.once("error", reject);
90
+ server.listen(options.port ?? 0, options.host, resolve);
91
+ });
92
+ return server;
93
+ };
94
+
95
+ // ../../adapters/node/dist/listen.js
96
+ var listen = async (api, options = {}) => {
97
+ const host = options.host ?? "127.0.0.1";
98
+ const server = await serve(api, { port: options.port ?? 0, host });
99
+ const address = server.address();
100
+ const port = typeof address === "object" && address !== null ? address.port : options.port ?? 0;
101
+ const shown = host.includes(":") ? `[${host}]` : host;
102
+ return {
103
+ url: `http://${shown}:${port}`,
104
+ port,
105
+ host,
106
+ server,
107
+ close: () => new Promise((resolve, reject) => {
108
+ server.close((error) => {
109
+ if (error && error.code !== "ERR_SERVER_NOT_RUNNING")
110
+ reject(error);
111
+ else
112
+ resolve();
113
+ });
114
+ server.closeAllConnections?.();
115
+ })
116
+ };
117
+ };
118
+
119
+ // ../../adapters/node/dist/cli.js
120
+ var optionHelp = (options) => Object.entries(options).map(([name2, option]) => {
121
+ const flag = `--${name2}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
122
+ const fallback = option.default !== void 0 ? ` (default: ${String(option.default)})` : "";
123
+ return ` ${flag.padEnd(30)} ${option.description}${fallback}`;
124
+ });
125
+ var help = (spec) => [
126
+ `${spec.bin} \u2014 ${spec.description}`,
127
+ "",
128
+ "Usage:",
129
+ ` ${spec.bin} <command> [options]`,
130
+ "",
131
+ "Commands:",
132
+ ...Object.entries(spec.commands).map(([name2, c]) => ` ${name2.padEnd(30)} ${c.summary}`),
133
+ "",
134
+ `Run \`${spec.bin} <command> --help\` for a command's options.`
135
+ ].join("\n");
136
+ var commandHelp = (spec, name2, command) => [
137
+ `${spec.bin} ${name2} \u2014 ${command.summary}`,
138
+ "",
139
+ "Usage:",
140
+ ` ${command.usage ?? `${spec.bin} ${name2} [options]`}`,
141
+ ...command.options ? ["", "Options:", ...optionHelp(command.options)] : []
142
+ ].join("\n");
143
+ var runCli = async (spec, argv) => {
144
+ const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : void 0;
145
+ const words = pair !== void 0 && spec.commands[pair] ? 2 : 1;
146
+ const name2 = words === 2 ? pair : argv[0];
147
+ const rest = argv.slice(words);
148
+ if (name2 === void 0 || name2 === "--help" || name2 === "-h" || name2 === "help") {
149
+ console.log(help(spec));
150
+ return 0;
151
+ }
152
+ const command = spec.commands[name2];
153
+ if (!command) {
154
+ console.error(`${spec.bin}: unknown command ${JSON.stringify(name2)}
155
+
156
+ ${help(spec)}`);
157
+ return 2;
158
+ }
159
+ if (rest.includes("--help") || rest.includes("-h")) {
160
+ console.log(commandHelp(spec, name2, command));
161
+ return 0;
162
+ }
163
+ let parsed;
164
+ try {
165
+ parsed = parseArgs({
166
+ args: rest,
167
+ allowPositionals: true,
168
+ strict: true,
169
+ options: Object.fromEntries(Object.entries(command.options ?? {}).map(([key, option]) => [
170
+ key,
171
+ {
172
+ type: option.type,
173
+ ...option.default !== void 0 ? { default: option.default } : {}
174
+ }
175
+ ]))
176
+ });
177
+ } catch (error) {
178
+ console.error(`${spec.bin} ${name2}: ${error instanceof Error ? error.message : String(error)}
179
+
180
+ ${commandHelp(spec, name2, command)}`);
181
+ return 2;
182
+ }
183
+ return command.run(parsed.values, parsed.positionals);
184
+ };
185
+ var COMMON_SERVE_OPTIONS = {
186
+ port: { type: "string", value: "<port>", description: "Port to listen on" },
187
+ host: { type: "string", value: "<host>", description: "Interface to bind", default: "127.0.0.1" },
188
+ "admin-key": {
189
+ type: "string",
190
+ value: "<key>",
191
+ description: "Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)"
192
+ },
193
+ seed: { type: "string", value: "<seed>", description: "Seed for every random choice" },
194
+ log: {
195
+ type: "string",
196
+ value: "<pretty|json|off>",
197
+ description: "Request log format",
198
+ default: "pretty"
199
+ },
200
+ "log-requests": {
201
+ type: "boolean",
202
+ description: "One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json"
203
+ },
204
+ config: {
205
+ type: "string",
206
+ value: "<file>",
207
+ description: "Serve every service in a mockingbird.json config instead"
208
+ }
209
+ };
210
+ var formatLog = (format) => {
211
+ if (format === "off")
212
+ return void 0;
213
+ if (format === "json")
214
+ return (entry) => console.log(JSON.stringify(entry));
215
+ return (entry) => {
216
+ const op = entry.operationId ?? (entry.unmatched ? "UNMATCHED" : "-");
217
+ const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
218
+ const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
219
+ const adopted = entry.adopted ? " adopted" : "";
220
+ console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}`);
221
+ };
222
+ };
223
+ var asString = (value) => typeof value === "string" ? value : void 0;
224
+ var loadTarget = async (name2, own) => {
225
+ if (name2 === own.name)
226
+ return own;
227
+ const specifier = `@crvouga/mockingbird-service-${name2}/server`;
228
+ try {
229
+ const mod = await import(specifier);
230
+ if (!mod.serveTarget)
231
+ throw new Error(`${specifier} exports no serveTarget`);
232
+ return mod.serveTarget;
233
+ } catch (error) {
234
+ const reason = error instanceof Error ? error.message : String(error);
235
+ throw new Error(`cannot load service "${name2}": ${reason}. Install @crvouga/mockingbird-service-${name2}.`);
236
+ }
237
+ };
238
+ var start = async (target, values, config) => {
239
+ const runtime = await target.create(values, {
240
+ adminKey: config.adminKey,
241
+ seed: config.seed,
242
+ onLog: formatLog(config.log)
243
+ });
244
+ const listening = await listen(runtime, { port: config.port, host: config.host });
245
+ console.log(`${target.name} mock listening on ${listening.url}`);
246
+ console.log(`${target.name} health: GET ${listening.url}/health`);
247
+ console.log(`${target.name} admin: ${listening.url}/__admin (${config.adminKey ? "x-mockingbird-admin-key required" : "open \u2014 pass --admin-key to lock"})`);
248
+ for (const line of target.banner?.(runtime) ?? [])
249
+ console.log(`${target.name} ${line}`);
250
+ return listening;
251
+ };
252
+ var untilSignal = async (servers) => new Promise((resolve) => {
253
+ const stop = async () => {
254
+ await Promise.allSettled(servers.map((s) => s.close()));
255
+ resolve(0);
256
+ };
257
+ process.once("SIGINT", stop);
258
+ process.once("SIGTERM", stop);
259
+ });
260
+ var serveCommand = (target) => ({
261
+ summary: `Serve the ${target.name} mock over HTTP`,
262
+ options: { ...COMMON_SERVE_OPTIONS, ...target.options },
263
+ async run(values) {
264
+ const log = values["log-requests"] === true ? "json" : asString(values.log) ?? "pretty";
265
+ if (!["pretty", "json", "off"].includes(log)) {
266
+ console.error(`--log must be pretty, json or off (got ${log})`);
267
+ return 2;
268
+ }
269
+ const configPath = asString(values.config);
270
+ if (configPath !== void 0) {
271
+ const config = JSON.parse(await readFile(configPath, "utf8"));
272
+ const servers = [];
273
+ try {
274
+ for (const [name2, entry] of Object.entries(config.services ?? {})) {
275
+ const each = await loadTarget(name2, target);
276
+ servers.push(await start(each, entry.options ?? {}, {
277
+ port: entry.port ?? each.defaultPort,
278
+ host: entry.host ?? "127.0.0.1",
279
+ ...entry.adminKey !== void 0 ? { adminKey: entry.adminKey } : {},
280
+ ...entry.seed !== void 0 ? { seed: entry.seed } : {},
281
+ log: config.log ?? log
282
+ }));
283
+ }
284
+ } catch (error) {
285
+ await Promise.allSettled(servers.map((s) => s.close()));
286
+ console.error(error instanceof Error ? error.message : String(error));
287
+ return 1;
288
+ }
289
+ return untilSignal(servers);
290
+ }
291
+ const port = asString(values.port);
292
+ const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
293
+ const seed = asString(values.seed);
294
+ let listening;
295
+ try {
296
+ listening = await start(target, values, {
297
+ port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
298
+ host: asString(values.host) ?? "127.0.0.1",
299
+ ...adminKey !== void 0 ? { adminKey } : {},
300
+ ...seed !== void 0 ? { seed } : {},
301
+ log
302
+ });
303
+ } catch (error) {
304
+ console.error(error instanceof Error ? error.message : String(error));
305
+ return 1;
306
+ }
307
+ return untilSignal([listening]);
308
+ }
309
+ });
310
+
311
+ // src/tls.ts
312
+ import { generateKeyPairSync, randomBytes, sign } from "node:crypto";
313
+ var tlv = (tag, ...parts) => {
314
+ const length = parts.reduce((sum, part) => sum + part.length, 0);
315
+ const header = length < 128 ? [tag, length] : length < 256 ? [tag, 129, length] : [tag, 130, length >> 8, length & 255];
316
+ const out = new Uint8Array(header.length + length);
317
+ out.set(header, 0);
318
+ let offset = header.length;
319
+ for (const part of parts) {
320
+ out.set(part, offset);
321
+ offset += part.length;
322
+ }
323
+ return out;
324
+ };
325
+ var sequence = (...parts) => tlv(48, ...parts);
326
+ var bytes = (value) => new TextEncoder().encode(value);
327
+ var oid = (dotted) => {
328
+ const [first = 0, second = 0, ...rest] = dotted.split(".").map(Number);
329
+ const out = [first * 40 + second];
330
+ for (const arc of rest) {
331
+ const base128 = [arc & 127];
332
+ for (let value = arc >> 7; value > 0; value >>= 7) base128.unshift(value & 127 | 128);
333
+ out.push(...base128);
334
+ }
335
+ return tlv(6, Uint8Array.from(out));
336
+ };
337
+ var integer = (value) => {
338
+ let start2 = 0;
339
+ while (start2 < value.length - 1 && value[start2] === 0) start2++;
340
+ const bytes2 = value.subarray(start2);
341
+ return tlv(2, (bytes2[0] ?? 0) & 128 ? Uint8Array.from([0, ...bytes2]) : bytes2);
342
+ };
343
+ var utcTime = (date) => {
344
+ const two = (n) => String(n).padStart(2, "0");
345
+ return tlv(
346
+ 23,
347
+ bytes(
348
+ `${two(date.getUTCFullYear() % 100)}${two(date.getUTCMonth() + 1)}${two(date.getUTCDate())}${two(date.getUTCHours())}${two(date.getUTCMinutes())}${two(date.getUTCSeconds())}Z`
349
+ )
350
+ );
351
+ };
352
+ var name = (commonName) => sequence(tlv(49, sequence(oid("2.5.4.3"), tlv(12, bytes(commonName)))));
353
+ var ipBytes = (ip) => Uint8Array.from(ip.split(".").map(Number));
354
+ var pem = (label, der) => `-----BEGIN ${label}-----
355
+ ${(Buffer.from(der).toString("base64").match(/.{1,64}/g) ?? []).join("\n")}
356
+ -----END ${label}-----
357
+ `;
358
+ var selfSignedCertificate = (hosts = ["localhost", "127.0.0.1"]) => {
359
+ const { privateKey, publicKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
360
+ const algorithm = sequence(oid("1.2.840.10045.4.3.2"));
361
+ const now = Date.now();
362
+ const altNames = sequence(
363
+ ...hosts.map(
364
+ (host) => /^\d+\.\d+\.\d+\.\d+$/.test(host) ? tlv(135, ipBytes(host)) : tlv(130, bytes(host))
365
+ )
366
+ );
367
+ const extensions = tlv(
368
+ 163,
369
+ sequence(
370
+ sequence(oid("2.5.29.19"), tlv(4, sequence(tlv(1, Uint8Array.from([255]))))),
371
+ sequence(oid("2.5.29.17"), tlv(4, altNames))
372
+ )
373
+ );
374
+ const serial = randomBytes(16);
375
+ serial[0] = (serial[0] ?? 0) & 127;
376
+ const tbs = sequence(
377
+ tlv(160, integer(Uint8Array.from([2]))),
378
+ integer(serial),
379
+ algorithm,
380
+ name(hosts[0] ?? "localhost"),
381
+ sequence(utcTime(new Date(now - 864e5)), utcTime(new Date(now + 3650 * 864e5))),
382
+ name(hosts[0] ?? "localhost"),
383
+ new Uint8Array(publicKey.export({ type: "spki", format: "der" })),
384
+ extensions
385
+ );
386
+ const signature = sign("sha256", tbs, privateKey);
387
+ const certificate = sequence(tbs, algorithm, tlv(3, Uint8Array.from([0]), signature));
388
+ return {
389
+ cert: pem("CERTIFICATE", certificate),
390
+ key: privateKey.export({ type: "pkcs8", format: "pem" }).toString()
391
+ };
392
+ };
393
+
394
+ // src/server.ts
395
+ var DEFAULT_PORT = 8793;
396
+ var listenOn = async (server, bind) => {
397
+ await new Promise((resolve, reject) => {
398
+ server.once("error", reject);
399
+ server.listen(bind.port, bind.host, resolve);
400
+ });
401
+ const address = server.address();
402
+ return typeof address === "object" && address !== null ? address.port : bind.port;
403
+ };
404
+ var tunnel = (socket, target, head, sockets) => {
405
+ const inner = connect(target.port, target.host);
406
+ sockets.add(socket);
407
+ sockets.add(inner);
408
+ const close = () => {
409
+ socket.destroy();
410
+ inner.destroy();
411
+ sockets.delete(socket);
412
+ sockets.delete(inner);
413
+ };
414
+ socket.on("error", close).on("close", close);
415
+ inner.on("error", close).on("close", close);
416
+ if (head.length > 0) inner.write(head);
417
+ socket.pipe(inner);
418
+ inner.pipe(socket);
419
+ socket.resume();
420
+ };
421
+ var secureDoor = async (certificate, upstream, bind) => {
422
+ const sockets = /* @__PURE__ */ new Set();
423
+ const terminator = createTlsServer(
424
+ { cert: certificate.cert, key: certificate.key },
425
+ (socket) => tunnel(socket, upstream, new Uint8Array(), sockets)
426
+ );
427
+ const tlsPort = await listenOn(terminator, { host: "127.0.0.1", port: 0 });
428
+ const decrypted = { host: "127.0.0.1", port: tlsPort };
429
+ const door = createNetServer((socket) => {
430
+ let buffered = Buffer.alloc(0);
431
+ const onData = (chunk) => {
432
+ buffered = Buffer.concat([buffered, chunk]);
433
+ if (buffered[0] === 22) {
434
+ socket.off("data", onData);
435
+ socket.pause();
436
+ tunnel(socket, decrypted, buffered, sockets);
437
+ return;
438
+ }
439
+ const end = buffered.indexOf("\r\n\r\n");
440
+ if (end < 0 && buffered.length < 16384) return;
441
+ socket.off("data", onData);
442
+ socket.pause();
443
+ if (/^CONNECT /i.test(buffered.subarray(0, 8).toString("latin1"))) {
444
+ socket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
445
+ tunnel(socket, decrypted, buffered.subarray(end + 4), sockets);
446
+ } else {
447
+ tunnel(socket, upstream, buffered, sockets);
448
+ }
449
+ };
450
+ socket.on("data", onData);
451
+ socket.on("error", () => socket.destroy());
452
+ });
453
+ const port = await listenOn(door, bind);
454
+ return {
455
+ port,
456
+ close: () => new Promise((resolve) => {
457
+ for (const socket of sockets) socket.destroy();
458
+ terminator.close();
459
+ door.close(() => resolve());
460
+ })
461
+ };
462
+ };
463
+ var CERTIFICATE_HOSTS = ["localhost", "127.0.0.1", "mailosaur.com"];
464
+ var certificateFor = (tls) => tls.cert && tls.key ? { cert: tls.cert, key: tls.key } : selfSignedCertificate(CERTIFICATE_HOSTS);
465
+ var createServer2 = async (options = {}) => {
466
+ const { port, host, tls, ...rest } = options;
467
+ const runtime = createRuntime(rest);
468
+ const listening = await listen(runtime, {
469
+ port: port ?? 0,
470
+ ...host !== void 0 ? { host } : {}
471
+ });
472
+ if (!tls) return { ...listening, runtime };
473
+ const options_ = tls === true ? {} : tls;
474
+ const certificate = certificateFor(options_);
475
+ const door = await secureDoor(
476
+ certificate,
477
+ { host: listening.host, port: listening.port },
478
+ { host: listening.host, port: options_.port ?? 0 }
479
+ );
480
+ const shown = listening.host.includes(":") ? `[${listening.host}]` : listening.host;
481
+ return {
482
+ ...listening,
483
+ runtime,
484
+ tlsUrl: `https://${shown}:${door.port}`,
485
+ proxyUrl: `http://${shown}:${door.port}`,
486
+ cert: certificate.cert,
487
+ close: async () => {
488
+ await door.close();
489
+ await listening.close();
490
+ }
491
+ };
492
+ };
493
+ var text = (value) => typeof value === "string" ? value : void 0;
494
+ var serveTarget = {
495
+ name: "mailosaur",
496
+ defaultPort: DEFAULT_PORT,
497
+ options: {
498
+ "tls-port": {
499
+ type: "string",
500
+ value: "<port>",
501
+ description: "Also serve HTTPS and an HTTP CONNECT proxy on this port (the mailosaur SDK only speaks HTTPS, always to port 443): construct the SDK with HTTPS_PROXY=http://127.0.0.1:<port>"
502
+ },
503
+ "tls-cert": { type: "string", value: "<pem file>", description: "Certificate for --tls-port" },
504
+ "tls-key": { type: "string", value: "<pem file>", description: "Private key for --tls-port" },
505
+ "tls-cert-out": {
506
+ type: "string",
507
+ value: "<file>",
508
+ description: "Write the generated self-signed certificate here (use it as NODE_EXTRA_CA_CERTS for the app)"
509
+ },
510
+ "poll-delay": {
511
+ type: "string",
512
+ value: "<ms[,ms\u2026]>",
513
+ description: "x-ms-delay the SDK waits between empty polls (default 20)"
514
+ }
515
+ },
516
+ create: async (values, common) => {
517
+ const delays = text(values["poll-delay"]);
518
+ if (delays && !/^\d+(,\d+)*$/.test(delays)) {
519
+ throw new Error("--poll-delay must look like 20 or 20,50,100");
520
+ }
521
+ const runtime = createRuntime({
522
+ ...delays ? { settings: { pollDelaysMs: delays.split(",").map(Number) } } : {},
523
+ ...common.adminKey !== void 0 ? { adminKey: common.adminKey } : {},
524
+ ...common.seed !== void 0 ? { seed: common.seed } : {},
525
+ ...common.onLog ? { onLog: common.onLog } : {}
526
+ });
527
+ const tlsPort = text(values["tls-port"]);
528
+ if (tlsPort !== void 0) {
529
+ const certFile = text(values["tls-cert"]);
530
+ const keyFile = text(values["tls-key"]);
531
+ if (certFile === void 0 !== (keyFile === void 0)) {
532
+ throw new Error("--tls-cert and --tls-key go together");
533
+ }
534
+ const certificate = certificateFor(
535
+ certFile && keyFile ? { cert: await readFile2(certFile, "utf8"), key: await readFile2(keyFile, "utf8") } : {}
536
+ );
537
+ const out = text(values["tls-cert-out"]);
538
+ if (out) writeFileSync(out, certificate.cert);
539
+ const inner = await listen(runtime, { port: 0 });
540
+ const door = await secureDoor(
541
+ certificate,
542
+ { host: inner.host, port: inner.port },
543
+ { host: "127.0.0.1", port: Number(tlsPort) }
544
+ );
545
+ console.log(
546
+ `mailosaur HTTPS + CONNECT proxy on 127.0.0.1:${door.port} (HTTPS_PROXY=http://127.0.0.1:${door.port})`
547
+ );
548
+ }
549
+ return runtime;
550
+ },
551
+ banner: () => [
552
+ "auth: Authorization: Basic base64(<api key>:) \u2014 any key; the SDK needs HTTPS (--tls-port)",
553
+ "ingest: POST /__admin/ingest {to, from?, subject?, html?, text?, server?, type?}",
554
+ "namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<api key>: <ns>}"
555
+ ]
556
+ };
557
+
558
+ export {
559
+ runCli,
560
+ serveCommand,
561
+ selfSignedCertificate,
562
+ DEFAULT_PORT,
563
+ CERTIFICATE_HOSTS,
564
+ createServer2 as createServer,
565
+ serveTarget
566
+ };
567
+ //# sourceMappingURL=chunk-LQNQZS6T.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/server.ts", "../../../adapters/node/src/cli.ts", "../../../adapters/node/src/serve.ts", "../../../adapters/node/src/listen.ts", "../src/tls.ts"],
4
+ "sourcesContent": ["/// <reference types=\"node\" />\nimport { writeFileSync } from \"node:fs\"\nimport { readFile } from \"node:fs/promises\"\nimport {\n connect,\n createServer as createNetServer,\n type Server as NetServer,\n type Socket,\n} from \"node:net\"\nimport { createServer as createTlsServer, type Server as TlsServer } from \"node:tls\"\nimport { type Listening, listen, type ServeTarget } from \"@crvouga/mockingbird-adapter-node\"\nimport { createRuntime, type MailosaurRuntime, type MailosaurRuntimeOptions } from \"./runtime.js\"\nimport { type SelfSignedCertificate, selfSignedCertificate } from \"./tls.js\"\n\nexport type { SelfSignedCertificate } from \"./tls.js\"\nexport { selfSignedCertificate } from \"./tls.js\"\n\n/** Port `mockingbird-mailosaur serve` listens on when none is given. */\nexport const DEFAULT_PORT = 8793\n\nexport type TlsOptions = {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** PEM certificate and key; default: a fresh self-signed one for localhost, 127.0.0.1 and mailosaur.com. */\n cert?: string\n key?: string\n}\n\nexport type MailosaurServerOptions = MailosaurRuntimeOptions & {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`. */\n host?: string\n /**\n * Also answer over HTTPS. The `mailosaur` SDK only speaks HTTPS (it calls `https.request`\n * whatever its base URL says), so pass `tls: true` whenever the SDK is the client.\n */\n tls?: boolean | TlsOptions\n}\n\nexport type MailosaurServer = Listening & {\n runtime: MailosaurRuntime\n /** `https://127.0.0.1:<port>` when `tls` was asked for. */\n tlsUrl?: string\n /** The same port as an HTTP proxy (`HTTPS_PROXY`), how the unmodified SDK reaches the mock. */\n proxyUrl?: string\n /** The PEM certificate the HTTPS listener presents (trust it with `ca` / `NODE_EXTRA_CA_CERTS`). */\n cert?: string\n}\n\ntype Endpoint = { host: string; port: number }\ntype Door = { port: number; close(): Promise<void> }\n\nconst listenOn = async (server: NetServer | TlsServer, bind: Endpoint): Promise<number> => {\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject)\n server.listen(bind.port, bind.host, resolve)\n })\n const address = server.address()\n return typeof address === \"object\" && address !== null ? address.port : bind.port\n}\n\n/** Pipe `socket` (after replaying `head`) to `target`, tearing both down together. */\nconst tunnel = (socket: Socket, target: Endpoint, head: Uint8Array, sockets: Set<Socket>) => {\n const inner = connect(target.port, target.host)\n sockets.add(socket)\n sockets.add(inner)\n const close = () => {\n socket.destroy()\n inner.destroy()\n sockets.delete(socket)\n sockets.delete(inner)\n }\n socket.on(\"error\", close).on(\"close\", close)\n inner.on(\"error\", close).on(\"close\", close)\n if (head.length > 0) inner.write(head)\n socket.pipe(inner)\n inner.pipe(socket)\n socket.resume()\n}\n\n/**\n * One port that speaks HTTPS, plain HTTP and the HTTP `CONNECT` proxy method, all onto the\n * runtime's plain HTTP listener `upstream`:\n *\n * - a TLS ClientHello is terminated with `certificate` (the SDK's direct HTTPS);\n * - `CONNECT <any host>:<port>` is answered `200` and the tunnel is terminated the same way.\n * The `mailosaur` SDK drops the port of its base URL (it always connects to 443), so this\n * is how an unmodified SDK reaches the mock: `HTTPS_PROXY=http://127.0.0.1:<port>` when the\n * client is constructed, and the certificate trusted (`NODE_EXTRA_CA_CERTS`). Every CONNECT\n * target is tunnelled into the mock, never to the network;\n * - anything else is plain HTTP.\n */\nconst secureDoor = async (\n certificate: SelfSignedCertificate,\n upstream: Endpoint,\n bind: Endpoint,\n): Promise<Door> => {\n const sockets = new Set<Socket>()\n const terminator = createTlsServer({ cert: certificate.cert, key: certificate.key }, (socket) =>\n tunnel(socket, upstream, new Uint8Array(), sockets),\n )\n const tlsPort = await listenOn(terminator, { host: \"127.0.0.1\", port: 0 })\n const decrypted = { host: \"127.0.0.1\", port: tlsPort }\n const door = createNetServer((socket) => {\n let buffered = Buffer.alloc(0)\n const onData = (chunk: Buffer) => {\n buffered = Buffer.concat([buffered, chunk])\n if (buffered[0] === 0x16) {\n socket.off(\"data\", onData)\n socket.pause()\n tunnel(socket, decrypted, buffered, sockets)\n return\n }\n const end = buffered.indexOf(\"\\r\\n\\r\\n\")\n if (end < 0 && buffered.length < 16_384) return\n socket.off(\"data\", onData)\n socket.pause()\n if (/^CONNECT /i.test(buffered.subarray(0, 8).toString(\"latin1\"))) {\n socket.write(\"HTTP/1.1 200 Connection Established\\r\\n\\r\\n\")\n tunnel(socket, decrypted, buffered.subarray(end + 4), sockets)\n } else {\n tunnel(socket, upstream, buffered, sockets)\n }\n }\n socket.on(\"data\", onData)\n socket.on(\"error\", () => socket.destroy())\n })\n const port = await listenOn(door, bind)\n return {\n port,\n close: () =>\n new Promise<void>((resolve) => {\n for (const socket of sockets) socket.destroy()\n terminator.close()\n door.close(() => resolve())\n }),\n }\n}\n\n/** The hosts the generated certificate names: loopback, and the SDK's default host. */\nexport const CERTIFICATE_HOSTS = [\"localhost\", \"127.0.0.1\", \"mailosaur.com\"] as const\n\nconst certificateFor = (tls: TlsOptions): SelfSignedCertificate =>\n tls.cert && tls.key ? { cert: tls.cert, key: tls.key } : selfSignedCertificate(CERTIFICATE_HOSTS)\n\n/** Serve the Mailosaur mock over `node:http`, and over HTTPS too when `tls` is set. */\nexport const createServer = async (\n options: MailosaurServerOptions = {},\n): Promise<MailosaurServer> => {\n const { port, host, tls, ...rest } = options\n const runtime = createRuntime(rest)\n const listening = await listen(runtime, {\n port: port ?? 0,\n ...(host !== undefined ? { host } : {}),\n })\n if (!tls) return { ...listening, runtime }\n const options_ = tls === true ? {} : tls\n const certificate = certificateFor(options_)\n const door = await secureDoor(\n certificate,\n { host: listening.host, port: listening.port },\n { host: listening.host, port: options_.port ?? 0 },\n )\n const shown = listening.host.includes(\":\") ? `[${listening.host}]` : listening.host\n return {\n ...listening,\n runtime,\n tlsUrl: `https://${shown}:${door.port}`,\n proxyUrl: `http://${shown}:${door.port}`,\n cert: certificate.cert,\n close: async () => {\n await door.close()\n await listening.close()\n },\n }\n}\n\nconst text = (value: string | boolean | undefined) =>\n typeof value === \"string\" ? value : undefined\n\n/** How `serve` (and `serve --config`) builds the Mailosaur mock from flags. */\nexport const serveTarget: ServeTarget = {\n name: \"mailosaur\",\n defaultPort: DEFAULT_PORT,\n options: {\n \"tls-port\": {\n type: \"string\",\n value: \"<port>\",\n description:\n \"Also serve HTTPS and an HTTP CONNECT proxy on this port (the mailosaur SDK only speaks HTTPS, always to port 443): construct the SDK with HTTPS_PROXY=http://127.0.0.1:<port>\",\n },\n \"tls-cert\": { type: \"string\", value: \"<pem file>\", description: \"Certificate for --tls-port\" },\n \"tls-key\": { type: \"string\", value: \"<pem file>\", description: \"Private key for --tls-port\" },\n \"tls-cert-out\": {\n type: \"string\",\n value: \"<file>\",\n description:\n \"Write the generated self-signed certificate here (use it as NODE_EXTRA_CA_CERTS for the app)\",\n },\n \"poll-delay\": {\n type: \"string\",\n value: \"<ms[,ms\u2026]>\",\n description: \"x-ms-delay the SDK waits between empty polls (default 20)\",\n },\n },\n create: async (values, common) => {\n const delays = text(values[\"poll-delay\"])\n if (delays && !/^\\d+(,\\d+)*$/.test(delays)) {\n throw new Error(\"--poll-delay must look like 20 or 20,50,100\")\n }\n const runtime = createRuntime({\n ...(delays ? { settings: { pollDelaysMs: delays.split(\",\").map(Number) } } : {}),\n ...(common.adminKey !== undefined ? { adminKey: common.adminKey } : {}),\n ...(common.seed !== undefined ? { seed: common.seed } : {}),\n ...(common.onLog ? { onLog: common.onLog } : {}),\n })\n const tlsPort = text(values[\"tls-port\"])\n if (tlsPort !== undefined) {\n const certFile = text(values[\"tls-cert\"])\n const keyFile = text(values[\"tls-key\"])\n if ((certFile === undefined) !== (keyFile === undefined)) {\n throw new Error(\"--tls-cert and --tls-key go together\")\n }\n const certificate = certificateFor(\n certFile && keyFile\n ? { cert: await readFile(certFile, \"utf8\"), key: await readFile(keyFile, \"utf8\") }\n : {},\n )\n const out = text(values[\"tls-cert-out\"])\n if (out) writeFileSync(out, certificate.cert)\n // The secure door fronts its own loopback HTTP listener over the same runtime.\n const inner = await listen(runtime, { port: 0 })\n const door = await secureDoor(\n certificate,\n { host: inner.host, port: inner.port },\n { host: \"127.0.0.1\", port: Number(tlsPort) },\n )\n console.log(\n `mailosaur HTTPS + CONNECT proxy on 127.0.0.1:${door.port} (HTTPS_PROXY=http://127.0.0.1:${door.port})`,\n )\n }\n return runtime\n },\n banner: () => [\n \"auth: Authorization: Basic base64(<api key>:) \u2014 any key; the SDK needs HTTPS (--tls-port)\",\n \"ingest: POST /__admin/ingest {to, from?, subject?, html?, text?, server?, type?}\",\n \"namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<api key>: <ns>}\",\n ],\n}\n", "import { readFile } from \"node:fs/promises\"\nimport { type ParseArgsConfig, parseArgs } from \"node:util\"\nimport type { RequestLog, ServiceInstance, ServiceRuntime } from \"@crvouga/mockingbird-service\"\nimport { type Listening, listen } from \"./listen.js\"\n\nexport type CliOption = {\n type: \"string\" | \"boolean\"\n description: string\n /** Shown in help; the value placeholder, e.g. `<port>`. */\n value?: string\n default?: string | boolean\n}\n\nexport type CliValues = Record<string, string | boolean | undefined>\n\nexport type CliCommand = {\n summary: string\n usage?: string\n options?: Record<string, CliOption>\n /** Resolves to an exit code; a server command resolves only when it stops. */\n run(values: CliValues, positionals: string[]): Promise<number>\n}\n\nexport type CliSpec = {\n bin: string\n description: string\n commands: Record<string, CliCommand>\n}\n\nconst optionHelp = (options: Record<string, CliOption>): string[] =>\n Object.entries(options).map(([name, option]) => {\n const flag = `--${name}${option.type === \"string\" ? ` ${option.value ?? \"<value>\"}` : \"\"}`\n const fallback = option.default !== undefined ? ` (default: ${String(option.default)})` : \"\"\n return ` ${flag.padEnd(30)} ${option.description}${fallback}`\n })\n\nconst help = (spec: CliSpec): string =>\n [\n `${spec.bin} \u2014 ${spec.description}`,\n \"\",\n \"Usage:\",\n ` ${spec.bin} <command> [options]`,\n \"\",\n \"Commands:\",\n ...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),\n \"\",\n `Run \\`${spec.bin} <command> --help\\` for a command's options.`,\n ].join(\"\\n\")\n\nconst commandHelp = (spec: CliSpec, name: string, command: CliCommand): string =>\n [\n `${spec.bin} ${name} \u2014 ${command.summary}`,\n \"\",\n \"Usage:\",\n ` ${command.usage ?? `${spec.bin} ${name} [options]`}`,\n ...(command.options ? [\"\", \"Options:\", ...optionHelp(command.options)] : []),\n ].join(\"\\n\")\n\n/** Parse `argv` against `spec` and run the chosen command. Resolves to an exit code. */\nexport const runCli = async (spec: CliSpec, argv: string[]): Promise<number> => {\n // Two-word commands (`corpus pull`) win over one-word ones.\n const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : undefined\n const words = pair !== undefined && spec.commands[pair] ? 2 : 1\n const name = words === 2 ? pair : argv[0]\n const rest = argv.slice(words)\n if (name === undefined || name === \"--help\" || name === \"-h\" || name === \"help\") {\n console.log(help(spec))\n return 0\n }\n const command = spec.commands[name]\n if (!command) {\n console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}\\n\\n${help(spec)}`)\n return 2\n }\n if (rest.includes(\"--help\") || rest.includes(\"-h\")) {\n console.log(commandHelp(spec, name, command))\n return 0\n }\n let parsed: { values: CliValues; positionals: string[] }\n try {\n parsed = parseArgs({\n args: rest,\n allowPositionals: true,\n strict: true,\n options: Object.fromEntries(\n Object.entries(command.options ?? {}).map(([key, option]) => [\n key,\n {\n type: option.type,\n ...(option.default !== undefined ? { default: option.default } : {}),\n },\n ]),\n ) as ParseArgsConfig[\"options\"],\n }) as { values: CliValues; positionals: string[] }\n } catch (error) {\n console.error(\n `${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}\\n\\n${commandHelp(spec, name, command)}`,\n )\n return 2\n }\n return command.run(parsed.values, parsed.positionals)\n}\n\n// \u2500\u2500 serve \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type LogFormat = \"pretty\" | \"json\" | \"off\"\n\nexport type CommonServeOptions = {\n adminKey: string | undefined\n seed: string | undefined\n onLog: ((entry: RequestLog) => void) | undefined\n}\n\n/**\n * What a service contributes to `serve`: how to build its runtime from CLI flags,\n * and what to say at startup. Every service's `./server` entry exports one as\n * `serveTarget`, which is also how `serve --config` finds services by name.\n */\nexport type ServeTarget = {\n name: string\n defaultPort: number\n /** Serve flags beyond the common ones. */\n options?: Record<string, CliOption>\n create(\n values: CliValues,\n common: CommonServeOptions,\n ): Promise<ServiceRuntime<ServiceInstance>> | ServiceRuntime<ServiceInstance>\n /** Startup lines after the listen address, e.g. the loaded corpus version. */\n banner?(runtime: ServiceRuntime<ServiceInstance>): string[]\n}\n\nconst COMMON_SERVE_OPTIONS: Record<string, CliOption> = {\n port: { type: \"string\", value: \"<port>\", description: \"Port to listen on\" },\n host: { type: \"string\", value: \"<host>\", description: \"Interface to bind\", default: \"127.0.0.1\" },\n \"admin-key\": {\n type: \"string\",\n value: \"<key>\",\n description: \"Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)\",\n },\n seed: { type: \"string\", value: \"<seed>\", description: \"Seed for every random choice\" },\n log: {\n type: \"string\",\n value: \"<pretty|json|off>\",\n description: \"Request log format\",\n default: \"pretty\",\n },\n \"log-requests\": {\n type: \"boolean\",\n description:\n \"One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json\",\n },\n config: {\n type: \"string\",\n value: \"<file>\",\n description: \"Serve every service in a mockingbird.json config instead\",\n },\n}\n\nconst formatLog = (format: LogFormat) => {\n if (format === \"off\") return undefined\n if (format === \"json\") return (entry: RequestLog) => console.log(JSON.stringify(entry))\n return (entry: RequestLog) => {\n const op = entry.operationId ?? (entry.unmatched ? \"UNMATCHED\" : \"-\")\n const ns = entry.namespace === \"default\" ? \"\" : ` [${entry.namespace}]`\n const fault = entry.faultId ? ` fault=${entry.faultId}` : \"\"\n const adopted = entry.adopted ? \" adopted\" : \"\"\n console.log(\n `${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}`,\n )\n }\n}\n\nconst asString = (value: string | boolean | undefined): string | undefined =>\n typeof value === \"string\" ? value : undefined\n\n/** A service entry in `mockingbird.json`. */\nexport type ConfigService = {\n port?: number\n host?: string\n adminKey?: string\n seed?: string\n /** Service-specific serve flags, by long name: `{ \"webhook-url\": \"\u2026\" }`. */\n options?: Record<string, string | boolean>\n}\n\nexport type MockingbirdConfig = {\n /** Keyed by service name: `junction` loads `@crvouga/mockingbird-service-junction`. */\n services: Record<string, ConfigService>\n log?: LogFormat\n}\n\nconst loadTarget = async (name: string, own: ServeTarget): Promise<ServeTarget> => {\n if (name === own.name) return own\n const specifier = `@crvouga/mockingbird-service-${name}/server`\n try {\n const mod = (await import(specifier)) as { serveTarget?: ServeTarget }\n if (!mod.serveTarget) throw new Error(`${specifier} exports no serveTarget`)\n return mod.serveTarget\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n throw new Error(\n `cannot load service \"${name}\": ${reason}. Install @crvouga/mockingbird-service-${name}.`,\n )\n }\n}\n\nconst start = async (\n target: ServeTarget,\n values: CliValues,\n config: { port: number; host: string; adminKey?: string; seed?: string; log: LogFormat },\n): Promise<Listening> => {\n const runtime = await target.create(values, {\n adminKey: config.adminKey,\n seed: config.seed,\n onLog: formatLog(config.log),\n })\n const listening = await listen(runtime, { port: config.port, host: config.host })\n console.log(`${target.name} mock listening on ${listening.url}`)\n console.log(`${target.name} health: GET ${listening.url}/health`)\n console.log(\n `${target.name} admin: ${listening.url}/__admin (${config.adminKey ? \"x-mockingbird-admin-key required\" : \"open \u2014 pass --admin-key to lock\"})`,\n )\n for (const line of target.banner?.(runtime) ?? []) console.log(`${target.name} ${line}`)\n return listening\n}\n\nconst untilSignal = async (servers: Listening[]): Promise<number> =>\n new Promise((resolve) => {\n const stop = async () => {\n await Promise.allSettled(servers.map((s) => s.close()))\n resolve(0)\n }\n process.once(\"SIGINT\", stop)\n process.once(\"SIGTERM\", stop)\n })\n\n/** The standard `serve` command for a service, including multi-service `--config`. */\nexport const serveCommand = (target: ServeTarget): CliCommand => ({\n summary: `Serve the ${target.name} mock over HTTP`,\n options: { ...COMMON_SERVE_OPTIONS, ...target.options },\n async run(values) {\n const log = (\n values[\"log-requests\"] === true ? \"json\" : (asString(values.log) ?? \"pretty\")\n ) as LogFormat\n if (![\"pretty\", \"json\", \"off\"].includes(log)) {\n console.error(`--log must be pretty, json or off (got ${log})`)\n return 2\n }\n const configPath = asString(values.config)\n if (configPath !== undefined) {\n const config = JSON.parse(await readFile(configPath, \"utf8\")) as MockingbirdConfig\n const servers: Listening[] = []\n try {\n for (const [name, entry] of Object.entries(config.services ?? {})) {\n const each = await loadTarget(name, target)\n servers.push(\n await start(each, entry.options ?? {}, {\n port: entry.port ?? each.defaultPort,\n host: entry.host ?? \"127.0.0.1\",\n ...(entry.adminKey !== undefined ? { adminKey: entry.adminKey } : {}),\n ...(entry.seed !== undefined ? { seed: entry.seed } : {}),\n log: config.log ?? log,\n }),\n )\n }\n } catch (error) {\n await Promise.allSettled(servers.map((s) => s.close()))\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal(servers)\n }\n const port = asString(values.port)\n const adminKey = asString(values[\"admin-key\"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY\n const seed = asString(values.seed)\n let listening: Listening\n try {\n listening = await start(target, values, {\n port: port === undefined ? target.defaultPort : Number.parseInt(port, 10),\n host: asString(values.host) ?? \"127.0.0.1\",\n ...(adminKey !== undefined ? { adminKey } : {}),\n ...(seed !== undefined ? { seed } : {}),\n log,\n })\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal([listening])\n },\n})\n", "import { createServer } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\n\n/** Options for {@link serve}. */\nexport type NodeServeOptions = {\n port?: number\n host?: string\n}\n\n/**\n * Serve any Mockingbird {@link FetchAPI} over `node:http`.\n * Port defaults to `0`, so the OS assigns an ephemeral port (read from `server.address()`).\n */\nexport const serve = async (api: FetchAPI, options: NodeServeOptions = {}) => {\n const server = createServer(async (req, res) => {\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n chunks.push(Buffer.from(chunk))\n }\n const body = Buffer.concat(chunks)\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : undefined\n const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`\n const raw = req.url ?? \"/\"\n const url = new URL(raw.replace(/^\\/+/, \"/\"), base)\n const method = req.method ?? \"GET\"\n const init: RequestInit = { method, headers: req.headers as Record<string, string> }\n if (method !== \"GET\" && method !== \"HEAD\" && body.length > 0) {\n init.body = body\n }\n const aborted = new AbortController()\n res.once(\"close\", () => {\n if (!res.writableFinished) aborted.abort()\n })\n init.signal = aborted.signal\n const request = new Request(url, init)\n let response: Response\n try {\n response = await api.fetch(request)\n } catch (error) {\n // A `drop` fault: destroy the socket so the client sees the connection die.\n if ((error as { code?: string }).code === \"MOCKINGBIRD_DROP\") {\n req.socket.destroy()\n return\n }\n res.writeHead(500, { \"content-type\": \"application/json\" })\n res.end(\n JSON.stringify({\n error: {\n type: \"mockingbird_internal\",\n message: error instanceof Error ? error.message : String(error),\n },\n }),\n )\n return\n }\n // Headers#entries() joins repeated headers; Set-Cookie must stay one header per cookie.\n const headers: Record<string, string | string[]> = Object.fromEntries(response.headers)\n const cookies = response.headers.getSetCookie()\n if (cookies.length > 0) headers[\"set-cookie\"] = cookies\n if (!response.body) {\n res.writeHead(response.status, headers)\n res.end()\n return\n }\n // Stream the body chunk by chunk: event streams and long-polls must not be buffered.\n res.writeHead(response.status, headers)\n res.flushHeaders()\n const reader = response.body.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (!res.write(value)) await new Promise<void>((resolve) => res.once(\"drain\", resolve))\n }\n res.end()\n } catch {\n res.destroy()\n } finally {\n reader.releaseLock()\n }\n })\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject)\n server.listen(options.port ?? 0, options.host, resolve)\n })\n return server\n}\n", "import type { Server } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\nimport { serve } from \"./serve.js\"\n\nexport type ListenOptions = {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`: a mock should not be reachable off the machine by accident. */\n host?: string\n}\n\n/** A running server, with the address it actually bound. */\nexport type Listening = {\n url: string\n port: number\n host: string\n server: Server\n close(): Promise<void>\n}\n\nexport const listen = async (api: FetchAPI, options: ListenOptions = {}): Promise<Listening> => {\n const host = options.host ?? \"127.0.0.1\"\n const server = await serve(api, { port: options.port ?? 0, host })\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : (options.port ?? 0)\n const shown = host.includes(\":\") ? `[${host}]` : host\n return {\n url: `http://${shown}:${port}`,\n port,\n host,\n server,\n close: () =>\n new Promise<void>((resolve, reject) => {\n // Stop accepting first, then drop keep-alive sockets so close() can finish.\n server.close((error) => {\n if (error && (error as { code?: string }).code !== \"ERR_SERVER_NOT_RUNNING\") reject(error)\n else resolve()\n })\n server.closeAllConnections?.()\n }),\n }\n}\n", "/// <reference types=\"node\" />\nimport { generateKeyPairSync, randomBytes, sign } from \"node:crypto\"\n\n/**\n * A throwaway self-signed certificate for `localhost` / `127.0.0.1`, generated in memory at\n * startup. The Mailosaur SDK always speaks HTTPS (it calls `https.request` whatever the base\n * URL says), so the mock has to answer over TLS; generating the certificate here means no\n * private key ships in the package and no `openssl` is needed.\n *\n * Minimal DER encoding of an X.509 v3 certificate (ECDSA P-256 / SHA-256) with a\n * subjectAltName for every host given.\n */\nexport type SelfSignedCertificate = { cert: string; key: string }\n\nconst tlv = (tag: number, ...parts: Uint8Array[]): Uint8Array => {\n const length = parts.reduce((sum, part) => sum + part.length, 0)\n const header =\n length < 0x80\n ? [tag, length]\n : length < 0x100\n ? [tag, 0x81, length]\n : [tag, 0x82, length >> 8, length & 0xff]\n const out = new Uint8Array(header.length + length)\n out.set(header, 0)\n let offset = header.length\n for (const part of parts) {\n out.set(part, offset)\n offset += part.length\n }\n return out\n}\n\nconst sequence = (...parts: Uint8Array[]) => tlv(0x30, ...parts)\nconst bytes = (value: string) => new TextEncoder().encode(value)\n\nconst oid = (dotted: string): Uint8Array => {\n const [first = 0, second = 0, ...rest] = dotted.split(\".\").map(Number)\n const out = [first * 40 + second]\n for (const arc of rest) {\n const base128 = [arc & 0x7f]\n for (let value = arc >> 7; value > 0; value >>= 7) base128.unshift((value & 0x7f) | 0x80)\n out.push(...base128)\n }\n return tlv(0x06, Uint8Array.from(out))\n}\n\n/** A non-negative DER INTEGER: minimal bytes (no redundant leading zeros), 0x00-padded if the high bit is set. */\nconst integer = (value: Uint8Array) => {\n let start = 0\n while (start < value.length - 1 && value[start] === 0) start++\n const bytes = value.subarray(start)\n return tlv(0x02, (bytes[0] ?? 0) & 0x80 ? Uint8Array.from([0, ...bytes]) : bytes)\n}\n\nconst utcTime = (date: Date) => {\n const two = (n: number) => String(n).padStart(2, \"0\")\n return tlv(\n 0x17,\n bytes(\n `${two(date.getUTCFullYear() % 100)}${two(date.getUTCMonth() + 1)}${two(date.getUTCDate())}` +\n `${two(date.getUTCHours())}${two(date.getUTCMinutes())}${two(date.getUTCSeconds())}Z`,\n ),\n )\n}\n\nconst name = (commonName: string) =>\n sequence(tlv(0x31, sequence(oid(\"2.5.4.3\"), tlv(0x0c, bytes(commonName)))))\n\nconst ipBytes = (ip: string) => Uint8Array.from(ip.split(\".\").map(Number))\n\nconst pem = (label: string, der: Uint8Array) =>\n `-----BEGIN ${label}-----\\n${(\n Buffer.from(der)\n .toString(\"base64\")\n .match(/.{1,64}/g) ?? []\n ).join(\"\\n\")}\\n-----END ${label}-----\\n`\n\n/** A fresh certificate valid from a day ago for ten years, for `hosts` (names or IPv4). */\nexport const selfSignedCertificate = (\n hosts: readonly string[] = [\"localhost\", \"127.0.0.1\"],\n): SelfSignedCertificate => {\n const { privateKey, publicKey } = generateKeyPairSync(\"ec\", { namedCurve: \"prime256v1\" })\n const algorithm = sequence(oid(\"1.2.840.10045.4.3.2\"))\n const now = Date.now()\n const altNames = sequence(\n ...hosts.map((host) =>\n /^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(host) ? tlv(0x87, ipBytes(host)) : tlv(0x82, bytes(host)),\n ),\n )\n const extensions = tlv(\n 0xa3,\n sequence(\n sequence(oid(\"2.5.29.19\"), tlv(0x04, sequence(tlv(0x01, Uint8Array.from([0xff]))))),\n sequence(oid(\"2.5.29.17\"), tlv(0x04, altNames)),\n ),\n )\n const serial = randomBytes(16)\n serial[0] = (serial[0] ?? 0) & 0x7f\n const tbs = sequence(\n tlv(0xa0, integer(Uint8Array.from([2]))),\n integer(serial),\n algorithm,\n name(hosts[0] ?? \"localhost\"),\n sequence(utcTime(new Date(now - 86_400_000)), utcTime(new Date(now + 3_650 * 86_400_000))),\n name(hosts[0] ?? \"localhost\"),\n new Uint8Array(publicKey.export({ type: \"spki\", format: \"der\" })),\n extensions,\n )\n const signature = sign(\"sha256\", tbs, privateKey)\n const certificate = sequence(tbs, algorithm, tlv(0x03, Uint8Array.from([0]), signature))\n return {\n cert: pem(\"CERTIFICATE\", certificate),\n key: privateKey.export({ type: \"pkcs8\", format: \"pem\" }).toString(),\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["readFile", "name", "start", "bytes", "createServer", "readFile"]
7
+ }