emuhealth 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +10 -8
  2. package/dist/api.d.ts +4 -1
  3. package/dist/api.js +56 -21
  4. package/dist/api.js.map +1 -1
  5. package/dist/{chunk-GQ4IO5X5.js → chunk-OFHRZRRG.js} +58 -30
  6. package/dist/chunk-OFHRZRRG.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-QJ24W3VN.js → dist-YK2XJNH7.js} +9 -7
  28. package/dist/dist-YK2XJNH7.js.map +1 -0
  29. package/dist/{dist-C6Y6IXLU.js → dist-YMRASK5I.js} +9 -7
  30. package/dist/dist-YMRASK5I.js.map +1 -0
  31. package/dist/{dist-J2Q7BESS.js → dist-ZGCIWJJM.js} +9 -7
  32. package/dist/dist-ZGCIWJJM.js.map +1 -0
  33. package/dist/index.js +13 -11
  34. package/dist/index.js.map +1 -1
  35. package/dist/project-worker.js +1 -1
  36. package/package.json +7 -7
  37. package/dist/chunk-GQ4IO5X5.js.map +0 -1
  38. package/dist/dist-7UXL7QGA.js.map +0 -1
  39. package/dist/dist-A2VVAQ3G.js.map +0 -1
  40. package/dist/dist-B5YXZCS3.js.map +0 -1
  41. package/dist/dist-C6Y6IXLU.js.map +0 -1
  42. package/dist/dist-EGN5EUC3.js.map +0 -1
  43. package/dist/dist-FJPSLFJD.js.map +0 -1
  44. package/dist/dist-H6TJ2XTL.js.map +0 -1
  45. package/dist/dist-J2Q7BESS.js.map +0 -1
  46. package/dist/dist-M5AWLPBU.js.map +0 -1
  47. package/dist/dist-PLRHPEKZ.js.map +0 -1
  48. package/dist/dist-QJ24W3VN.js.map +0 -1
  49. package/dist/dist-SOYGDR4W.js.map +0 -1
package/README.md CHANGED
@@ -35,6 +35,8 @@ 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
+
38
40
  Stripe webhooks configured with a secret include a `Stripe-Signature` header signed over the timestamp and raw request body.
39
41
 
40
42
  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 +84,13 @@ try {
82
84
  }
83
85
  ```
84
86
 
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.
87
+ 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
88
 
87
89
  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
90
 
89
91
  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
92
 
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.
93
+ 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
94
 
93
95
  ## CLI
94
96
 
@@ -122,7 +124,7 @@ npx emuhealth list
122
124
 
123
125
  | Flag | Default | Description |
124
126
  |------|---------|-------------|
125
- | `-p, --port` | `4000` | Base port (auto-increments per service) |
127
+ | `-p, --port` | `4000` | Base port (auto-increments per service; health services start at base + 100) |
126
128
  | `--host` | `127.0.0.1` | Listening address; use `0.0.0.0` to allow network access |
127
129
  | `-s, --service` | all | Comma-separated services to enable |
128
130
  | `--seed` | auto-detect | Path to seed config (YAML or JSON) |
@@ -130,7 +132,7 @@ npx emuhealth list
130
132
  | `--portless` | off | Serve over HTTPS via portless (auto-registers aliases) |
131
133
  | `--generated-secrets-file` | none | Generate omitted service secrets and write them to a new owner-only JSON file |
132
134
 
133
- The port can also be set via `EMULATE_PORT` or `PORT` environment variables.
135
+ 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
136
 
135
137
  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
138
 
@@ -158,13 +160,13 @@ If portless is not installed, emulate will prompt to install it (`npm i -g portl
158
160
 
159
161
  The `--portless` flag overwrites any existing portless aliases matching `*.emulate`. Aliases are removed automatically when emulate shuts down.
160
162
 
161
- For a custom base URL without portless (any reverse proxy), use `--base-url` or the `EMULATE_BASE_URL` env var:
163
+ 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
164
 
163
165
  ```bash
164
166
  npx emuhealth start --base-url "https://{service}.myproxy.test"
165
167
  ```
166
168
 
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.
169
+ 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
170
 
169
171
  Per-service overrides are also supported in the seed config (these take highest priority over all other base URL sources):
170
172
 
@@ -258,7 +260,7 @@ afterAll(() => Promise.all([github.close(), vercel.close()]))
258
260
  | `port` | `4000` | Port for the HTTP server |
259
261
  | `hostname` | `127.0.0.1` | Listening address for built-in and custom emulators |
260
262
  | `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>`. |
263
+ | `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
264
 
263
265
  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
266
 
@@ -273,7 +275,7 @@ Use `hostname: '0.0.0.0'` to allow connections from containers or other machines
273
275
 
274
276
  ## Configuration
275
277
 
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.
278
+ 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
279
 
278
280
  ```yaml
279
281
  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 [];
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,41 @@ function defineConfig(config) {
1937
1939
  return config;
1938
1940
  }
1939
1941
 
1942
+ // src/registry-health.ts
1943
+ var HEALTH_SERVICE_NAME_LIST = [];
1944
+ var HEALTH_SERVICE_NAMES = HEALTH_SERVICE_NAME_LIST;
1945
+ var HEALTH_SERVICE_REGISTRY = {};
1946
+ var HEALTH_PORT_OFFSET = 100;
1947
+ function defaultServicePort(service, basePort, index, healthNames = HEALTH_SERVICE_NAMES) {
1948
+ const healthIndex = healthNames.indexOf(service);
1949
+ return healthIndex === -1 ? basePort + index : basePort + HEALTH_PORT_OFFSET + healthIndex;
1950
+ }
1951
+
1940
1952
  // src/registry.ts
1953
+ var SERVICE_NAME_LIST = [
1954
+ "vercel",
1955
+ "github",
1956
+ "google",
1957
+ "slack",
1958
+ "apple",
1959
+ "microsoft",
1960
+ "okta",
1961
+ "aws",
1962
+ "resend",
1963
+ "stripe",
1964
+ "mongoatlas",
1965
+ "clerk",
1966
+ "linear",
1967
+ "twilio"
1968
+ ];
1969
+ var SERVICE_NAMES = [...SERVICE_NAME_LIST, ...HEALTH_SERVICE_NAMES];
1941
1970
  var SERVICE_REGISTRY = {
1971
+ ...HEALTH_SERVICE_REGISTRY,
1942
1972
  vercel: {
1943
1973
  label: "Vercel REST API emulator",
1944
1974
  endpoints: "projects, deployments, domains, env vars, users, teams, file uploads, protection bypass, blob storage",
1945
1975
  async load() {
1946
- const mod = await import("./dist-QJ24W3VN.js");
1976
+ const mod = await import("./dist-YK2XJNH7.js");
1947
1977
  return { plugin: mod.vercelPlugin, seedFromConfig: mod.seedFromConfig };
1948
1978
  },
1949
1979
  defaultFallback(cfg) {
@@ -1970,7 +2000,7 @@ var SERVICE_REGISTRY = {
1970
2000
  label: "GitHub REST API emulator",
1971
2001
  endpoints: "users, repos, issues, PRs, comments, reviews, labels, milestones, branches, git data, orgs, teams, releases, webhooks, search, actions, checks, rate limit",
1972
2002
  async load() {
1973
- const mod = await import("./dist-7UXL7QGA.js");
2003
+ const mod = await import("./dist-M5TIXC2F.js");
1974
2004
  return {
1975
2005
  plugin: mod.githubPlugin,
1976
2006
  seedFromConfig: mod.seedFromConfig,
@@ -2034,7 +2064,7 @@ var SERVICE_REGISTRY = {
2034
2064
  label: "Google OAuth 2.0 / OpenID Connect + Gmail, Calendar, and Drive emulator",
2035
2065
  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
2066
  async load() {
2037
- const mod = await import("./dist-EESUF62H.js");
2067
+ const mod = await import("./dist-575EVZLI.js");
2038
2068
  return { plugin: mod.googlePlugin, seedFromConfig: mod.seedFromConfig };
2039
2069
  },
2040
2070
  defaultFallback(cfg) {
@@ -2116,7 +2146,7 @@ var SERVICE_REGISTRY = {
2116
2146
  label: "Slack API emulator",
2117
2147
  endpoints: "auth, chat, conversations, users, profiles, presence, files, pins, bookmarks, views, reactions, team, OAuth, incoming webhooks, inspector",
2118
2148
  async load() {
2119
- const mod = await import("./dist-B5YXZCS3.js");
2149
+ const mod = await import("./dist-IMIDQ6CJ.js");
2120
2150
  return { plugin: mod.slackPlugin, seedFromConfig: mod.seedFromConfig };
2121
2151
  },
2122
2152
  defaultFallback() {
@@ -2197,7 +2227,7 @@ var SERVICE_REGISTRY = {
2197
2227
  label: "Apple Sign In / OAuth emulator",
2198
2228
  endpoints: "OAuth authorize, token exchange, JWKS",
2199
2229
  async load() {
2200
- const mod = await import("./dist-SOYGDR4W.js");
2230
+ const mod = await import("./dist-L3SNHIRB.js");
2201
2231
  return { plugin: mod.applePlugin, seedFromConfig: mod.seedFromConfig };
2202
2232
  },
2203
2233
  defaultFallback(cfg) {
@@ -2222,7 +2252,7 @@ var SERVICE_REGISTRY = {
2222
2252
  label: "Microsoft Entra ID OAuth 2.0 / OpenID Connect emulator",
2223
2253
  endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, Graph /me, logout, token revocation",
2224
2254
  async load() {
2225
- const mod = await import("./dist-J2Q7BESS.js");
2255
+ const mod = await import("./dist-ZGCIWJJM.js");
2226
2256
  return { plugin: mod.microsoftPlugin, seedFromConfig: mod.seedFromConfig };
2227
2257
  },
2228
2258
  defaultFallback(cfg) {
@@ -2247,7 +2277,7 @@ var SERVICE_REGISTRY = {
2247
2277
  label: "Okta OAuth 2.0 / OpenID Connect + management API emulator",
2248
2278
  endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo/introspect/revoke/logout, users, groups, apps, authorization servers",
2249
2279
  async load() {
2250
- const mod = await import("./dist-A2VVAQ3G.js");
2280
+ const mod = await import("./dist-EUMN2OAH.js");
2251
2281
  return { plugin: mod.oktaPlugin, seedFromConfig: mod.seedFromConfig };
2252
2282
  },
2253
2283
  defaultFallback(cfg) {
@@ -2275,7 +2305,7 @@ var SERVICE_REGISTRY = {
2275
2305
  label: "AWS cloud service emulator",
2276
2306
  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
2307
  async load() {
2278
- const mod = await import("./dist-PLRHPEKZ.js");
2308
+ const mod = await import("./dist-22OZW7MU.js");
2279
2309
  return { plugin: mod.awsPlugin, seedFromConfig: mod.seedFromConfig };
2280
2310
  },
2281
2311
  defaultFallback() {
@@ -2297,7 +2327,7 @@ var SERVICE_REGISTRY = {
2297
2327
  label: "Resend email API emulator",
2298
2328
  endpoints: "emails with idempotency, recipient restrictions, send attempts, domains, contacts, API keys, inbox UI",
2299
2329
  async load() {
2300
- const mod = await import("./dist-EGN5EUC3.js");
2330
+ const mod = await import("./dist-B4K5JXTS.js");
2301
2331
  return { plugin: mod.resendPlugin, seedFromConfig: mod.seedFromConfig };
2302
2332
  },
2303
2333
  defaultFallback() {
@@ -2314,7 +2344,7 @@ var SERVICE_REGISTRY = {
2314
2344
  label: "Stripe payments emulator",
2315
2345
  endpoints: "customers, payment methods, customer sessions, payment intents, charges, products, prices, checkout sessions, webhooks",
2316
2346
  async load() {
2317
- const mod = await import("./dist-FJPSLFJD.js");
2347
+ const mod = await import("./dist-KRJZAPPL.js");
2318
2348
  return { plugin: mod.stripePlugin, seedFromConfig: mod.seedFromConfig };
2319
2349
  },
2320
2350
  defaultFallback() {
@@ -2351,7 +2381,7 @@ var SERVICE_REGISTRY = {
2351
2381
  label: "Clerk authentication and user management emulator",
2352
2382
  endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo, users, email addresses, organizations, memberships, invitations, sessions",
2353
2383
  async load() {
2354
- const mod = await import("./dist-C6Y6IXLU.js");
2384
+ const mod = await import("./dist-YMRASK5I.js");
2355
2385
  return { plugin: mod.clerkPlugin, seedFromConfig: mod.seedFromConfig };
2356
2386
  },
2357
2387
  defaultFallback(cfg) {
@@ -2390,7 +2420,7 @@ var SERVICE_REGISTRY = {
2390
2420
  label: "Linear GraphQL API emulator",
2391
2421
  endpoints: "GraphQL, OAuth, issues, teams, users, workflow states, comments, labels, projects, cycles, webhooks, agents, inspector",
2392
2422
  async load() {
2393
- const mod = await import("./dist-M5AWLPBU.js");
2423
+ const mod = await import("./dist-ATXASBBA.js");
2394
2424
  return { plugin: mod.linearPlugin, seedFromConfig: mod.seedFromConfig };
2395
2425
  },
2396
2426
  defaultFallback(cfg) {
@@ -2455,7 +2485,7 @@ var SERVICE_REGISTRY = {
2455
2485
  label: "Twilio API emulator",
2456
2486
  endpoints: "accounts, API keys, phone numbers, Programmable Messaging, Messaging Services, Verify, Voice, webhooks, simulator, inspector",
2457
2487
  async load() {
2458
- const mod = await import("./dist-H6TJ2XTL.js");
2488
+ const mod = await import("./dist-KAYD3L2O.js");
2459
2489
  return { plugin: mod.twilioPlugin, seedFromConfig: mod.seedFromConfig };
2460
2490
  },
2461
2491
  defaultFallback(cfg) {
@@ -2509,6 +2539,11 @@ var SERVICE_REGISTRY = {
2509
2539
  }
2510
2540
  };
2511
2541
 
2542
+ // src/env.ts
2543
+ function brandedEnv(name) {
2544
+ return process.env[`EMUHEALTH_${name}`] || process.env[`EMULATE_${name}`] || void 0;
2545
+ }
2546
+
2512
2547
  // src/base-url.ts
2513
2548
  function resolveBaseUrl(opts) {
2514
2549
  if (opts.seedBaseUrl) {
@@ -2517,7 +2552,7 @@ function resolveBaseUrl(opts) {
2517
2552
  if (opts.baseUrl) {
2518
2553
  return opts.baseUrl.replace(/\{service\}/g, opts.service);
2519
2554
  }
2520
- const envBaseUrl = process.env.EMULATE_BASE_URL;
2555
+ const envBaseUrl = brandedEnv("BASE_URL");
2521
2556
  if (envBaseUrl) {
2522
2557
  return envBaseUrl.replace(/\{service\}/g, opts.service);
2523
2558
  }
@@ -2594,7 +2629,7 @@ function closeHttpServer(server) {
2594
2629
  });
2595
2630
  }
2596
2631
  async function createBuiltinEmulator(options) {
2597
- const { service, port = 4e3, seed: seedConfig } = options;
2632
+ const { service, port = defaultServicePort(service, 4e3, 0), seed: seedConfig } = options;
2598
2633
  const entry = SERVICE_REGISTRY[service];
2599
2634
  if (!entry) {
2600
2635
  throw new Error(`Unknown service: ${service}`);