@crvouga/mockingbird-service-junction 0.1.2 → 0.2.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.
- package/CHANGELOG.md +31 -0
- package/README.md +447 -144
- package/dist/chunk-4C6VYRJN.js +18 -0
- package/dist/chunk-4C6VYRJN.js.map +7 -0
- package/dist/chunk-KKNULV7X.js +406 -0
- package/dist/chunk-KKNULV7X.js.map +7 -0
- package/dist/chunk-MA3HSUVD.js +7771 -0
- package/dist/chunk-MA3HSUVD.js.map +7 -0
- package/dist/chunk-MD4KYEIM.js +145758 -0
- package/dist/chunk-MD4KYEIM.js.map +7 -0
- package/dist/cli.js +214 -0
- package/dist/cli.js.map +7 -0
- package/dist/corpus.d.ts +97 -0
- package/dist/corpus.js +8 -0
- package/dist/corpus.js.map +7 -0
- package/dist/index.d.ts +641 -2
- package/dist/index.js +65 -6082
- package/dist/index.js.map +4 -4
- package/dist/server.d.ts +978 -0
- package/dist/server.js +16 -0
- package/dist/server.js.map +7 -0
- package/package.json +23 -4
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/sealed-corpus.ts
|
|
2
|
+
var SEALED_CORPUS_VERSION = 1;
|
|
3
|
+
var parseSealedCorpus = (value) => {
|
|
4
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5
|
+
throw new Error("sealed corpus must be an object");
|
|
6
|
+
}
|
|
7
|
+
const record = value;
|
|
8
|
+
if (record.version !== SEALED_CORPUS_VERSION) {
|
|
9
|
+
throw new Error(`unsupported sealed corpus version: ${String(record.version)}`);
|
|
10
|
+
}
|
|
11
|
+
return record;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
SEALED_CORPUS_VERSION,
|
|
16
|
+
parseSealedCorpus
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=chunk-4C6VYRJN.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/sealed-corpus.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ExpectedResult, LabTestRecord } from \"./catalog.js\"\nimport type { GetCacheEntry } from \"./state.js\"\n\n/**\n * Version of the sealed-corpus on-disk format. Bumped whenever the shape changes so a\n * stale recording is rejected instead of half-applied.\n */\nexport const SEALED_CORPUS_VERSION = 1\n\n/**\n * Exact sandbox recording of the provider-owned reads a drop-in consumer depends on:\n * parameter-stable GET observations (area/PSC inventory), the lab catalog, team labs, and\n * lab accounts. Availability is intentionally absent \u2014 its slot dates and single-use\n * `booking_key`s can never match a live cache key, so it stays with the generator.\n */\nexport type SealedCorpus = {\n version: typeof SEALED_CORPUS_VERSION\n recordedAt: string\n source: string\n /** SHA-256 of the recording's content, written by `corpus pull`. Identifies it in logs. */\n fingerprint?: string\n observations: Record<string, GetCacheEntry>\n catalog: {\n labTests: LabTestRecord[]\n labs: Record<string, unknown>[]\n expectedResults: Record<string, ExpectedResult[]>\n }\n labAccounts: Record<string, unknown>[]\n}\n\n/** Throws on a non-object or an unrecognized `version`. */\nexport const parseSealedCorpus = (value: unknown): SealedCorpus => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(\"sealed corpus must be an object\")\n }\n const record = value as Record<string, unknown>\n if (record.version !== SEALED_CORPUS_VERSION) {\n throw new Error(`unsupported sealed corpus version: ${String(record.version)}`)\n }\n return record as unknown as SealedCorpus\n}\n"],
|
|
5
|
+
"mappings": ";AAOO,IAAM,wBAAwB;AAwB9B,IAAM,oBAAoB,CAAC,UAAiC;AACjE,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GAAG;AACvE,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACnD;AACA,QAAM,SAAS;AACf,MAAI,OAAO,YAAY,uBAAuB;AAC5C,UAAM,IAAI,MAAM,sCAAsC,OAAO,OAAO,OAAO,CAAC,EAAE;AAAA,EAChF;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRuntime,
|
|
3
|
+
pullCorpus
|
|
4
|
+
} from "./chunk-MA3HSUVD.js";
|
|
5
|
+
import {
|
|
6
|
+
defaultCorpus
|
|
7
|
+
} from "./chunk-MD4KYEIM.js";
|
|
8
|
+
import {
|
|
9
|
+
parseSealedCorpus
|
|
10
|
+
} from "./chunk-4C6VYRJN.js";
|
|
11
|
+
|
|
12
|
+
// src/server.ts
|
|
13
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
14
|
+
|
|
15
|
+
// ../../adapters/node/dist/cli.js
|
|
16
|
+
import { readFile } from "node:fs/promises";
|
|
17
|
+
import { parseArgs } from "node:util";
|
|
18
|
+
|
|
19
|
+
// ../../adapters/node/dist/serve.js
|
|
20
|
+
import { createServer } from "node:http";
|
|
21
|
+
var serve = async (api, options = {}) => {
|
|
22
|
+
const server = createServer(async (req, res) => {
|
|
23
|
+
const chunks = [];
|
|
24
|
+
for await (const chunk of req) {
|
|
25
|
+
chunks.push(Buffer.from(chunk));
|
|
26
|
+
}
|
|
27
|
+
const body = Buffer.concat(chunks);
|
|
28
|
+
const address = server.address();
|
|
29
|
+
const port = typeof address === "object" && address !== null ? address.port : void 0;
|
|
30
|
+
const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`;
|
|
31
|
+
const raw = req.url ?? "/";
|
|
32
|
+
const url = new URL(raw.replace(/^\/+/, "/"), base);
|
|
33
|
+
const method = req.method ?? "GET";
|
|
34
|
+
const init = { method, headers: req.headers };
|
|
35
|
+
if (method !== "GET" && method !== "HEAD" && body.length > 0) {
|
|
36
|
+
init.body = body;
|
|
37
|
+
}
|
|
38
|
+
const request = new Request(url, init);
|
|
39
|
+
const response = await api.fetch(request);
|
|
40
|
+
const headers = Object.fromEntries(response.headers);
|
|
41
|
+
const cookies = response.headers.getSetCookie();
|
|
42
|
+
if (cookies.length > 0)
|
|
43
|
+
headers["set-cookie"] = cookies;
|
|
44
|
+
res.writeHead(response.status, headers);
|
|
45
|
+
res.end(Buffer.from(await response.arrayBuffer()));
|
|
46
|
+
});
|
|
47
|
+
await new Promise((resolve, reject) => {
|
|
48
|
+
server.once("error", reject);
|
|
49
|
+
server.listen(options.port ?? 0, options.host, resolve);
|
|
50
|
+
});
|
|
51
|
+
return server;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ../../adapters/node/dist/listen.js
|
|
55
|
+
var listen = async (api, options = {}) => {
|
|
56
|
+
const host = options.host ?? "127.0.0.1";
|
|
57
|
+
const server = await serve(api, { port: options.port ?? 0, host });
|
|
58
|
+
const address = server.address();
|
|
59
|
+
const port = typeof address === "object" && address !== null ? address.port : options.port ?? 0;
|
|
60
|
+
const shown = host.includes(":") ? `[${host}]` : host;
|
|
61
|
+
return {
|
|
62
|
+
url: `http://${shown}:${port}`,
|
|
63
|
+
port,
|
|
64
|
+
host,
|
|
65
|
+
server,
|
|
66
|
+
close: () => new Promise((resolve, reject) => {
|
|
67
|
+
server.close((error) => {
|
|
68
|
+
if (error && error.code !== "ERR_SERVER_NOT_RUNNING")
|
|
69
|
+
reject(error);
|
|
70
|
+
else
|
|
71
|
+
resolve();
|
|
72
|
+
});
|
|
73
|
+
server.closeAllConnections?.();
|
|
74
|
+
})
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ../../adapters/node/dist/cli.js
|
|
79
|
+
var optionHelp = (options) => Object.entries(options).map(([name, option]) => {
|
|
80
|
+
const flag = `--${name}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
|
|
81
|
+
const fallback = option.default !== void 0 ? ` (default: ${String(option.default)})` : "";
|
|
82
|
+
return ` ${flag.padEnd(30)} ${option.description}${fallback}`;
|
|
83
|
+
});
|
|
84
|
+
var help = (spec) => [
|
|
85
|
+
`${spec.bin} \u2014 ${spec.description}`,
|
|
86
|
+
"",
|
|
87
|
+
"Usage:",
|
|
88
|
+
` ${spec.bin} <command> [options]`,
|
|
89
|
+
"",
|
|
90
|
+
"Commands:",
|
|
91
|
+
...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),
|
|
92
|
+
"",
|
|
93
|
+
`Run \`${spec.bin} <command> --help\` for a command's options.`
|
|
94
|
+
].join("\n");
|
|
95
|
+
var commandHelp = (spec, name, command) => [
|
|
96
|
+
`${spec.bin} ${name} \u2014 ${command.summary}`,
|
|
97
|
+
"",
|
|
98
|
+
"Usage:",
|
|
99
|
+
` ${command.usage ?? `${spec.bin} ${name} [options]`}`,
|
|
100
|
+
...command.options ? ["", "Options:", ...optionHelp(command.options)] : []
|
|
101
|
+
].join("\n");
|
|
102
|
+
var runCli = async (spec, argv) => {
|
|
103
|
+
const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : void 0;
|
|
104
|
+
const words = pair !== void 0 && spec.commands[pair] ? 2 : 1;
|
|
105
|
+
const name = words === 2 ? pair : argv[0];
|
|
106
|
+
const rest = argv.slice(words);
|
|
107
|
+
if (name === void 0 || name === "--help" || name === "-h" || name === "help") {
|
|
108
|
+
console.log(help(spec));
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
const command = spec.commands[name];
|
|
112
|
+
if (!command) {
|
|
113
|
+
console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}
|
|
114
|
+
|
|
115
|
+
${help(spec)}`);
|
|
116
|
+
return 2;
|
|
117
|
+
}
|
|
118
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
119
|
+
console.log(commandHelp(spec, name, command));
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
let parsed;
|
|
123
|
+
try {
|
|
124
|
+
parsed = parseArgs({
|
|
125
|
+
args: rest,
|
|
126
|
+
allowPositionals: true,
|
|
127
|
+
strict: true,
|
|
128
|
+
options: Object.fromEntries(Object.entries(command.options ?? {}).map(([key, option]) => [
|
|
129
|
+
key,
|
|
130
|
+
{
|
|
131
|
+
type: option.type,
|
|
132
|
+
...option.default !== void 0 ? { default: option.default } : {}
|
|
133
|
+
}
|
|
134
|
+
]))
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(`${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
138
|
+
|
|
139
|
+
${commandHelp(spec, name, command)}`);
|
|
140
|
+
return 2;
|
|
141
|
+
}
|
|
142
|
+
return command.run(parsed.values, parsed.positionals);
|
|
143
|
+
};
|
|
144
|
+
var COMMON_SERVE_OPTIONS = {
|
|
145
|
+
port: { type: "string", value: "<port>", description: "Port to listen on" },
|
|
146
|
+
host: { type: "string", value: "<host>", description: "Interface to bind", default: "127.0.0.1" },
|
|
147
|
+
"admin-key": {
|
|
148
|
+
type: "string",
|
|
149
|
+
value: "<key>",
|
|
150
|
+
description: "Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)"
|
|
151
|
+
},
|
|
152
|
+
seed: { type: "string", value: "<seed>", description: "Seed for every random choice" },
|
|
153
|
+
log: {
|
|
154
|
+
type: "string",
|
|
155
|
+
value: "<pretty|json|off>",
|
|
156
|
+
description: "Request log format",
|
|
157
|
+
default: "pretty"
|
|
158
|
+
},
|
|
159
|
+
config: {
|
|
160
|
+
type: "string",
|
|
161
|
+
value: "<file>",
|
|
162
|
+
description: "Serve every service in a mockingbird.json config instead"
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var formatLog = (format) => {
|
|
166
|
+
if (format === "off")
|
|
167
|
+
return void 0;
|
|
168
|
+
if (format === "json")
|
|
169
|
+
return (entry) => console.log(JSON.stringify(entry));
|
|
170
|
+
return (entry) => {
|
|
171
|
+
const op = entry.operationId ?? (entry.unmatched ? "UNMATCHED" : "-");
|
|
172
|
+
const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
|
|
173
|
+
const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
|
|
174
|
+
console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}`);
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
178
|
+
var loadTarget = async (name, own) => {
|
|
179
|
+
if (name === own.name)
|
|
180
|
+
return own;
|
|
181
|
+
const specifier = `@crvouga/mockingbird-service-${name}/server`;
|
|
182
|
+
try {
|
|
183
|
+
const mod = await import(specifier);
|
|
184
|
+
if (!mod.serveTarget)
|
|
185
|
+
throw new Error(`${specifier} exports no serveTarget`);
|
|
186
|
+
return mod.serveTarget;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
189
|
+
throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
var start = async (target, values, config) => {
|
|
193
|
+
const runtime = await target.create(values, {
|
|
194
|
+
adminKey: config.adminKey,
|
|
195
|
+
seed: config.seed,
|
|
196
|
+
onLog: formatLog(config.log)
|
|
197
|
+
});
|
|
198
|
+
const listening = await listen(runtime, { port: config.port, host: config.host });
|
|
199
|
+
console.log(`${target.name} mock listening on ${listening.url}`);
|
|
200
|
+
console.log(`${target.name} health: GET ${listening.url}/health`);
|
|
201
|
+
console.log(`${target.name} admin: ${listening.url}/__admin (${config.adminKey ? "x-mockingbird-admin-key required" : "open \u2014 pass --admin-key to lock"})`);
|
|
202
|
+
for (const line of target.banner?.(runtime) ?? [])
|
|
203
|
+
console.log(`${target.name} ${line}`);
|
|
204
|
+
return listening;
|
|
205
|
+
};
|
|
206
|
+
var untilSignal = async (servers) => new Promise((resolve) => {
|
|
207
|
+
const stop = async () => {
|
|
208
|
+
await Promise.allSettled(servers.map((s) => s.close()));
|
|
209
|
+
resolve(0);
|
|
210
|
+
};
|
|
211
|
+
process.once("SIGINT", stop);
|
|
212
|
+
process.once("SIGTERM", stop);
|
|
213
|
+
});
|
|
214
|
+
var serveCommand = (target) => ({
|
|
215
|
+
summary: `Serve the ${target.name} mock over HTTP`,
|
|
216
|
+
options: { ...COMMON_SERVE_OPTIONS, ...target.options },
|
|
217
|
+
async run(values) {
|
|
218
|
+
const log = asString(values.log) ?? "pretty";
|
|
219
|
+
if (!["pretty", "json", "off"].includes(log)) {
|
|
220
|
+
console.error(`--log must be pretty, json or off (got ${log})`);
|
|
221
|
+
return 2;
|
|
222
|
+
}
|
|
223
|
+
const configPath = asString(values.config);
|
|
224
|
+
if (configPath !== void 0) {
|
|
225
|
+
const config = JSON.parse(await readFile(configPath, "utf8"));
|
|
226
|
+
const servers = [];
|
|
227
|
+
try {
|
|
228
|
+
for (const [name, entry] of Object.entries(config.services ?? {})) {
|
|
229
|
+
const each = await loadTarget(name, target);
|
|
230
|
+
servers.push(await start(each, entry.options ?? {}, {
|
|
231
|
+
port: entry.port ?? each.defaultPort,
|
|
232
|
+
host: entry.host ?? "127.0.0.1",
|
|
233
|
+
...entry.adminKey !== void 0 ? { adminKey: entry.adminKey } : {},
|
|
234
|
+
...entry.seed !== void 0 ? { seed: entry.seed } : {},
|
|
235
|
+
log: config.log ?? log
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
} catch (error) {
|
|
239
|
+
await Promise.allSettled(servers.map((s) => s.close()));
|
|
240
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
241
|
+
return 1;
|
|
242
|
+
}
|
|
243
|
+
return untilSignal(servers);
|
|
244
|
+
}
|
|
245
|
+
const port = asString(values.port);
|
|
246
|
+
const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
247
|
+
const seed = asString(values.seed);
|
|
248
|
+
let listening;
|
|
249
|
+
try {
|
|
250
|
+
listening = await start(target, values, {
|
|
251
|
+
port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
|
|
252
|
+
host: asString(values.host) ?? "127.0.0.1",
|
|
253
|
+
...adminKey !== void 0 ? { adminKey } : {},
|
|
254
|
+
...seed !== void 0 ? { seed } : {},
|
|
255
|
+
log
|
|
256
|
+
});
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
return untilSignal([listening]);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// src/server.ts
|
|
266
|
+
var DEFAULT_PORT = 8787;
|
|
267
|
+
var loadCorpus = async (source) => {
|
|
268
|
+
if (typeof source !== "string") return source;
|
|
269
|
+
if (source === "none") return void 0;
|
|
270
|
+
if (source === "default") return defaultCorpus;
|
|
271
|
+
const raw = await readFile2(source, "utf8").catch(() => void 0);
|
|
272
|
+
if (raw === void 0) throw new Error(`junction mock corpus not found: ${source}`);
|
|
273
|
+
return parseSealedCorpus(JSON.parse(raw));
|
|
274
|
+
};
|
|
275
|
+
var createServer2 = async (options = {}) => {
|
|
276
|
+
const { port, host, corpus: source, ...rest } = options;
|
|
277
|
+
const corpus = await loadCorpus(source ?? "default");
|
|
278
|
+
const runtime = createRuntime({ ...rest, ...corpus ? { corpus } : {} });
|
|
279
|
+
const listening = await listen(runtime, {
|
|
280
|
+
port: port ?? 0,
|
|
281
|
+
...host !== void 0 ? { host } : {}
|
|
282
|
+
});
|
|
283
|
+
return { ...listening, runtime };
|
|
284
|
+
};
|
|
285
|
+
var asString2 = (value) => typeof value === "string" && value !== "" ? value : void 0;
|
|
286
|
+
var retryDelays = (value) => {
|
|
287
|
+
if (value === void 0) return void 0;
|
|
288
|
+
const delays = value.split(",").map((part) => Number.parseInt(part.trim(), 10));
|
|
289
|
+
if (delays.some((delay) => !Number.isFinite(delay) || delay < 0)) {
|
|
290
|
+
throw new Error(`--webhook-retry-delays must be comma-separated milliseconds (got ${value})`);
|
|
291
|
+
}
|
|
292
|
+
return delays;
|
|
293
|
+
};
|
|
294
|
+
var serveTarget = {
|
|
295
|
+
name: "junction",
|
|
296
|
+
defaultPort: DEFAULT_PORT,
|
|
297
|
+
options: {
|
|
298
|
+
corpus: {
|
|
299
|
+
type: "string",
|
|
300
|
+
value: "<default|none|file>",
|
|
301
|
+
description: "Corpus to serve: the shipped one, none (synthetic), or a pulled file",
|
|
302
|
+
default: "default"
|
|
303
|
+
},
|
|
304
|
+
geo: {
|
|
305
|
+
type: "string",
|
|
306
|
+
value: "<corpus|synthetic>",
|
|
307
|
+
description: "Unknown ZIPs: refuse (corpus) or invent coverage (synthetic)"
|
|
308
|
+
},
|
|
309
|
+
"lab-accounts": {
|
|
310
|
+
type: "string",
|
|
311
|
+
value: "<file>",
|
|
312
|
+
description: "JSON array of lab accounts to route orders through"
|
|
313
|
+
},
|
|
314
|
+
"seed-url": {
|
|
315
|
+
type: "string",
|
|
316
|
+
value: "<url>",
|
|
317
|
+
description: "Pull a corpus from this Junction team at boot (prefer a committed corpus)"
|
|
318
|
+
},
|
|
319
|
+
"seed-key": {
|
|
320
|
+
type: "string",
|
|
321
|
+
value: "<key>",
|
|
322
|
+
description: "API key for --seed-url (env MOCKINGBIRD_JUNCTION_SEED_KEY)"
|
|
323
|
+
},
|
|
324
|
+
"webhook-url": { type: "string", value: "<url>", description: "Deliver signed webhooks here" },
|
|
325
|
+
"webhook-secret": {
|
|
326
|
+
type: "string",
|
|
327
|
+
value: "<whsec_\u2026>",
|
|
328
|
+
description: "Svix signing secret (env MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET)"
|
|
329
|
+
},
|
|
330
|
+
"webhook-retry-delays": {
|
|
331
|
+
type: "string",
|
|
332
|
+
value: "<ms,ms,\u2026>",
|
|
333
|
+
description: "Delay before each delivery attempt (default: Svix's schedule)"
|
|
334
|
+
},
|
|
335
|
+
"webhook-scope": {
|
|
336
|
+
type: "string",
|
|
337
|
+
value: "<scope>",
|
|
338
|
+
description: "Sent as x-mockingbird-scope on every delivery"
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
async create(values, common) {
|
|
342
|
+
const seedUrl = asString2(values["seed-url"]);
|
|
343
|
+
const seedKey = asString2(values["seed-key"]) ?? process.env.MOCKINGBIRD_JUNCTION_SEED_KEY;
|
|
344
|
+
let corpus;
|
|
345
|
+
if (seedUrl !== void 0) {
|
|
346
|
+
if (seedKey === void 0) throw new Error("--seed-url needs --seed-key");
|
|
347
|
+
console.log(
|
|
348
|
+
`junction pulling a corpus from ${seedUrl} (commit one with \`corpus pull\` to skip this)`
|
|
349
|
+
);
|
|
350
|
+
corpus = await pullCorpus({ baseUrl: seedUrl, apiKey: seedKey });
|
|
351
|
+
} else {
|
|
352
|
+
corpus = await loadCorpus(asString2(values.corpus) ?? "default");
|
|
353
|
+
}
|
|
354
|
+
const geo = asString2(values.geo);
|
|
355
|
+
if (geo !== void 0 && geo !== "corpus" && geo !== "synthetic") {
|
|
356
|
+
throw new Error(`--geo must be corpus or synthetic (got ${geo})`);
|
|
357
|
+
}
|
|
358
|
+
const accountsFile = asString2(values["lab-accounts"]);
|
|
359
|
+
const labAccounts = accountsFile === void 0 ? void 0 : JSON.parse(await readFile2(accountsFile, "utf8"));
|
|
360
|
+
const webhookUrl = asString2(values["webhook-url"]);
|
|
361
|
+
const webhookSecret = asString2(values["webhook-secret"]) ?? process.env.MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET;
|
|
362
|
+
if (webhookUrl !== void 0 && webhookSecret === void 0) {
|
|
363
|
+
throw new Error("--webhook-url needs --webhook-secret (the whsec_\u2026 your receiver verifies)");
|
|
364
|
+
}
|
|
365
|
+
const delays = retryDelays(asString2(values["webhook-retry-delays"]));
|
|
366
|
+
const scope = asString2(values["webhook-scope"]);
|
|
367
|
+
return createRuntime({
|
|
368
|
+
...corpus ? { corpus } : {},
|
|
369
|
+
...geo ? { geo } : {},
|
|
370
|
+
...labAccounts ? { labAccounts } : {},
|
|
371
|
+
...webhookUrl !== void 0 && webhookSecret !== void 0 ? {
|
|
372
|
+
webhooks: {
|
|
373
|
+
url: webhookUrl,
|
|
374
|
+
secret: webhookSecret,
|
|
375
|
+
...delays ? { retryDelaysMs: delays } : {},
|
|
376
|
+
...scope ? { headers: { "x-mockingbird-scope": scope } } : {}
|
|
377
|
+
}
|
|
378
|
+
} : {},
|
|
379
|
+
...common.adminKey !== void 0 ? { adminKey: common.adminKey } : {},
|
|
380
|
+
...common.seed !== void 0 ? { seed: common.seed } : {},
|
|
381
|
+
...common.onLog ? { onLog: common.onLog } : {}
|
|
382
|
+
});
|
|
383
|
+
},
|
|
384
|
+
banner(runtime) {
|
|
385
|
+
const junction = runtime;
|
|
386
|
+
const api = junction.instance();
|
|
387
|
+
const info = api.corpusInfo();
|
|
388
|
+
return [
|
|
389
|
+
info ? `corpus ${info.label}: ${info.observations} observations, ${info.zips} ZIPs, ${info.labTests} lab tests, ${info.labAccounts} lab accounts (recorded ${info.recordedAt} from ${info.source})` : "corpus none: synthetic catalog and coverage",
|
|
390
|
+
`geo ${api.geoMode}${api.geoMode === "corpus" ? " (unknown ZIPs answer 424 MOCKINGBIRD_UNKNOWN_ZIP)" : ""}`,
|
|
391
|
+
`lab accounts: ${api.labAccounts().length}`,
|
|
392
|
+
`webhooks: ${junction.webhooks ? "signed delivery on" : "off"}`,
|
|
393
|
+
"auth: any x-vital-api-key value (e.g. sk_us_mockingbird)"
|
|
394
|
+
];
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
export {
|
|
399
|
+
runCli,
|
|
400
|
+
serveCommand,
|
|
401
|
+
DEFAULT_PORT,
|
|
402
|
+
loadCorpus,
|
|
403
|
+
createServer2 as createServer,
|
|
404
|
+
serveTarget
|
|
405
|
+
};
|
|
406
|
+
//# sourceMappingURL=chunk-KKNULV7X.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"],
|
|
4
|
+
"sourcesContent": ["/// <reference types=\"node\" />\nimport { readFile } from \"node:fs/promises\"\nimport {\n type CliValues,\n type Listening,\n listen,\n type ServeTarget,\n} from \"@crvouga/mockingbird-adapter-node\"\nimport { defaultCorpus } from \"./corpus.js\"\nimport { pullCorpus } from \"./corpus-tools.js\"\nimport type { LabAccountInput } from \"./lab-accounts.js\"\nimport { createRuntime, type JunctionRuntime, type JunctionRuntimeOptions } from \"./runtime.js\"\nimport { parseSealedCorpus, type SealedCorpus } from \"./sealed-corpus.js\"\nimport type { GeoMode } from \"./state.js\"\n\n/** Port `mockingbird-junction serve` listens on when none is given. */\nexport const DEFAULT_PORT = 8787\n\n/** A corpus to serve: the shipped one, none (synthetic), a file path, or a loaded corpus. */\nexport type CorpusSource = \"default\" | \"none\" | (string & {}) | SealedCorpus\n\nexport type JunctionServerOptions = Omit<JunctionRuntimeOptions, \"corpus\"> & {\n /** Default `0`: the OS picks a free port (read it from `url` / `port`). */\n port?: number\n /** Default `127.0.0.1`. */\n host?: string\n /** Default `\"default\"`: the corpus shipped with this package. */\n corpus?: CorpusSource\n}\n\nexport type JunctionServer = Listening & { runtime: JunctionRuntime }\n\n/** Resolve a {@link CorpusSource} to a corpus, or `undefined` for synthetic data. */\nexport const loadCorpus = async (source: CorpusSource): Promise<SealedCorpus | undefined> => {\n if (typeof source !== \"string\") return source\n if (source === \"none\") return undefined\n if (source === \"default\") return defaultCorpus\n const raw = await readFile(source, \"utf8\").catch(() => undefined)\n if (raw === undefined) throw new Error(`junction mock corpus not found: ${source}`)\n return parseSealedCorpus(JSON.parse(raw))\n}\n\n/**\n * Serve the Junction mock over `node:http`. Resolves once it is listening.\n *\n * const server = await createServer()\n * // point the Vital SDK at server.url; await server.close() when done\n */\nexport const createServer = async (\n options: JunctionServerOptions = {},\n): Promise<JunctionServer> => {\n const { port, host, corpus: source, ...rest } = options\n const corpus = await loadCorpus(source ?? \"default\")\n const runtime = createRuntime({ ...rest, ...(corpus ? { corpus } : {}) })\n const listening = await listen(runtime, {\n port: port ?? 0,\n ...(host !== undefined ? { host } : {}),\n })\n return { ...listening, runtime }\n}\n\nconst asString = (value: string | boolean | undefined): string | undefined =>\n typeof value === \"string\" && value !== \"\" ? value : undefined\n\nconst retryDelays = (value: string | undefined): number[] | undefined => {\n if (value === undefined) return undefined\n const delays = value.split(\",\").map((part) => Number.parseInt(part.trim(), 10))\n if (delays.some((delay) => !Number.isFinite(delay) || delay < 0)) {\n throw new Error(`--webhook-retry-delays must be comma-separated milliseconds (got ${value})`)\n }\n return delays\n}\n\n/** How `serve` (and `serve --config`) builds the Junction mock from flags. */\nexport const serveTarget: ServeTarget = {\n name: \"junction\",\n defaultPort: DEFAULT_PORT,\n options: {\n corpus: {\n type: \"string\",\n value: \"<default|none|file>\",\n description: \"Corpus to serve: the shipped one, none (synthetic), or a pulled file\",\n default: \"default\",\n },\n geo: {\n type: \"string\",\n value: \"<corpus|synthetic>\",\n description: \"Unknown ZIPs: refuse (corpus) or invent coverage (synthetic)\",\n },\n \"lab-accounts\": {\n type: \"string\",\n value: \"<file>\",\n description: \"JSON array of lab accounts to route orders through\",\n },\n \"seed-url\": {\n type: \"string\",\n value: \"<url>\",\n description: \"Pull a corpus from this Junction team at boot (prefer a committed corpus)\",\n },\n \"seed-key\": {\n type: \"string\",\n value: \"<key>\",\n description: \"API key for --seed-url (env MOCKINGBIRD_JUNCTION_SEED_KEY)\",\n },\n \"webhook-url\": { type: \"string\", value: \"<url>\", description: \"Deliver signed webhooks here\" },\n \"webhook-secret\": {\n type: \"string\",\n value: \"<whsec_\u2026>\",\n description: \"Svix signing secret (env MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET)\",\n },\n \"webhook-retry-delays\": {\n type: \"string\",\n value: \"<ms,ms,\u2026>\",\n description: \"Delay before each delivery attempt (default: Svix's schedule)\",\n },\n \"webhook-scope\": {\n type: \"string\",\n value: \"<scope>\",\n description: \"Sent as x-mockingbird-scope on every delivery\",\n },\n },\n async create(values: CliValues, common) {\n const seedUrl = asString(values[\"seed-url\"])\n const seedKey = asString(values[\"seed-key\"]) ?? process.env.MOCKINGBIRD_JUNCTION_SEED_KEY\n let corpus: SealedCorpus | undefined\n if (seedUrl !== undefined) {\n if (seedKey === undefined) throw new Error(\"--seed-url needs --seed-key\")\n console.log(\n `junction pulling a corpus from ${seedUrl} (commit one with \\`corpus pull\\` to skip this)`,\n )\n corpus = await pullCorpus({ baseUrl: seedUrl, apiKey: seedKey })\n } else {\n corpus = await loadCorpus(asString(values.corpus) ?? \"default\")\n }\n const geo = asString(values.geo)\n if (geo !== undefined && geo !== \"corpus\" && geo !== \"synthetic\") {\n throw new Error(`--geo must be corpus or synthetic (got ${geo})`)\n }\n const accountsFile = asString(values[\"lab-accounts\"])\n const labAccounts =\n accountsFile === undefined\n ? undefined\n : (JSON.parse(await readFile(accountsFile, \"utf8\")) as LabAccountInput[])\n const webhookUrl = asString(values[\"webhook-url\"])\n const webhookSecret =\n asString(values[\"webhook-secret\"]) ?? process.env.MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET\n if (webhookUrl !== undefined && webhookSecret === undefined) {\n throw new Error(\"--webhook-url needs --webhook-secret (the whsec_\u2026 your receiver verifies)\")\n }\n const delays = retryDelays(asString(values[\"webhook-retry-delays\"]))\n const scope = asString(values[\"webhook-scope\"])\n return createRuntime({\n ...(corpus ? { corpus } : {}),\n ...(geo ? { geo: geo as GeoMode } : {}),\n ...(labAccounts ? { labAccounts } : {}),\n ...(webhookUrl !== undefined && webhookSecret !== undefined\n ? {\n webhooks: {\n url: webhookUrl,\n secret: webhookSecret,\n ...(delays ? { retryDelaysMs: delays } : {}),\n ...(scope ? { headers: { \"x-mockingbird-scope\": scope } } : {}),\n },\n }\n : {}),\n ...(common.adminKey !== undefined ? { adminKey: common.adminKey } : {}),\n ...(common.seed !== undefined ? { seed: common.seed } : {}),\n ...(common.onLog ? { onLog: common.onLog } : {}),\n })\n },\n banner(runtime) {\n const junction = runtime as JunctionRuntime\n const api = junction.instance()\n const info = api.corpusInfo()\n return [\n info\n ? `corpus ${info.label}: ${info.observations} observations, ${info.zips} ZIPs, ${info.labTests} lab tests, ${info.labAccounts} lab accounts (recorded ${info.recordedAt} from ${info.source})`\n : \"corpus none: synthetic catalog and coverage\",\n `geo ${api.geoMode}${api.geoMode === \"corpus\" ? \" (unknown ZIPs answer 424 MOCKINGBIRD_UNKNOWN_ZIP)\" : \"\"}`,\n `lab accounts: ${api.labAccounts().length}`,\n `webhooks: ${junction.webhooks ? \"signed delivery on\" : \"off\"}`,\n \"auth: any x-vital-api-key value (e.g. sk_us_mockingbird)\",\n ]\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 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 console.log(\n `${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}`,\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 = (asString(values.log) ?? \"pretty\") 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 request = new Request(url, init)\n const response = await api.fetch(request)\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 res.writeHead(response.status, headers)\n res.end(Buffer.from(await response.arrayBuffer()))\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"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["readFile", "readFile", "createServer", "asString"]
|
|
7
|
+
}
|