@crvouga/mockingbird-service-resend 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 +5 -0
- package/README.md +159 -0
- package/dist/chunk-A4NJUMTM.js +369 -0
- package/dist/chunk-A4NJUMTM.js.map +7 -0
- package/dist/chunk-MAW2TLUJ.js +3233 -0
- package/dist/chunk-MAW2TLUJ.js.map +7 -0
- package/dist/cli.js +19 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +1040 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +7 -0
- package/dist/server.d.ts +1372 -0
- package/dist/server.js +12 -0
- package/dist/server.js.map +7 -0
- package/package.json +93 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# @crvouga/mockingbird-service-resend
|
|
2
|
+
|
|
3
|
+
Stateful mock of the **Resend** email API for test suites. Every send lands in an **outbox**
|
|
4
|
+
that tests read (`GET /__admin/outbox`, and the links in each email). `Idempotency-Key` replays
|
|
5
|
+
return the first send's id. Inbound emails are stored and announced with a Svix-signed
|
|
6
|
+
`email.received` webhook, and the received-email endpoints serve their content and
|
|
7
|
+
attachments. With `--forward-to-inbox`, every sent email is also copied into the
|
|
8
|
+
[Mailosaur mock](../mailosaur), so one inbox holds every code and link.
|
|
9
|
+
|
|
10
|
+
- Operation coverage: [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/resend/SUPPORT.md)
|
|
11
|
+
- `openapi.yaml` is trimmed from Resend's published API reference to what `resend@4.8.0` and
|
|
12
|
+
our backend call.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -D @crvouga/mockingbird-service-resend
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
|
|
21
|
+
`npx mockingbird-resend serve`, `createServer` from `./server` (Node), or `createRuntime` with
|
|
22
|
+
any Fetch server.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
`resend@4.x` reads `RESEND_BASE_URL` **once, when the module is first imported**, so set it in
|
|
27
|
+
the app's environment (or before the first `import("resend")` in a test). Set the app's
|
|
28
|
+
`RESEND_INBOUND_WEBHOOK_SECRET` and pass the same `whsec_…` value as `--webhook-secret`.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx mockingbird-resend serve --port 8794 \
|
|
32
|
+
--webhook-url http://127.0.0.1:3000/messaging/inbound/email \
|
|
33
|
+
--webhook-secret "$RESEND_INBOUND_WEBHOOK_SECRET" \
|
|
34
|
+
--forward-to-inbox http://127.0.0.1:8793
|
|
35
|
+
RESEND_BASE_URL=http://127.0.0.1:8794 node app.js
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
import { createServer } from "@crvouga/mockingbird-service-resend/server"
|
|
40
|
+
|
|
41
|
+
const mock = await createServer()
|
|
42
|
+
process.env.RESEND_BASE_URL = mock.url
|
|
43
|
+
const { Resend } = await import("resend")
|
|
44
|
+
|
|
45
|
+
await new Resend("re_test").emails.send({
|
|
46
|
+
from: "Geviti <no-reply@gogeviti.com>",
|
|
47
|
+
to: ["invitee@example.com"],
|
|
48
|
+
subject: "You're invited",
|
|
49
|
+
html: '<a href="https://app.test/family/invitations/claim?token=abc">Join</a>',
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const { messages } = await (await fetch(`${mock.url}/__admin/outbox?to=invitee@example.com`)).json()
|
|
53
|
+
const { links } = await (await fetch(`${mock.url}/__admin/outbox/${messages[0].id}/links`)).json()
|
|
54
|
+
// links[0] === "https://app.test/family/invitations/claim?token=abc"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Whatever sends the mail (the SDK above, or raw HTTP), the outbox is the assertion surface:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { createServer } from "@crvouga/mockingbird-service-resend/server"
|
|
61
|
+
|
|
62
|
+
const mock = await createServer()
|
|
63
|
+
await fetch(`${mock.url}/emails`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { "content-type": "application/json", authorization: "Bearer re_test" },
|
|
66
|
+
body: JSON.stringify({ from: "no-reply@example.com", to: ["invitee@example.com"], subject: "Hi", text: "Hello" }),
|
|
67
|
+
})
|
|
68
|
+
const sent = await (await fetch(`${mock.url}/__admin/outbox?to=invitee@example.com`)).json()
|
|
69
|
+
await mock.close()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Routes
|
|
73
|
+
|
|
74
|
+
| Route | Behaviour |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `POST /emails` | `{from, to, subject, html?, text?, cc?, bcc?, reply_to?, headers?, tags?, attachments?, scheduled_at?}` → `{id}` (a UUID). The SDK renders `react` to HTML before sending, so the mock sees HTML. Violations answer Resend's body `{statusCode, name, message}`: 422 `missing_required_field` (`Missing \`to\` field.`, or no `html`/`text`), 422 `validation_error` for a malformed address (`Invalid \`from\` field. The email address needs to follow …`), a tag outside `[A-Za-z0-9_-]`, or any other contract violation. `Idempotency-Key`: the same key and payload replay the first 200 byte for byte (`idempotent-replayed: true`); another payload is 409 `invalid_idempotent_request`; a key still in flight is 409 `concurrent_idempotent_requests`; a key outside 1–256 characters is 400 `invalid_idempotency_key`. Only 200s are remembered. |
|
|
77
|
+
| `GET /emails/{id}` | `emails.get`: `{object: "email", id, to, from, created_at, subject, html, text, cc, bcc, reply_to, last_event: "delivered" \| "scheduled", scheduled_at, tags}`. |
|
|
78
|
+
| `GET /emails/receiving/{id}` | A received email: `{object: "email", id, to, from, cc, bcc, reply_to, created_at, subject, html, text, headers, message_id, attachments[{id, filename, content_type, content_disposition, content_id}]}`. |
|
|
79
|
+
| `GET /emails/receiving/{id}/attachments` | `{object: "list", has_more: false, data: [{id, filename, content_type, content_disposition, content_id, size, download_url, expires_at}]}`. |
|
|
80
|
+
| `GET /downloads/inbound/{attachment_id}` | The `download_url`: unauthenticated, the bytes with `content-type` and `content-length`. In a non-default namespace the URL carries `/ns/<name>`. |
|
|
81
|
+
|
|
82
|
+
Auth is `Authorization: Bearer <key>`; any key works; none is 401 `missing_api_key`.
|
|
83
|
+
|
|
84
|
+
**SDK error mapping** (`resend@4.8.0` never throws): a non-2xx JSON body comes back verbatim as
|
|
85
|
+
`{data: null, error: {statusCode, name, message}}`; a non-JSON body becomes
|
|
86
|
+
`{name: "application_error", message: "Internal server error. …"}`; a dropped connection
|
|
87
|
+
becomes `{name: "application_error", message: "Unable to fetch data. The request could not be resolved."}`.
|
|
88
|
+
|
|
89
|
+
### Webhooks
|
|
90
|
+
|
|
91
|
+
`POST /__admin/inbound` stores the email, then posts to every endpoint:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{"type": "email.received", "created_at": "…",
|
|
95
|
+
"data": {"email_id": "…", "created_at": "…", "from": "…", "to": ["care+tok@care.example"], "cc": [], "bcc": [],
|
|
96
|
+
"subject": "…", "message_id": "<…>", "attachments": [{"id", "filename", "content_type", "content_disposition", "content_id"}]}}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Like Resend's, the body carries no `text`, `html`, `headers` or download URLs, so a receiver
|
|
100
|
+
hydrates them through the received-email routes. Pass `"inline": true` to include them. Each
|
|
101
|
+
delivery is signed the Svix way: `svix-id`, `svix-timestamp` (wall clock, even when the mock
|
|
102
|
+
clock moves), `svix-signature: v1,<base64 HMAC-SHA256(secret bytes, "id.ts.body")>`. The
|
|
103
|
+
official `svix` `Webhook.verify` accepts them. Non-2xx answers are retried (immediately, 5 s,
|
|
104
|
+
5 min, 30 min, 2 h). `GET /__admin/webhooks`, `…/events`, `…/flush`, `…/:id/replay` and
|
|
105
|
+
`PUT /__admin/webhook-endpoints` (per-namespace receivers) come with the contract.
|
|
106
|
+
|
|
107
|
+
### Admin (beyond the standard contract)
|
|
108
|
+
|
|
109
|
+
| Route | Effect |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `GET /__admin/outbox?to=&tag=<name>:<value>&since=&limit=` | Sent emails, oldest first: `{id, from, to (bare, lower-cased), toHeader, cc, bcc, replyTo, subject, html, text, tags, headers, attachments (filename, contentType, size), idempotencyKey, scheduledAt, createdAt}`. `tag=category` matches any value. `GET /__admin/outbox/:id` returns one. |
|
|
112
|
+
| `GET /__admin/outbox/:id/links` | `{id, links}`: every `href` in the HTML, entity-decoded (every URL in the text when there is no HTML). |
|
|
113
|
+
| `POST /__admin/inbound` | `{from, to, cc?, bcc?, replyTo?, subject?, text?, html?, headers?, messageId?, attachments?: [{filename, content (base64), contentType?, contentId?, contentDisposition?}], inline?}` → 201 `{id, webhook, event}`. |
|
|
114
|
+
| `GET /__admin/inbound` | Received emails, oldest first. |
|
|
115
|
+
| `GET /__admin/forwarding` | `{target, forwarded, failed, lastError}` for `--forward-to-inbox`. |
|
|
116
|
+
|
|
117
|
+
`--forward-to-inbox <url>` copies every accepted send (not replays) to the Mailosaur mock's
|
|
118
|
+
`POST /__admin/ingest`, under the **same namespace name**, and waits up to 2 s for it. A failing
|
|
119
|
+
inbox never fails the send; it is counted in `/__admin/forwarding`. Map the same key to the
|
|
120
|
+
same namespace on both mocks and a worker reads its forwarded mail through the Mailosaur SDK.
|
|
121
|
+
|
|
122
|
+
Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`; `GET /__admin/faults/presets`):
|
|
123
|
+
`send_422` (`{statusCode: 422, name: "validation_error", message}`), `send_429`
|
|
124
|
+
(`rate_limit_exceeded` with `retry-after` and `ratelimit-*` headers), `send_500`
|
|
125
|
+
(`internal_server_error`), `non_json_500` (an HTML 500 page), `network_drop` (the connection
|
|
126
|
+
dies before an answer), `receiving_500` (the received-email routes answer 500),
|
|
127
|
+
`webhook_duplicate`, `webhook_reorder`, `webhook_drop`.
|
|
128
|
+
|
|
129
|
+
### Namespaces
|
|
130
|
+
|
|
131
|
+
`new Resend(key)` cannot add headers, so map API keys to namespaces:
|
|
132
|
+
`PUT /__admin/credentials {"credentials": {"<RESEND_API_KEY>": "<namespace>"}}`. Also
|
|
133
|
+
`x-mockingbird-namespace`, or a `/ns/<name>` prefix on `RESEND_BASE_URL`.
|
|
134
|
+
|
|
135
|
+
### Deliberately not modelled
|
|
136
|
+
|
|
137
|
+
- Delivery itself: no SMTP, bounces, complaints, opens or clicks, and no `email.sent` /
|
|
138
|
+
`email.delivered` webhooks. `last_event` is `delivered` at once (`scheduled` with
|
|
139
|
+
`scheduled_at`, which is stored but never fires).
|
|
140
|
+
- Batch send, templates, domains, API keys, audiences, contacts and broadcasts (our code calls none).
|
|
141
|
+
- Attachment `path` URLs are not fetched; sent attachments keep metadata only.
|
|
142
|
+
- Rate limits, except through `send_429`.
|
|
143
|
+
- The idempotency window: keys never expire (Resend keeps them 24 h).
|
|
144
|
+
|
|
145
|
+
## API
|
|
146
|
+
|
|
147
|
+
| Export | Kind | Description |
|
|
148
|
+
| --- | --- | --- |
|
|
149
|
+
| `ResendAPI` | class | The in-process mock: `fetch(request)`, `reset()`, `sent()`, `inbound()`, `receive(input, origin)`, `state`. Options: `sqlite`, `now`, `namespace`, `publicNamespace`, `onSent`. |
|
|
150
|
+
| `createRuntime` | function | The mock with the full service contract (health, admin, namespaces, credentials, presets, Svix webhooks, outbox). Options: `webhooks: {url, secret, retryDelaysMs?, fetch?}`, `forwardToInbox: {url, adminKey?, timeoutMs?, fetch?}`, `clock`, `seed`, `adminKey`, `onLog`, `sqlite`. |
|
|
151
|
+
| `forwardToInbox` | function | Copy one sent email into a Mailosaur mock's ingest route. |
|
|
152
|
+
| `RESEND_PRESETS` | object | Every named fault preset. |
|
|
153
|
+
| `RESEND_NAMESPACE` | string | The service name, `"resend"`. |
|
|
154
|
+
| `DOWNLOAD_URL_TTL_MS` | number | The advertised lifetime of a `download_url` (1 h). |
|
|
155
|
+
| `bareAddress` | function | `Name <a@b.co>` → `a@b.co` (how the outbox's `?to=` compares). |
|
|
156
|
+
| `document`, `operationIds`, `supportedOperationIds` | values | The vendored OpenAPI contract and its operation ids. |
|
|
157
|
+
| `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http`; the `serve` CLI target (`--webhook-url`, `--webhook-secret`, `--forward-to-inbox`); port 8794. |
|
|
158
|
+
|
|
159
|
+
Part of [mockingbird](https://github.com/crvouga/mockingbird).
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRuntime
|
|
3
|
+
} from "./chunk-MAW2TLUJ.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 = 8794;
|
|
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: "resend",
|
|
316
|
+
defaultPort: DEFAULT_PORT,
|
|
317
|
+
options: {
|
|
318
|
+
"webhook-url": {
|
|
319
|
+
type: "string",
|
|
320
|
+
value: "<url>",
|
|
321
|
+
description: "Deliver email.received webhooks here (e.g. http://127.0.0.1:3000/messaging/inbound/email)"
|
|
322
|
+
},
|
|
323
|
+
"webhook-secret": {
|
|
324
|
+
type: "string",
|
|
325
|
+
value: "<whsec_\u2026>",
|
|
326
|
+
description: "Svix signing secret (the app's RESEND_INBOUND_WEBHOOK_SECRET)"
|
|
327
|
+
},
|
|
328
|
+
"forward-to-inbox": {
|
|
329
|
+
type: "string",
|
|
330
|
+
value: "<url>",
|
|
331
|
+
description: "Copy every sent email into a Mailosaur mock (its POST /__admin/ingest), e.g. http://127.0.0.1:8793"
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
create: (values, common) => {
|
|
335
|
+
const url = text(values["webhook-url"]);
|
|
336
|
+
const secret = text(values["webhook-secret"]);
|
|
337
|
+
const inbox = text(values["forward-to-inbox"]);
|
|
338
|
+
if (secret && !/^whsec_[A-Za-z0-9+/=]+$/.test(secret)) {
|
|
339
|
+
throw new Error("--webhook-secret must be whsec_<base64>, as Resend issues it");
|
|
340
|
+
}
|
|
341
|
+
return createRuntime({
|
|
342
|
+
...url ? { webhooks: { url, ...secret ? { secret } : {} } } : {},
|
|
343
|
+
...inbox ? {
|
|
344
|
+
forwardToInbox: {
|
|
345
|
+
url: inbox,
|
|
346
|
+
...common.adminKey !== void 0 ? { adminKey: common.adminKey } : {}
|
|
347
|
+
}
|
|
348
|
+
} : {},
|
|
349
|
+
...common.adminKey !== void 0 ? { adminKey: common.adminKey } : {},
|
|
350
|
+
...common.seed !== void 0 ? { seed: common.seed } : {},
|
|
351
|
+
...common.onLog ? { onLog: common.onLog } : {}
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
banner: () => [
|
|
355
|
+
"auth: Authorization: Bearer re_\u2026 (any key); set RESEND_BASE_URL before importing resend",
|
|
356
|
+
"outbox: GET /__admin/outbox?to=&tag=category:<v>, GET /__admin/outbox/:id/links",
|
|
357
|
+
"inbound: POST /__admin/inbound {from, to, subject, text?, html?, attachments?} \u2192 email.received",
|
|
358
|
+
"namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<api key>: <ns>}"
|
|
359
|
+
]
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
export {
|
|
363
|
+
runCli,
|
|
364
|
+
serveCommand,
|
|
365
|
+
DEFAULT_PORT,
|
|
366
|
+
createServer2 as createServer,
|
|
367
|
+
serveTarget
|
|
368
|
+
};
|
|
369
|
+
//# sourceMappingURL=chunk-A4NJUMTM.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 ResendRuntime, type ResendRuntimeOptions } from \"./runtime.js\"\n\n/** Port `mockingbird-resend serve` listens on when none is given. */\nexport const DEFAULT_PORT = 8794\n\nexport type ResendServerOptions = ResendRuntimeOptions & {\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 ResendServer = Listening & { runtime: ResendRuntime }\n\n/** Serve the Resend mock over `node:http`. Point `RESEND_BASE_URL` at `url` before importing `resend`. */\nexport const createServer = async (options: ResendServerOptions = {}): Promise<ResendServer> => {\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 Resend mock from flags. */\nexport const serveTarget: ServeTarget = {\n name: \"resend\",\n defaultPort: DEFAULT_PORT,\n options: {\n \"webhook-url\": {\n type: \"string\",\n value: \"<url>\",\n description:\n \"Deliver email.received webhooks here (e.g. http://127.0.0.1:3000/messaging/inbound/email)\",\n },\n \"webhook-secret\": {\n type: \"string\",\n value: \"<whsec_\u2026>\",\n description: \"Svix signing secret (the app's RESEND_INBOUND_WEBHOOK_SECRET)\",\n },\n \"forward-to-inbox\": {\n type: \"string\",\n value: \"<url>\",\n description:\n \"Copy every sent email into a Mailosaur mock (its POST /__admin/ingest), e.g. http://127.0.0.1:8793\",\n },\n },\n create: (values, common) => {\n const url = text(values[\"webhook-url\"])\n const secret = text(values[\"webhook-secret\"])\n const inbox = text(values[\"forward-to-inbox\"])\n if (secret && !/^whsec_[A-Za-z0-9+/=]+$/.test(secret)) {\n throw new Error(\"--webhook-secret must be whsec_<base64>, as Resend issues it\")\n }\n return createRuntime({\n ...(url ? { webhooks: { url, ...(secret ? { secret } : {}) } } : {}),\n ...(inbox\n ? {\n forwardToInbox: {\n url: inbox,\n ...(common.adminKey !== undefined ? { adminKey: common.adminKey } : {}),\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: () => [\n \"auth: Authorization: Bearer re_\u2026 (any key); set RESEND_BASE_URL before importing resend\",\n \"outbox: GET /__admin/outbox?to=&tag=category:<v>, GET /__admin/outbox/:id/links\",\n \"inbound: POST /__admin/inbound {from, to, subject, text?, html?, attachments?} \u2192 email.received\",\n \"namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<api key>: <ns>}\",\n ],\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["createServer"]
|
|
7
|
+
}
|