@crvouga/mockingbird-service-twilio 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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog — @crvouga/mockingbird-service-twilio
2
+
3
+ ## 0.1.0 (2026-09-22)
4
+
5
+ Initial release.
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # @crvouga/mockingbird-service-twilio
2
+
3
+ Stateful mock of **Twilio** for test suites: Lookup v2 phone validation, Verify v2 phone OTP
4
+ (with the real state machine: wrong codes, attempt limits, 10-minute expiry on the mock clock),
5
+ Programmable Messaging with an outbox, and call Recordings. It also signs and posts Twilio's
6
+ inbound SMS and voice webhooks to your app. Every product is served on one port. A suite reads the
7
+ OTP from the admin plane instead of bypassing verification (`E2E_OTP_BYPASS_*`).
8
+
9
+ - Operation coverage: [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/twilio/SUPPORT.md)
10
+ - The contract (`openapi.yaml`) is trimmed from Twilio's API reference to what our consumer
11
+ calls. Lookup v2 responses are checked byte for byte against the live API
12
+ (`scripts/parity.ts`, recorded in `test/fixtures/lookups.live.json`).
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install -D @crvouga/mockingbird-service-twilio
18
+ ```
19
+
20
+ ESM only. Node >= 22 or Bun >= 1.2. Lookup validation uses `libphonenumber-js` (Twilio's
21
+ Lookup is libphonenumber). Serve it with `npx mockingbird-twilio serve`, with `createServer`
22
+ from `./server` (Node), or with `createRuntime` in any Fetch server.
23
+
24
+ ## Usage
25
+
26
+ ```bash
27
+ npx mockingbird-twilio serve --port 8798 \
28
+ --app-url http://127.0.0.1:3000 \
29
+ --public-base-url "$TWILIO_VOICE_WEBHOOK_BASE_URL" \
30
+ --account-sid "$TWILIO_ACCOUNT_SID" --auth-token "$TWILIO_AUTH_TOKEN" \
31
+ --caller-id "$TWILIO_VOICE_CALLER_ID"
32
+ ```
33
+
34
+ ### Pointing the app at it (G-T1)
35
+
36
+ twilio-node builds a host for each product (`api.twilio.com`, `verify.twilio.com`,
37
+ `lookups.twilio.com`). The mock takes that host as the first path segment:
38
+ `{mock}/api/2010-04-01/…`, `{mock}/verify/v2/…`, `{mock}/lookups/v2/…`. Pass every
39
+ `new Twilio(...)` an `httpClient` that rewrites the URL. `twilioMockUrl` does the rewrite:
40
+
41
+ ```js
42
+ import { RequestClient, Twilio } from "twilio"
43
+ import { twilioMockUrl } from "@crvouga/mockingbird-service-twilio"
44
+
45
+ /** https://verify.twilio.com/v2/Services/VA…/Verifications → {base}/verify/v2/Services/VA…/Verifications */
46
+ class MockRequestClient extends RequestClient {
47
+ constructor(baseUrl) {
48
+ super()
49
+ this.baseUrl = baseUrl
50
+ }
51
+ request(opts) {
52
+ return super.request({ ...opts, uri: twilioMockUrl(opts.uri, this.baseUrl) })
53
+ }
54
+ }
55
+
56
+ const { TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN } = process.env
57
+ const base = process.env.TWILIO_API_BASE_URL // e.g. http://127.0.0.1:8798
58
+ const client = new Twilio(
59
+ TWILIO_ACCOUNT_SID,
60
+ TWILIO_AUTH_TOKEN,
61
+ base ? { httpClient: new MockRequestClient(base) } : {},
62
+ )
63
+ ```
64
+
65
+ The mock also routes by the `Host` header. A request that reaches it as `lookups.twilio.com`
66
+ (through a DNS override or a proxy) and has no prefix is served as `/lookups/…`. The recording
67
+ adapter's raw `fetch` of `https://api.twilio.com/2010-04-01/…` becomes
68
+ `twilioMockUrl(url, TWILIO_API_BASE_URL)`. `RecordingUrl` in webhooks keeps the canonical
69
+ `https://api.twilio.com` form that the adapter checks.
70
+
71
+ ### Reading the OTP in a test
72
+
73
+ ```ts
74
+ import { createRuntime } from "@crvouga/mockingbird-service-twilio"
75
+
76
+ const twilio = createRuntime()
77
+ // …the app calls verify.v2.services(VA).verifications.create({ to, channel: "sms" })…
78
+ const latest = await twilio.fetch(new Request("http://twilio.test/__admin/verify/+12025550123/latest"))
79
+ const { code, sid, status } = (await latest.json()) as { code: string; sid: string; status: string }
80
+ // …the app calls verificationChecks.create({ verificationSid: sid, code }) → status "approved"
81
+ ```
82
+
83
+ ### Routes
84
+
85
+ | Route | Behaviour |
86
+ | --- | --- |
87
+ | `GET /lookups/v2/PhoneNumbers/{PhoneNumber}` | Validates the number the way the live API does (libphonenumber): `valid`, `validation_errors` (`TOO_SHORT`, `TOO_LONG`, `INVALID_BUT_POSSIBLE`, `INVALID_COUNTRY_CODE`, `INVALID_LENGTH`, `NOT_A_NUMBER`), `phone_number`, `national_format`, `calling_country_code`, `country_code`, `url`. Every data package is `null`. A national number with no `CountryCode` must be a valid US number, otherwise the answer is `INVALID_COUNTRY_CODE`. Fictional 555-01xx numbers are valid. |
88
+ | `POST /verify/v2/Services/{VA}/Verifications` | `To` (valid E.164, or an email for `Channel=email`), `Channel`, optional `CustomCode`. Creates a `VE…` verification with a 6-digit code (or `fixedCode`). Sending again to the same pending verification keeps its sid and code and adds a `send_code_attempts` entry. The 6th send to one number within 10 minutes is 429 `60203`. A bad `To` is 400 `60200`. An unknown service sid (not `VA` + 32 hex) is 404 `20404`. |
89
+ | `POST /verify/v2/Services/{VA}/VerificationCheck` | `Code` plus `VerificationSid` or `To`. The right code returns 200 `approved`, `valid: true`. A wrong code returns 200 `pending` and counts an attempt. The 6th check is 429 `60202`. A missing, approved, canceled or expired (10 minutes on the mock clock) verification is 404 `20404`. |
90
+ | `GET` / `POST /verify/v2/Services/{VA}/Verifications/{VE}` | Fetches a verification, or sets `Status=canceled\|approved` on one. |
91
+ | `POST /api/2010-04-01/Accounts/{AC}/Messages.json` | `To`, `Body` or `MediaUrl`, and `From` or `MessagingServiceSid`. Returns 201 with an `SM…` sid (`MM…` with media), status `queued` (or `accepted` through a Messaging Service), and RFC 2822 dates. Errors: 21604 (no To), 21602 (no Body), 21603 (no From), 21211 (invalid To), 21617 (over 1600 characters). The message is recorded in the outbox. |
92
+ | `GET /api/2010-04-01/Accounts/{AC}/Messages/{SM}.json` | Reads a message back. |
93
+ | `GET /api/2010-04-01/Accounts/{AC}/Recordings/{RE}.wav` | The recording as a RIFF WAV. With `RequestedChannels=2` you get both channels. Otherwise a dual-channel recording is mixed down to mono, as Twilio does. |
94
+ | `GET` / `DELETE /api/2010-04-01/Accounts/{AC}/Recordings/{RE}.json` | Metadata, or a delete (204; a second delete is 404, which our consumer tolerates). |
95
+
96
+ Errors use Twilio's body, `{code, message, more_info, status}`, with `X-Twilio-Error-Code`.
97
+ Authentication is HTTP Basic: an `AC…` (or `SK…`) username and a non-empty password. With
98
+ `accounts` (or `--account-sid` plus `--auth-token`) only those pairs are accepted. A missing or
99
+ wrong credential is 401 `20003`. For a wrong token the message matches the live one:
100
+ `authentication failed, auth token is not valid for account AC…`.
101
+
102
+ ### Webhooks (S8.4)
103
+
104
+ The mock signs inbound webhooks with `X-Twilio-Signature`. The signature is base64
105
+ HMAC-SHA1(auth token, public URL + sorted `key+value` params), the same computation as
106
+ `twilio.validateRequest`. It signs against the **public base URL the app is configured with**
107
+ (`--public-base-url`, i.e. `TWILIO_VOICE_WEBHOOK_BASE_URL`), not the localhost address it posts
108
+ to. Twilio does not retry these webhooks, so the mock sends each one once.
109
+
110
+ | Trigger | Posts to | Payload |
111
+ | --- | --- | --- |
112
+ | `POST /__admin/inbound/sms {from, body, to?, media?, messageSid?, params?}` | `/messaging/inbound/sms` | `MessageSid`/`SmsSid` (`SM…`, or `MM…` with media), `AccountSid`, `From`, `To` (default: `--caller-id`), `Body`, `NumMedia`, `MediaUrl{i}`, `MediaContentType{i}`, `SmsStatus=received`, … |
113
+ | `POST /__admin/voice/twiml` | `/admin/messaging/voice/twiml` | `CallSid`, `From=client:<uuid>`, `To`, `CallStatus=ringing` plus the body's fields (e.g. `conversationId`) |
114
+ | `POST /__admin/voice/disclosure` | `/admin/messaging/voice/disclosure` | `CallSid`, `CallStatus=in-progress` |
115
+ | `POST /__admin/voice/status` | `/admin/messaging/voice/status` | `CallSid`, `CallStatus=completed`, `CallDuration` |
116
+ | `POST /__admin/voice/recording` | `/admin/messaging/voice/recording` | `RecordingSid`, canonical `RecordingUrl`, `RecordingStatus=completed`, `RecordingDuration`, `RecordingChannels=2`. It also stores a dual-channel WAV so the download works. |
117
+
118
+ Every body field overrides a default. The admin answer lists each delivery with the app's status
119
+ and response body (e.g. `<Response/>`). Event types for `PUT /__admin/webhook-endpoints`
120
+ filters are `sms.inbound` and `voice.{twiml,disclosure,status,recording}`.
121
+
122
+ ### Admin (beyond the standard contract)
123
+
124
+ | Route | Effect |
125
+ | --- | --- |
126
+ | `GET /__admin/verify/:e164/latest` | `{code, sid, status, to, channel, serviceSid, attempts, sendAttempts, createdAt, expiresAt}` for the newest verification to that number. This is how a test reads the OTP. |
127
+ | `GET` / `PUT /__admin/verify` | `{fixedCode: "000000" \| null, ttlSeconds, maxCheckAttempts, maxSendAttempts, sendWindowSeconds}` for the calling namespace. |
128
+ | `PUT /__admin/lookups/:e164` | `{valid, validationErrors?}` overrides Lookup for that number (Verify and Messages use it too). `DELETE` removes the override. |
129
+ | `PUT /__admin/recordings/:RE` | Uploads a recording: a raw WAV body (`content-type: audio/wav`), `{"wavBase64": …}`, or `{channels?, seconds?}` for a synthesised 8 kHz 16-bit tone. |
130
+ | `GET /__admin/outbox?to=&since=&kind=sms\|verify` | Everything "sent": SMS (`{to, from, messagingServiceSid, body, sid}`) and every Verify code delivery (`{code, body: "Your verification code is: …"}`). |
131
+ | `GET /__admin/messages` | Message resources in the namespace. |
132
+
133
+ Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`; list them with `GET /__admin/faults/presets`):
134
+ `verify_5xx` (start and check answer 500 `20500`), `sms_socket_drop` (Messages.json drops the
135
+ connection, an unknown outcome), `sms_4xx` (400 `21211`), `lookup_5xx` (503 `20503`; the EMR
136
+ fails open), `webhook_duplicate`, `webhook_drop`.
137
+
138
+ ### Namespaces
139
+
140
+ The Twilio SDK cannot add headers, so a suite picks a namespace by **AccountSid**:
141
+ `PUT /__admin/credentials {"credentials": {"<TWILIO_ACCOUNT_SID>": "<namespace>"}}`. A namespace
142
+ can also come from the `x-mockingbird-namespace` header or a `/ns/<name>` prefix on the base URL
143
+ (`twilioMockUrl` keeps the prefix).
144
+
145
+ ### Deliberately not modelled
146
+
147
+ - Delivery: messages stay `queued`/`accepted`, no status callbacks are sent (our consumer sets no
148
+ `statusCallback`), and no voice calls, conferences or media streams happen.
149
+ - Paid Lookup data packages (`Fields=line_type_intelligence`, `caller_name`, …) are always
150
+ `null`.
151
+ - Verify channels other than SMS are accepted and recorded, but nothing is delivered. Verify
152
+ Service configuration (code length, friendly name, rate-limit buckets) is global, set with
153
+ `PUT /__admin/verify`.
154
+ - "Sent, then the socket dropped": `sms_socket_drop` drops before the message is recorded.
155
+ - Accounts: any `AC…` sid is its own account, and the path's `{AccountSid}` is not
156
+ cross-checked against the credential.
157
+ - libphonenumber-js drops "local only" lengths from its metadata. The mock restores them for
158
+ NANP (7) and GB (4–6, 8), which is enough for the fictional numbers tests use. Other countries
159
+ may answer `TOO_SHORT` where Twilio answers `INVALID_BUT_POSSIBLE`.
160
+
161
+ ## API
162
+
163
+ | Export | Kind | Description |
164
+ | --- | --- | --- |
165
+ | `TwilioAPI` | class | The in-process mock: `fetch(request)`, `reset()`, `latestVerification(to)`, `resolveLookup(raw, country?)`, `setLookup(e164, override)`, `putRecording(sid, input)`, `outbox()`, `messages()`. Options: `sqlite`, `now`, `namespace`, `verify`, `accounts`. |
166
+ | `createRuntime` | function | The mock with the full service contract (health, admin, namespaces, credentials, presets, outbox, webhooks), plus host routing. Options: `app: {url, publicBaseUrl?, authToken, accountSid?, callerId?, messagingServiceSid?}`, `verify`, `accounts`, `retryDelaysMs`, `fetch`, `clock`, `seed`, `adminKey`, `onLog`. The returned runtime also has `inboundSms(input)` and `voiceWebhook(kind, params)`. |
167
+ | `TWILIO_PRESETS` | object | Every named fault preset. |
168
+ | `TWILIO_WEBHOOK_EVENTS` | object | Webhook event type → the app path it posts to. |
169
+ | `twilioMockUrl` | function | Rewrites an upstream `https://<product>.twilio.com/…` URL onto a mock base URL (for the G-T1 `httpClient`). |
170
+ | `TWILIO_PRODUCTS` | array | The product prefixes served: `api`, `verify`, `lookups`. |
171
+ | `lookup` | function | Lookup v2 validation of a raw input (optionally in a `CountryCode` region). |
172
+ | `e164Key` | function | `+` and digits: the key that admin overrides use. |
173
+ | `twilioError` | function | A Twilio error response, `{code, message, more_info, status}`. |
174
+ | `DEFAULT_VERIFY_SETTINGS` | object | Verify defaults: 10-minute expiry, 5 checks, 5 sends per 10 minutes, random codes. |
175
+ | `DEFAULT_ACCOUNT_SID` | string | The account used when the credential is an API key. |
176
+ | `TWILIO_NAMESPACE` | string | The service name, `"twilio"`. |
177
+ | `synthesizeWav`, `readWav`, `mixDownToMono` | functions | Make a PCM WAV, read its format, mix a multi-channel WAV down to mono. |
178
+ | `document`, `operationIds`, `supportedOperationIds` | values | The vendored OpenAPI contract and its operation ids. |
179
+ | `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http`; the `serve` CLI target; port 8798. |
180
+
181
+ Part of [mockingbird](https://github.com/crvouga/mockingbird).
@@ -0,0 +1,388 @@
1
+ import {
2
+ createRuntime
3
+ } from "./chunk-VOBVFMYN.js";
4
+
5
+ // ../../adapters/node/dist/cli.js
6
+ import { readFile } from "node:fs/promises";
7
+ import { parseArgs } from "node:util";
8
+
9
+ // ../../adapters/node/dist/serve.js
10
+ import { createServer } from "node:http";
11
+ var serve = async (api, options = {}) => {
12
+ const server = createServer(async (req, res) => {
13
+ const chunks = [];
14
+ for await (const chunk of req) {
15
+ chunks.push(Buffer.from(chunk));
16
+ }
17
+ const body = Buffer.concat(chunks);
18
+ const address = server.address();
19
+ const port = typeof address === "object" && address !== null ? address.port : void 0;
20
+ const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`;
21
+ const raw = req.url ?? "/";
22
+ const url = new URL(raw.replace(/^\/+/, "/"), base);
23
+ const method = req.method ?? "GET";
24
+ const init = { method, headers: req.headers };
25
+ if (method !== "GET" && method !== "HEAD" && body.length > 0) {
26
+ init.body = body;
27
+ }
28
+ const aborted = new AbortController();
29
+ res.once("close", () => {
30
+ if (!res.writableFinished)
31
+ aborted.abort();
32
+ });
33
+ init.signal = aborted.signal;
34
+ const request = new Request(url, init);
35
+ let response;
36
+ try {
37
+ response = await api.fetch(request);
38
+ } catch (error) {
39
+ if (error.code === "MOCKINGBIRD_DROP") {
40
+ req.socket.destroy();
41
+ return;
42
+ }
43
+ res.writeHead(500, { "content-type": "application/json" });
44
+ res.end(JSON.stringify({
45
+ error: {
46
+ type: "mockingbird_internal",
47
+ message: error instanceof Error ? error.message : String(error)
48
+ }
49
+ }));
50
+ return;
51
+ }
52
+ const headers = Object.fromEntries(response.headers);
53
+ const cookies = response.headers.getSetCookie();
54
+ if (cookies.length > 0)
55
+ headers["set-cookie"] = cookies;
56
+ if (!response.body) {
57
+ res.writeHead(response.status, headers);
58
+ res.end();
59
+ return;
60
+ }
61
+ res.writeHead(response.status, headers);
62
+ res.flushHeaders();
63
+ const reader = response.body.getReader();
64
+ try {
65
+ for (; ; ) {
66
+ const { done, value } = await reader.read();
67
+ if (done)
68
+ break;
69
+ if (!res.write(value))
70
+ await new Promise((resolve) => res.once("drain", resolve));
71
+ }
72
+ res.end();
73
+ } catch {
74
+ res.destroy();
75
+ } finally {
76
+ reader.releaseLock();
77
+ }
78
+ });
79
+ await new Promise((resolve, reject) => {
80
+ server.once("error", reject);
81
+ server.listen(options.port ?? 0, options.host, resolve);
82
+ });
83
+ return server;
84
+ };
85
+
86
+ // ../../adapters/node/dist/listen.js
87
+ var listen = async (api, options = {}) => {
88
+ const host = options.host ?? "127.0.0.1";
89
+ const server = await serve(api, { port: options.port ?? 0, host });
90
+ const address = server.address();
91
+ const port = typeof address === "object" && address !== null ? address.port : options.port ?? 0;
92
+ const shown = host.includes(":") ? `[${host}]` : host;
93
+ return {
94
+ url: `http://${shown}:${port}`,
95
+ port,
96
+ host,
97
+ server,
98
+ close: () => new Promise((resolve, reject) => {
99
+ server.close((error) => {
100
+ if (error && error.code !== "ERR_SERVER_NOT_RUNNING")
101
+ reject(error);
102
+ else
103
+ resolve();
104
+ });
105
+ server.closeAllConnections?.();
106
+ })
107
+ };
108
+ };
109
+
110
+ // ../../adapters/node/dist/cli.js
111
+ var optionHelp = (options) => Object.entries(options).map(([name, option]) => {
112
+ const flag = `--${name}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
113
+ const fallback = option.default !== void 0 ? ` (default: ${String(option.default)})` : "";
114
+ return ` ${flag.padEnd(30)} ${option.description}${fallback}`;
115
+ });
116
+ var help = (spec) => [
117
+ `${spec.bin} \u2014 ${spec.description}`,
118
+ "",
119
+ "Usage:",
120
+ ` ${spec.bin} <command> [options]`,
121
+ "",
122
+ "Commands:",
123
+ ...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),
124
+ "",
125
+ `Run \`${spec.bin} <command> --help\` for a command's options.`
126
+ ].join("\n");
127
+ var commandHelp = (spec, name, command) => [
128
+ `${spec.bin} ${name} \u2014 ${command.summary}`,
129
+ "",
130
+ "Usage:",
131
+ ` ${command.usage ?? `${spec.bin} ${name} [options]`}`,
132
+ ...command.options ? ["", "Options:", ...optionHelp(command.options)] : []
133
+ ].join("\n");
134
+ var runCli = async (spec, argv) => {
135
+ const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : void 0;
136
+ const words = pair !== void 0 && spec.commands[pair] ? 2 : 1;
137
+ const name = words === 2 ? pair : argv[0];
138
+ const rest = argv.slice(words);
139
+ if (name === void 0 || name === "--help" || name === "-h" || name === "help") {
140
+ console.log(help(spec));
141
+ return 0;
142
+ }
143
+ const command = spec.commands[name];
144
+ if (!command) {
145
+ console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}
146
+
147
+ ${help(spec)}`);
148
+ return 2;
149
+ }
150
+ if (rest.includes("--help") || rest.includes("-h")) {
151
+ console.log(commandHelp(spec, name, command));
152
+ return 0;
153
+ }
154
+ let parsed;
155
+ try {
156
+ parsed = parseArgs({
157
+ args: rest,
158
+ allowPositionals: true,
159
+ strict: true,
160
+ options: Object.fromEntries(Object.entries(command.options ?? {}).map(([key, option]) => [
161
+ key,
162
+ {
163
+ type: option.type,
164
+ ...option.default !== void 0 ? { default: option.default } : {}
165
+ }
166
+ ]))
167
+ });
168
+ } catch (error) {
169
+ console.error(`${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}
170
+
171
+ ${commandHelp(spec, name, command)}`);
172
+ return 2;
173
+ }
174
+ return command.run(parsed.values, parsed.positionals);
175
+ };
176
+ var COMMON_SERVE_OPTIONS = {
177
+ port: { type: "string", value: "<port>", description: "Port to listen on" },
178
+ host: { type: "string", value: "<host>", description: "Interface to bind", default: "127.0.0.1" },
179
+ "admin-key": {
180
+ type: "string",
181
+ value: "<key>",
182
+ description: "Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)"
183
+ },
184
+ seed: { type: "string", value: "<seed>", description: "Seed for every random choice" },
185
+ log: {
186
+ type: "string",
187
+ value: "<pretty|json|off>",
188
+ description: "Request log format",
189
+ default: "pretty"
190
+ },
191
+ "log-requests": {
192
+ type: "boolean",
193
+ description: "One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json"
194
+ },
195
+ config: {
196
+ type: "string",
197
+ value: "<file>",
198
+ description: "Serve every service in a mockingbird.json config instead"
199
+ }
200
+ };
201
+ var formatLog = (format) => {
202
+ if (format === "off")
203
+ return void 0;
204
+ if (format === "json")
205
+ return (entry) => console.log(JSON.stringify(entry));
206
+ return (entry) => {
207
+ const op = entry.operationId ?? (entry.unmatched ? "UNMATCHED" : "-");
208
+ const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
209
+ const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
210
+ const adopted = entry.adopted ? " adopted" : "";
211
+ console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}`);
212
+ };
213
+ };
214
+ var asString = (value) => typeof value === "string" ? value : void 0;
215
+ var loadTarget = async (name, own) => {
216
+ if (name === own.name)
217
+ return own;
218
+ const specifier = `@crvouga/mockingbird-service-${name}/server`;
219
+ try {
220
+ const mod = await import(specifier);
221
+ if (!mod.serveTarget)
222
+ throw new Error(`${specifier} exports no serveTarget`);
223
+ return mod.serveTarget;
224
+ } catch (error) {
225
+ const reason = error instanceof Error ? error.message : String(error);
226
+ throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
227
+ }
228
+ };
229
+ var start = async (target, values, config) => {
230
+ const runtime = await target.create(values, {
231
+ adminKey: config.adminKey,
232
+ seed: config.seed,
233
+ onLog: formatLog(config.log)
234
+ });
235
+ const listening = await listen(runtime, { port: config.port, host: config.host });
236
+ console.log(`${target.name} mock listening on ${listening.url}`);
237
+ console.log(`${target.name} health: GET ${listening.url}/health`);
238
+ console.log(`${target.name} admin: ${listening.url}/__admin (${config.adminKey ? "x-mockingbird-admin-key required" : "open \u2014 pass --admin-key to lock"})`);
239
+ for (const line of target.banner?.(runtime) ?? [])
240
+ console.log(`${target.name} ${line}`);
241
+ return listening;
242
+ };
243
+ var untilSignal = async (servers) => new Promise((resolve) => {
244
+ const stop = async () => {
245
+ await Promise.allSettled(servers.map((s) => s.close()));
246
+ resolve(0);
247
+ };
248
+ process.once("SIGINT", stop);
249
+ process.once("SIGTERM", stop);
250
+ });
251
+ var serveCommand = (target) => ({
252
+ summary: `Serve the ${target.name} mock over HTTP`,
253
+ options: { ...COMMON_SERVE_OPTIONS, ...target.options },
254
+ async run(values) {
255
+ const log = values["log-requests"] === true ? "json" : asString(values.log) ?? "pretty";
256
+ if (!["pretty", "json", "off"].includes(log)) {
257
+ console.error(`--log must be pretty, json or off (got ${log})`);
258
+ return 2;
259
+ }
260
+ const configPath = asString(values.config);
261
+ if (configPath !== void 0) {
262
+ const config = JSON.parse(await readFile(configPath, "utf8"));
263
+ const servers = [];
264
+ try {
265
+ for (const [name, entry] of Object.entries(config.services ?? {})) {
266
+ const each = await loadTarget(name, target);
267
+ servers.push(await start(each, entry.options ?? {}, {
268
+ port: entry.port ?? each.defaultPort,
269
+ host: entry.host ?? "127.0.0.1",
270
+ ...entry.adminKey !== void 0 ? { adminKey: entry.adminKey } : {},
271
+ ...entry.seed !== void 0 ? { seed: entry.seed } : {},
272
+ log: config.log ?? log
273
+ }));
274
+ }
275
+ } catch (error) {
276
+ await Promise.allSettled(servers.map((s) => s.close()));
277
+ console.error(error instanceof Error ? error.message : String(error));
278
+ return 1;
279
+ }
280
+ return untilSignal(servers);
281
+ }
282
+ const port = asString(values.port);
283
+ const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
284
+ const seed = asString(values.seed);
285
+ let listening;
286
+ try {
287
+ listening = await start(target, values, {
288
+ port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
289
+ host: asString(values.host) ?? "127.0.0.1",
290
+ ...adminKey !== void 0 ? { adminKey } : {},
291
+ ...seed !== void 0 ? { seed } : {},
292
+ log
293
+ });
294
+ } catch (error) {
295
+ console.error(error instanceof Error ? error.message : String(error));
296
+ return 1;
297
+ }
298
+ return untilSignal([listening]);
299
+ }
300
+ });
301
+
302
+ // src/server.ts
303
+ var DEFAULT_PORT = 8798;
304
+ var createServer2 = async (options = {}) => {
305
+ const { port, host, ...rest } = options;
306
+ const runtime = createRuntime(rest);
307
+ const listening = await listen(runtime, {
308
+ port: port ?? 0,
309
+ ...host !== void 0 ? { host } : {}
310
+ });
311
+ return { ...listening, runtime };
312
+ };
313
+ var text = (value) => typeof value === "string" ? value : void 0;
314
+ var serveTarget = {
315
+ name: "twilio",
316
+ defaultPort: DEFAULT_PORT,
317
+ options: {
318
+ "app-url": {
319
+ type: "string",
320
+ value: "<url>",
321
+ description: "Post inbound SMS and voice webhooks to this app origin (e.g. http://127.0.0.1:3000)"
322
+ },
323
+ "public-base-url": {
324
+ type: "string",
325
+ value: "<url>",
326
+ description: "Sign webhooks against this base (the app's TWILIO_VOICE_WEBHOOK_BASE_URL)"
327
+ },
328
+ "account-sid": {
329
+ type: "string",
330
+ value: "<AC\u2026>",
331
+ description: "The app's TWILIO_ACCOUNT_SID (AccountSid in webhooks)"
332
+ },
333
+ "auth-token": {
334
+ type: "string",
335
+ value: "<token>",
336
+ description: "The app's TWILIO_AUTH_TOKEN: signs webhooks, and with --account-sid is the only accepted token"
337
+ },
338
+ "caller-id": {
339
+ type: "string",
340
+ value: "<+1\u2026>",
341
+ description: "To of inbound SMS (the app's TWILIO_VOICE_CALLER_ID)"
342
+ },
343
+ "fixed-code": {
344
+ type: "string",
345
+ value: "<digits>",
346
+ description: "Every Verify code is this (default: random 6 digits, read via /__admin)"
347
+ }
348
+ },
349
+ create: (values, common) => {
350
+ const appUrl = text(values["app-url"]);
351
+ const accountSid = text(values["account-sid"]);
352
+ const authToken = text(values["auth-token"]);
353
+ const publicBaseUrl = text(values["public-base-url"]);
354
+ const callerId = text(values["caller-id"]);
355
+ const fixedCode = text(values["fixed-code"]);
356
+ if (appUrl && !authToken) throw new Error("--app-url needs --auth-token (the signing key)");
357
+ return createRuntime({
358
+ ...appUrl && authToken ? {
359
+ app: {
360
+ url: appUrl,
361
+ authToken,
362
+ ...publicBaseUrl ? { publicBaseUrl } : {},
363
+ ...accountSid ? { accountSid } : {},
364
+ ...callerId ? { callerId } : {}
365
+ }
366
+ } : {},
367
+ ...accountSid && authToken ? { accounts: { [accountSid]: authToken } } : {},
368
+ ...fixedCode ? { verify: { fixedCode } } : {},
369
+ ...common.adminKey !== void 0 ? { adminKey: common.adminKey } : {},
370
+ ...common.seed !== void 0 ? { seed: common.seed } : {},
371
+ ...common.onLog ? { onLog: common.onLog } : {}
372
+ });
373
+ },
374
+ banner: () => [
375
+ "routes: /lookups/v2/\u2026, /verify/v2/\u2026, /api/2010-04-01/\u2026 (the Twilio host as a path prefix)",
376
+ "auth: Basic AccountSid:AuthToken; OTP: GET /__admin/verify/<e164>/latest",
377
+ "namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<AccountSid>: <ns>}"
378
+ ]
379
+ };
380
+
381
+ export {
382
+ runCli,
383
+ serveCommand,
384
+ DEFAULT_PORT,
385
+ createServer2 as createServer,
386
+ serveTarget
387
+ };
388
+ //# sourceMappingURL=chunk-37HPXDSK.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../adapters/node/src/cli.ts", "../../../adapters/node/src/serve.ts", "../../../adapters/node/src/listen.ts", "../src/server.ts"],
4
+ "sourcesContent": ["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 { type Listening, listen, type ServeTarget } from \"@crvouga/mockingbird-adapter-node\"\nimport { createRuntime, type TwilioRuntime, type TwilioRuntimeOptions } from \"./runtime.js\"\n\n/** Port `mockingbird-twilio serve` listens on when none is given. */\nexport const DEFAULT_PORT = 8798\n\nexport type TwilioServerOptions = TwilioRuntimeOptions & {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`. */\n host?: string\n}\n\nexport type TwilioServer = Listening & { runtime: TwilioRuntime }\n\n/** Serve the Twilio mock over `node:http`. */\nexport const createServer = async (options: TwilioServerOptions = {}): Promise<TwilioServer> => {\n const { port, host, ...rest } = options\n const runtime = createRuntime(rest)\n const listening = await listen(runtime, {\n port: port ?? 0,\n ...(host !== undefined ? { host } : {}),\n })\n return { ...listening, runtime }\n}\n\nconst text = (value: string | boolean | undefined) =>\n typeof value === \"string\" ? value : undefined\n\n/** How `serve` (and `serve --config`) builds the Twilio mock from flags. */\nexport const serveTarget: ServeTarget = {\n name: \"twilio\",\n defaultPort: DEFAULT_PORT,\n options: {\n \"app-url\": {\n type: \"string\",\n value: \"<url>\",\n description:\n \"Post inbound SMS and voice webhooks to this app origin (e.g. http://127.0.0.1:3000)\",\n },\n \"public-base-url\": {\n type: \"string\",\n value: \"<url>\",\n description: \"Sign webhooks against this base (the app's TWILIO_VOICE_WEBHOOK_BASE_URL)\",\n },\n \"account-sid\": {\n type: \"string\",\n value: \"<AC\u2026>\",\n description: \"The app's TWILIO_ACCOUNT_SID (AccountSid in webhooks)\",\n },\n \"auth-token\": {\n type: \"string\",\n value: \"<token>\",\n description:\n \"The app's TWILIO_AUTH_TOKEN: signs webhooks, and with --account-sid is the only accepted token\",\n },\n \"caller-id\": {\n type: \"string\",\n value: \"<+1\u2026>\",\n description: \"To of inbound SMS (the app's TWILIO_VOICE_CALLER_ID)\",\n },\n \"fixed-code\": {\n type: \"string\",\n value: \"<digits>\",\n description: \"Every Verify code is this (default: random 6 digits, read via /__admin)\",\n },\n },\n create: (values, common) => {\n const appUrl = text(values[\"app-url\"])\n const accountSid = text(values[\"account-sid\"])\n const authToken = text(values[\"auth-token\"])\n const publicBaseUrl = text(values[\"public-base-url\"])\n const callerId = text(values[\"caller-id\"])\n const fixedCode = text(values[\"fixed-code\"])\n if (appUrl && !authToken) throw new Error(\"--app-url needs --auth-token (the signing key)\")\n return createRuntime({\n ...(appUrl && authToken\n ? {\n app: {\n url: appUrl,\n authToken,\n ...(publicBaseUrl ? { publicBaseUrl } : {}),\n ...(accountSid ? { accountSid } : {}),\n ...(callerId ? { callerId } : {}),\n },\n }\n : {}),\n ...(accountSid && authToken ? { accounts: { [accountSid]: authToken } } : {}),\n ...(fixedCode ? { verify: { fixedCode } } : {}),\n ...(common.adminKey !== undefined ? { adminKey: common.adminKey } : {}),\n ...(common.seed !== undefined ? { seed: common.seed } : {}),\n ...(common.onLog ? { onLog: common.onLog } : {}),\n })\n },\n banner: () => [\n \"routes: /lookups/v2/\u2026, /verify/v2/\u2026, /api/2010-04-01/\u2026 (the Twilio host as a path prefix)\",\n \"auth: Basic AccountSid:AuthToken; OTP: GET /__admin/verify/<e164>/latest\",\n \"namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<AccountSid>: <ns>}\",\n ],\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["createServer"]
7
+ }