emuhealth 0.1.1 → 0.3.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.
Files changed (51) hide show
  1. package/README.md +12 -8
  2. package/dist/api.d.ts +4 -1
  3. package/dist/api.js +81 -21
  4. package/dist/api.js.map +1 -1
  5. package/dist/{chunk-GQ4IO5X5.js → chunk-VCQ3UPC5.js} +83 -30
  6. package/dist/chunk-VCQ3UPC5.js.map +1 -0
  7. package/dist/{dist-PLRHPEKZ.js → dist-22OZW7MU.js} +9 -7
  8. package/dist/dist-22OZW7MU.js.map +1 -0
  9. package/dist/{dist-EESUF62H.js → dist-575EVZLI.js} +9 -7
  10. package/dist/{dist-EESUF62H.js.map → dist-575EVZLI.js.map} +1 -1
  11. package/dist/{dist-M5AWLPBU.js → dist-ATXASBBA.js} +9 -7
  12. package/dist/dist-ATXASBBA.js.map +1 -0
  13. package/dist/{dist-EGN5EUC3.js → dist-B4K5JXTS.js} +9 -7
  14. package/dist/dist-B4K5JXTS.js.map +1 -0
  15. package/dist/{dist-A2VVAQ3G.js → dist-EUMN2OAH.js} +9 -7
  16. package/dist/dist-EUMN2OAH.js.map +1 -0
  17. package/dist/{dist-B5YXZCS3.js → dist-IMIDQ6CJ.js} +9 -7
  18. package/dist/dist-IMIDQ6CJ.js.map +1 -0
  19. package/dist/{dist-H6TJ2XTL.js → dist-KAYD3L2O.js} +9 -7
  20. package/dist/dist-KAYD3L2O.js.map +1 -0
  21. package/dist/{dist-FJPSLFJD.js → dist-KRJZAPPL.js} +9 -7
  22. package/dist/dist-KRJZAPPL.js.map +1 -0
  23. package/dist/{dist-SOYGDR4W.js → dist-L3SNHIRB.js} +9 -7
  24. package/dist/dist-L3SNHIRB.js.map +1 -0
  25. package/dist/{dist-7UXL7QGA.js → dist-M5TIXC2F.js} +9 -7
  26. package/dist/dist-M5TIXC2F.js.map +1 -0
  27. package/dist/dist-WLTGCLZF.js +1531 -0
  28. package/dist/dist-WLTGCLZF.js.map +1 -0
  29. package/dist/{dist-QJ24W3VN.js → dist-YK2XJNH7.js} +9 -7
  30. package/dist/dist-YK2XJNH7.js.map +1 -0
  31. package/dist/{dist-C6Y6IXLU.js → dist-YMRASK5I.js} +9 -7
  32. package/dist/dist-YMRASK5I.js.map +1 -0
  33. package/dist/{dist-J2Q7BESS.js → dist-ZGCIWJJM.js} +9 -7
  34. package/dist/dist-ZGCIWJJM.js.map +1 -0
  35. package/dist/index.js +24 -11
  36. package/dist/index.js.map +1 -1
  37. package/dist/project-worker.js +1 -1
  38. package/package.json +7 -6
  39. package/dist/chunk-GQ4IO5X5.js.map +0 -1
  40. package/dist/dist-7UXL7QGA.js.map +0 -1
  41. package/dist/dist-A2VVAQ3G.js.map +0 -1
  42. package/dist/dist-B5YXZCS3.js.map +0 -1
  43. package/dist/dist-C6Y6IXLU.js.map +0 -1
  44. package/dist/dist-EGN5EUC3.js.map +0 -1
  45. package/dist/dist-FJPSLFJD.js.map +0 -1
  46. package/dist/dist-H6TJ2XTL.js.map +0 -1
  47. package/dist/dist-J2Q7BESS.js.map +0 -1
  48. package/dist/dist-M5AWLPBU.js.map +0 -1
  49. package/dist/dist-PLRHPEKZ.js.map +0 -1
  50. package/dist/dist-QJ24W3VN.js.map +0 -1
  51. package/dist/dist-SOYGDR4W.js.map +0 -1
package/README.md CHANGED
@@ -35,6 +35,10 @@ All services listen on IPv4 loopback (`127.0.0.1`) by default. No config file ne
35
35
  - **Linear** on `http://localhost:4012`
36
36
  - **Twilio** on `http://localhost:4013`
37
37
 
38
+ Health and life science services use a fixed range starting at `http://localhost:4100`. Each health service keeps its port no matter which other services run or how many upstream services exist.
39
+
40
+ - **FHIR R4** on `http://localhost:4100` (create, read, update, JSON Patch, delete, versioning, `_history`, search, batch and transaction Bundles, and a CapabilityStatement at `/metadata`; SMART on FHIR is on the roadmap)
41
+
38
42
  Stripe webhooks configured with a secret include a `Stripe-Signature` header signed over the timestamp and raw request body.
39
43
 
40
44
  Slack event callbacks use `X-Slack-Request-Timestamp` and `X-Slack-Signature` when `slack.signing_secret` is configured. The signature is an HMAC-SHA256 over `v0:<timestamp>:<raw-body>`; configure the receiver with the same secret and verify against the unparsed request body. Existing event subscriptions remain unsigned when the secret is absent or empty.
@@ -82,13 +86,13 @@ try {
82
86
  }
83
87
  ```
84
88
 
85
- Use `defineConfig({ services: { acme: { emulator: acme }, github: { emulator: 'github' } } })` in `emulate.config.ts`. YAML/JSON entries accept local module paths and installed packages. `--config` selects a config explicitly; legacy flat configs and `--seed` remain supported. Node loads local TypeScript with path aliases and source locations without additional runtime dependencies. Node 26 supports erasable TypeScript only; compile enums and parameter properties to JavaScript before loading them. Node 24 also supports native TypeScript transforms.
89
+ Use `defineConfig({ services: { acme: { emulator: acme }, github: { emulator: 'github' } } })` in `emuhealth.config.ts` (`emulate.config.ts` also works). YAML/JSON entries accept local module paths and installed packages. `--config` selects a config explicitly; legacy flat configs and `--seed` remain supported. Node loads local TypeScript with path aliases and source locations without additional runtime dependencies. Node 26 supports erasable TypeScript only; compile enums and parameter properties to JavaScript before loading them. Node 24 also supports native TypeScript transforms.
86
90
 
87
91
  Custom state uses your own record shapes and IDs. Seeds replace the complete initial state. Reset restores the captured seed; successful watch reloads create a new baseline and reset the run. With config auto-discovery, watch mode also detects recognized config files created after startup. Instances are independent. Persistence is opt-in, with versioned snapshots and no cross-process locking. Use `port: 0` for HTTP tests, or `listen: false` to test without opening a port. Custom reset and close are awaitable. Framework adapters keep root-relative custom redirects under the service mount while preserving custom HTML bodies.
88
92
 
89
93
  Streamed responses persist state changes when their bodies finish or are canceled. Reset and close cancel active streams before running cleanup. `c.header('Set-Cookie', value, { append: true })` retains cookies already set on the response. In a Next.js route, export `OPTIONS` from `createEmulateHandler` to forward preflight requests and custom OPTIONS handlers.
90
94
 
91
- See the [custom emulator guide](https://emulate.dev/docs/custom-emulators) and [complete inventory example](examples/custom-api) for validation, middleware, persistence, adapters, package sharing, and troubleshooting.
95
+ See the [custom emulator guide](https://www.emuhealth.dev/docs/custom-emulators) and [complete inventory example](examples/custom-api) for validation, middleware, persistence, adapters, package sharing, and troubleshooting.
92
96
 
93
97
  ## CLI
94
98
 
@@ -122,7 +126,7 @@ npx emuhealth list
122
126
 
123
127
  | Flag | Default | Description |
124
128
  |------|---------|-------------|
125
- | `-p, --port` | `4000` | Base port (auto-increments per service) |
129
+ | `-p, --port` | `4000` | Base port (auto-increments per service; health services start at base + 100) |
126
130
  | `--host` | `127.0.0.1` | Listening address; use `0.0.0.0` to allow network access |
127
131
  | `-s, --service` | all | Comma-separated services to enable |
128
132
  | `--seed` | auto-detect | Path to seed config (YAML or JSON) |
@@ -130,7 +134,7 @@ npx emuhealth list
130
134
  | `--portless` | off | Serve over HTTPS via portless (auto-registers aliases) |
131
135
  | `--generated-secrets-file` | none | Generate omitted service secrets and write them to a new owner-only JSON file |
132
136
 
133
- The port can also be set via `EMULATE_PORT` or `PORT` environment variables.
137
+ The port can also be set via the `EMUHEALTH_PORT` or `PORT` environment variables. The upstream name `EMULATE_PORT` still works when `EMUHEALTH_PORT` is not set.
134
138
 
135
139
  For access from a container or another machine, use `npx emuhealth start --host 0.0.0.0`. Set `--base-url` to a URL reachable by those clients when using OAuth redirects or other advertised URLs.
136
140
 
@@ -158,13 +162,13 @@ If portless is not installed, emulate will prompt to install it (`npm i -g portl
158
162
 
159
163
  The `--portless` flag overwrites any existing portless aliases matching `*.emulate`. Aliases are removed automatically when emulate shuts down.
160
164
 
161
- For a custom base URL without portless (any reverse proxy), use `--base-url` or the `EMULATE_BASE_URL` env var:
165
+ For a custom base URL without portless (any reverse proxy), use `--base-url` or the `EMUHEALTH_BASE_URL` env var (`EMULATE_BASE_URL` also works):
162
166
 
163
167
  ```bash
164
168
  npx emuhealth start --base-url "https://{service}.myproxy.test"
165
169
  ```
166
170
 
167
- The `PORTLESS_URL` env var is automatically set by the `portless` CLI wrapper when running a command through it (e.g. `portless github.emulate emuhealth start`), typically to a value like `https://{service}.emulate.localhost`. It supports `{service}` interpolation, just like `--base-url` and `EMULATE_BASE_URL`. When no explicit `baseUrl` is provided, it is used as a fallback.
171
+ The `PORTLESS_URL` env var is automatically set by the `portless` CLI wrapper when running a command through it (e.g. `portless github.emulate emuhealth start`), typically to a value like `https://{service}.emulate.localhost`. It supports `{service}` interpolation, just like `--base-url` and `EMUHEALTH_BASE_URL`. When no explicit `baseUrl` is provided, it is used as a fallback.
168
172
 
169
173
  Per-service overrides are also supported in the seed config (these take highest priority over all other base URL sources):
170
174
 
@@ -258,7 +262,7 @@ afterAll(() => Promise.all([github.close(), vercel.close()]))
258
262
  | `port` | `4000` | Port for the HTTP server |
259
263
  | `hostname` | `127.0.0.1` | Listening address for built-in and custom emulators |
260
264
  | `seed` | none | Inline seed data (same shape as YAML config) |
261
- | `baseUrl` | none | Override advertised base URL. Per-service `baseUrl` in seed config takes highest priority, then this option, then `EMULATE_BASE_URL` env var (supports `{service}`), then `PORTLESS_URL` (supports `{service}`, automatically set by the `portless` CLI wrapper), then `http://localhost:<port>`. |
265
+ | `baseUrl` | none | Override advertised base URL. Per-service `baseUrl` in seed config takes highest priority, then this option, then `EMUHEALTH_BASE_URL` env var (or `EMULATE_BASE_URL`, supports `{service}`), then `PORTLESS_URL` (supports `{service}`, automatically set by the `portless` CLI wrapper), then `http://localhost:<port>`. |
262
266
 
263
267
  Use `hostname: '0.0.0.0'` to allow connections from containers or other machines, and `baseUrl` to advertise a URL reachable by those clients.
264
268
 
@@ -273,7 +277,7 @@ Use `hostname: '0.0.0.0'` to allow connections from containers or other machines
273
277
 
274
278
  ## Configuration
275
279
 
276
- Configuration is optional. The CLI auto-detects config files in this order: `emulate.config.yaml` / `.yml`, `emulate.config.json`, `service-emulator.config.yaml` / `.yml`, `service-emulator.config.json`. Or pass `--seed <file>` explicitly. Run `npx emuhealth init` to generate a starter file.
280
+ Configuration is optional. The CLI auto-detects config files in this order: `emuhealth.config.yaml` / `.yml`, `emuhealth.config.json`, then the upstream names `emulate.config.yaml` / `.yml`, `emulate.config.json`, `service-emulator.config.yaml` / `.yml`, `service-emulator.config.json`. An `emuhealth.config.*` file always wins over the upstream names. Or pass `--seed <file>` explicitly. Run `npx emuhealth init` to generate a starter file.
277
281
 
278
282
  ```yaml
279
283
  tokens:
package/dist/api.d.ts CHANGED
@@ -249,8 +249,11 @@ declare function defineConfig<Definitions extends Record<string, EmulatorDefinit
249
249
  tokens?: EmulateConfig["tokens"];
250
250
  }): typeof config;
251
251
 
252
+ declare const HEALTH_SERVICE_NAME_LIST: readonly ["fhir"];
253
+ type HealthServiceName = (typeof HEALTH_SERVICE_NAME_LIST)[number];
254
+
252
255
  declare const SERVICE_NAME_LIST: readonly ["vercel", "github", "google", "slack", "apple", "microsoft", "okta", "aws", "resend", "stripe", "mongoatlas", "clerk", "linear", "twilio"];
253
- type ServiceName = (typeof SERVICE_NAME_LIST)[number];
256
+ type ServiceName = (typeof SERVICE_NAME_LIST)[number] | HealthServiceName;
254
257
 
255
258
  interface SeedConfig {
256
259
  tokens?: Record<string, {
package/dist/api.js CHANGED
@@ -832,7 +832,7 @@ function createErrorHandler(documentationUrl) {
832
832
  };
833
833
  }
834
834
  var errorHandler = createErrorHandler();
835
- var isDebug = typeof process !== "undefined" && (process.env.DEBUG === "1" || process.env.DEBUG === "true" || process.env.EMULATE_DEBUG === "1");
835
+ var isDebug = typeof process !== "undefined" && (process.env.DEBUG === "1" || process.env.DEBUG === "true" || process.env.EMULATE_DEBUG === "1" || process.env.EMUHEALTH_DEBUG === "1");
836
836
  function debug(label, ...args) {
837
837
  if (isDebug) {
838
838
  console.log(`[${label}]`, ...args);
@@ -1256,15 +1256,17 @@ body{
1256
1256
  .checkout-form-side{padding:32px 20px;}
1257
1257
  }
1258
1258
  `;
1259
- var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
1259
+ var PROJECT_URL = "https://www.emuhealth.dev";
1260
+ var REPO_URL = "https://github.com/emuhealth/emuhealth";
1261
+ var POWERED_BY = `<div class="powered-by">Powered by <a href="${PROJECT_URL}" target="_blank" rel="noopener">emuhealth</a></div>`;
1260
1262
  function emuBar(service) {
1261
1263
  const title = service ? `${escapeHtml(service)} Emulator` : "Emulator";
1262
1264
  return `<div class="emu-bar">
1263
1265
  <span class="emu-bar-title">${title}</span>
1264
1266
  <nav class="emu-bar-links">
1265
- <a href="https://github.com/vercel-labs/emulate/issues" target="_blank" rel="noopener"><span class="full">Report Issue</span><span class="short">Report</span></a>
1266
- <a href="https://github.com/vercel-labs/emulate" target="_blank" rel="noopener"><span class="full">Source Code</span><span class="short">Source</span></a>
1267
- <a href="https://emulate.dev" target="_blank" rel="noopener"><span class="full">Learn More</span><span class="short">Learn</span></a>
1267
+ <a href="${REPO_URL}/issues" target="_blank" rel="noopener"><span class="full">Report Issue</span><span class="short">Report</span></a>
1268
+ <a href="${REPO_URL}" target="_blank" rel="noopener"><span class="full">Source Code</span><span class="short">Source</span></a>
1269
+ <a href="${PROJECT_URL}" target="_blank" rel="noopener"><span class="full">Learn More</span><span class="short">Learn</span></a>
1268
1270
  </nav>
1269
1271
  </div>`;
1270
1272
  }
@@ -1275,7 +1277,7 @@ function head(title) {
1275
1277
  <meta charset="utf-8"/>
1276
1278
  <meta name="viewport" content="width=device-width,initial-scale=1"/>
1277
1279
  <link rel="icon" href="/_emulate/favicon.ico"/>
1278
- <title>${escapeHtml(title)} | emulate</title>
1280
+ <title>${escapeHtml(title)} | emuhealth</title>
1279
1281
  <style>${CSS}</style>
1280
1282
  </head>`;
1281
1283
  }
@@ -1937,13 +1939,66 @@ function defineConfig(config) {
1937
1939
  return config;
1938
1940
  }
1939
1941
 
1942
+ // src/registry-health.ts
1943
+ var HEALTH_SERVICE_NAME_LIST = ["fhir"];
1944
+ var HEALTH_SERVICE_NAMES = HEALTH_SERVICE_NAME_LIST;
1945
+ var HEALTH_SERVICE_REGISTRY = {
1946
+ fhir: {
1947
+ label: "FHIR R4 server emulator",
1948
+ endpoints: "create, read, vread, update (including create on update), JSON Patch, logical delete, _history, search with includes and chaining, batch and transaction Bundles, CapabilityStatement at /metadata, ETag and If-Match",
1949
+ async load() {
1950
+ const mod = await import("./dist-WLTGCLZF.js");
1951
+ return { plugin: mod.fhirPlugin, seedFromConfig: mod.seedFromConfig };
1952
+ },
1953
+ defaultFallback() {
1954
+ return { login: "fhir-user", id: 1, scopes: [] };
1955
+ },
1956
+ initConfig: {
1957
+ fhir: {
1958
+ resources: [
1959
+ {
1960
+ resourceType: "Patient",
1961
+ id: "synthetic-patient-1",
1962
+ name: [{ family: "Example", given: ["Jamie"] }],
1963
+ gender: "female",
1964
+ birthDate: "1980-01-01"
1965
+ }
1966
+ ]
1967
+ }
1968
+ }
1969
+ }
1970
+ };
1971
+ var HEALTH_PORT_OFFSET = 100;
1972
+ function defaultServicePort(service, basePort, index, healthNames = HEALTH_SERVICE_NAMES) {
1973
+ const healthIndex = healthNames.indexOf(service);
1974
+ return healthIndex === -1 ? basePort + index : basePort + HEALTH_PORT_OFFSET + healthIndex;
1975
+ }
1976
+
1940
1977
  // src/registry.ts
1978
+ var SERVICE_NAME_LIST = [
1979
+ "vercel",
1980
+ "github",
1981
+ "google",
1982
+ "slack",
1983
+ "apple",
1984
+ "microsoft",
1985
+ "okta",
1986
+ "aws",
1987
+ "resend",
1988
+ "stripe",
1989
+ "mongoatlas",
1990
+ "clerk",
1991
+ "linear",
1992
+ "twilio"
1993
+ ];
1994
+ var SERVICE_NAMES = [...SERVICE_NAME_LIST, ...HEALTH_SERVICE_NAMES];
1941
1995
  var SERVICE_REGISTRY = {
1996
+ ...HEALTH_SERVICE_REGISTRY,
1942
1997
  vercel: {
1943
1998
  label: "Vercel REST API emulator",
1944
1999
  endpoints: "projects, deployments, domains, env vars, users, teams, file uploads, protection bypass, blob storage",
1945
2000
  async load() {
1946
- const mod = await import("./dist-QJ24W3VN.js");
2001
+ const mod = await import("./dist-YK2XJNH7.js");
1947
2002
  return { plugin: mod.vercelPlugin, seedFromConfig: mod.seedFromConfig };
1948
2003
  },
1949
2004
  defaultFallback(cfg) {
@@ -1970,7 +2025,7 @@ var SERVICE_REGISTRY = {
1970
2025
  label: "GitHub REST API emulator",
1971
2026
  endpoints: "users, repos, issues, PRs, comments, reviews, labels, milestones, branches, git data, orgs, teams, releases, webhooks, search, actions, checks, rate limit",
1972
2027
  async load() {
1973
- const mod = await import("./dist-7UXL7QGA.js");
2028
+ const mod = await import("./dist-M5TIXC2F.js");
1974
2029
  return {
1975
2030
  plugin: mod.githubPlugin,
1976
2031
  seedFromConfig: mod.seedFromConfig,
@@ -2034,7 +2089,7 @@ var SERVICE_REGISTRY = {
2034
2089
  label: "Google OAuth 2.0 / OpenID Connect + Gmail, Calendar, and Drive emulator",
2035
2090
  endpoints: "OAuth authorize, token exchange, userinfo, RS256 OIDC discovery and JWKS, token revocation, Gmail messages/drafts/threads/labels/history/settings, Calendar discovery/lists/events/freebusy, Drive files/uploads",
2036
2091
  async load() {
2037
- const mod = await import("./dist-EESUF62H.js");
2092
+ const mod = await import("./dist-575EVZLI.js");
2038
2093
  return { plugin: mod.googlePlugin, seedFromConfig: mod.seedFromConfig };
2039
2094
  },
2040
2095
  defaultFallback(cfg) {
@@ -2116,7 +2171,7 @@ var SERVICE_REGISTRY = {
2116
2171
  label: "Slack API emulator",
2117
2172
  endpoints: "auth, chat, conversations, users, profiles, presence, files, pins, bookmarks, views, reactions, team, OAuth, incoming webhooks, inspector",
2118
2173
  async load() {
2119
- const mod = await import("./dist-B5YXZCS3.js");
2174
+ const mod = await import("./dist-IMIDQ6CJ.js");
2120
2175
  return { plugin: mod.slackPlugin, seedFromConfig: mod.seedFromConfig };
2121
2176
  },
2122
2177
  defaultFallback() {
@@ -2197,7 +2252,7 @@ var SERVICE_REGISTRY = {
2197
2252
  label: "Apple Sign In / OAuth emulator",
2198
2253
  endpoints: "OAuth authorize, token exchange, JWKS",
2199
2254
  async load() {
2200
- const mod = await import("./dist-SOYGDR4W.js");
2255
+ const mod = await import("./dist-L3SNHIRB.js");
2201
2256
  return { plugin: mod.applePlugin, seedFromConfig: mod.seedFromConfig };
2202
2257
  },
2203
2258
  defaultFallback(cfg) {
@@ -2222,7 +2277,7 @@ var SERVICE_REGISTRY = {
2222
2277
  label: "Microsoft Entra ID OAuth 2.0 / OpenID Connect emulator",
2223
2278
  endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, Graph /me, logout, token revocation",
2224
2279
  async load() {
2225
- const mod = await import("./dist-J2Q7BESS.js");
2280
+ const mod = await import("./dist-ZGCIWJJM.js");
2226
2281
  return { plugin: mod.microsoftPlugin, seedFromConfig: mod.seedFromConfig };
2227
2282
  },
2228
2283
  defaultFallback(cfg) {
@@ -2247,7 +2302,7 @@ var SERVICE_REGISTRY = {
2247
2302
  label: "Okta OAuth 2.0 / OpenID Connect + management API emulator",
2248
2303
  endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo/introspect/revoke/logout, users, groups, apps, authorization servers",
2249
2304
  async load() {
2250
- const mod = await import("./dist-A2VVAQ3G.js");
2305
+ const mod = await import("./dist-EUMN2OAH.js");
2251
2306
  return { plugin: mod.oktaPlugin, seedFromConfig: mod.seedFromConfig };
2252
2307
  },
2253
2308
  defaultFallback(cfg) {
@@ -2275,7 +2330,7 @@ var SERVICE_REGISTRY = {
2275
2330
  label: "AWS cloud service emulator",
2276
2331
  endpoints: "S3 (buckets, objects), SQS (queues, messages), Cognito (User Pools, OAuth, JWTs, Identity Pools), IAM (users, roles, access keys), STS (assume role, caller identity)",
2277
2332
  async load() {
2278
- const mod = await import("./dist-PLRHPEKZ.js");
2333
+ const mod = await import("./dist-22OZW7MU.js");
2279
2334
  return { plugin: mod.awsPlugin, seedFromConfig: mod.seedFromConfig };
2280
2335
  },
2281
2336
  defaultFallback() {
@@ -2297,7 +2352,7 @@ var SERVICE_REGISTRY = {
2297
2352
  label: "Resend email API emulator",
2298
2353
  endpoints: "emails with idempotency, recipient restrictions, send attempts, domains, contacts, API keys, inbox UI",
2299
2354
  async load() {
2300
- const mod = await import("./dist-EGN5EUC3.js");
2355
+ const mod = await import("./dist-B4K5JXTS.js");
2301
2356
  return { plugin: mod.resendPlugin, seedFromConfig: mod.seedFromConfig };
2302
2357
  },
2303
2358
  defaultFallback() {
@@ -2314,7 +2369,7 @@ var SERVICE_REGISTRY = {
2314
2369
  label: "Stripe payments emulator",
2315
2370
  endpoints: "customers, payment methods, customer sessions, payment intents, charges, products, prices, checkout sessions, webhooks",
2316
2371
  async load() {
2317
- const mod = await import("./dist-FJPSLFJD.js");
2372
+ const mod = await import("./dist-KRJZAPPL.js");
2318
2373
  return { plugin: mod.stripePlugin, seedFromConfig: mod.seedFromConfig };
2319
2374
  },
2320
2375
  defaultFallback() {
@@ -2351,7 +2406,7 @@ var SERVICE_REGISTRY = {
2351
2406
  label: "Clerk authentication and user management emulator",
2352
2407
  endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo, users, email addresses, organizations, memberships, invitations, sessions",
2353
2408
  async load() {
2354
- const mod = await import("./dist-C6Y6IXLU.js");
2409
+ const mod = await import("./dist-YMRASK5I.js");
2355
2410
  return { plugin: mod.clerkPlugin, seedFromConfig: mod.seedFromConfig };
2356
2411
  },
2357
2412
  defaultFallback(cfg) {
@@ -2390,7 +2445,7 @@ var SERVICE_REGISTRY = {
2390
2445
  label: "Linear GraphQL API emulator",
2391
2446
  endpoints: "GraphQL, OAuth, issues, teams, users, workflow states, comments, labels, projects, cycles, webhooks, agents, inspector",
2392
2447
  async load() {
2393
- const mod = await import("./dist-M5AWLPBU.js");
2448
+ const mod = await import("./dist-ATXASBBA.js");
2394
2449
  return { plugin: mod.linearPlugin, seedFromConfig: mod.seedFromConfig };
2395
2450
  },
2396
2451
  defaultFallback(cfg) {
@@ -2455,7 +2510,7 @@ var SERVICE_REGISTRY = {
2455
2510
  label: "Twilio API emulator",
2456
2511
  endpoints: "accounts, API keys, phone numbers, Programmable Messaging, Messaging Services, Verify, Voice, webhooks, simulator, inspector",
2457
2512
  async load() {
2458
- const mod = await import("./dist-H6TJ2XTL.js");
2513
+ const mod = await import("./dist-KAYD3L2O.js");
2459
2514
  return { plugin: mod.twilioPlugin, seedFromConfig: mod.seedFromConfig };
2460
2515
  },
2461
2516
  defaultFallback(cfg) {
@@ -2509,6 +2564,11 @@ var SERVICE_REGISTRY = {
2509
2564
  }
2510
2565
  };
2511
2566
 
2567
+ // src/env.ts
2568
+ function brandedEnv(name) {
2569
+ return process.env[`EMUHEALTH_${name}`] || process.env[`EMULATE_${name}`] || void 0;
2570
+ }
2571
+
2512
2572
  // src/base-url.ts
2513
2573
  function resolveBaseUrl(opts) {
2514
2574
  if (opts.seedBaseUrl) {
@@ -2517,7 +2577,7 @@ function resolveBaseUrl(opts) {
2517
2577
  if (opts.baseUrl) {
2518
2578
  return opts.baseUrl.replace(/\{service\}/g, opts.service);
2519
2579
  }
2520
- const envBaseUrl = process.env.EMULATE_BASE_URL;
2580
+ const envBaseUrl = brandedEnv("BASE_URL");
2521
2581
  if (envBaseUrl) {
2522
2582
  return envBaseUrl.replace(/\{service\}/g, opts.service);
2523
2583
  }
@@ -2594,7 +2654,7 @@ function closeHttpServer(server) {
2594
2654
  });
2595
2655
  }
2596
2656
  async function createBuiltinEmulator(options) {
2597
- const { service, port = 4e3, seed: seedConfig } = options;
2657
+ const { service, port = defaultServicePort(service, 4e3, 0), seed: seedConfig } = options;
2598
2658
  const entry = SERVICE_REGISTRY[service];
2599
2659
  if (!entry) {
2600
2660
  throw new Error(`Unknown service: ${service}`);