emulate 0.4.1 → 0.5.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 (50) hide show
  1. package/README.md +59 -10
  2. package/dist/api.d.ts +2 -1
  3. package/dist/api.js +232 -96
  4. package/dist/api.js.map +1 -1
  5. package/dist/{chunk-TEPNEZ63.js → chunk-AQ2CLRU3.js} +26 -23
  6. package/dist/chunk-AQ2CLRU3.js.map +1 -0
  7. package/dist/chunk-WVQMFHQM.js +83 -0
  8. package/dist/chunk-WVQMFHQM.js.map +1 -0
  9. package/dist/{dist-RDFBZ5O6.js → dist-4X2KPMAJ.js} +212 -47
  10. package/dist/dist-4X2KPMAJ.js.map +1 -0
  11. package/dist/{dist-OTJZRQ3Q.js → dist-5JVGPOL3.js} +217 -75
  12. package/dist/dist-5JVGPOL3.js.map +1 -0
  13. package/dist/{dist-G7WQPZ3Y.js → dist-CE6BUCWQ.js} +211 -60
  14. package/dist/dist-CE6BUCWQ.js.map +1 -0
  15. package/dist/{dist-6JFNJPUU.js → dist-CFST4X4K.js} +172 -22
  16. package/dist/dist-CFST4X4K.js.map +1 -0
  17. package/dist/{dist-YOVM5HEY.js → dist-ENKE2S7V.js} +521 -60
  18. package/dist/dist-ENKE2S7V.js.map +1 -0
  19. package/dist/{dist-RMK3BS5M.js → dist-ETHHYBGF.js} +197 -33
  20. package/dist/dist-ETHHYBGF.js.map +1 -0
  21. package/dist/{dist-QMOJM6DV.js → dist-IBXD3O6A.js} +239 -54
  22. package/dist/dist-IBXD3O6A.js.map +1 -0
  23. package/dist/dist-J6LHUR52.js +1899 -0
  24. package/dist/dist-J6LHUR52.js.map +1 -0
  25. package/dist/{dist-6EW7SSOZ.js → dist-KKTYBE5S.js} +391 -222
  26. package/dist/dist-KKTYBE5S.js.map +1 -0
  27. package/dist/{dist-VVXVP5EZ.js → dist-LDUHEJAN.js} +553 -91
  28. package/dist/dist-LDUHEJAN.js.map +1 -0
  29. package/dist/{dist-B674PYKV.js → dist-PWGOAQC6.js} +22 -43
  30. package/dist/dist-PWGOAQC6.js.map +1 -0
  31. package/dist/{dist-H6JYGQM4.js → dist-REDHDZ3V.js} +272 -157
  32. package/dist/dist-REDHDZ3V.js.map +1 -0
  33. package/dist/fonts/favicon.ico +0 -0
  34. package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
  35. package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
  36. package/dist/index.js +365 -108
  37. package/dist/index.js.map +1 -1
  38. package/package.json +17 -14
  39. package/dist/chunk-TEPNEZ63.js.map +0 -1
  40. package/dist/dist-6EW7SSOZ.js.map +0 -1
  41. package/dist/dist-6JFNJPUU.js.map +0 -1
  42. package/dist/dist-B674PYKV.js.map +0 -1
  43. package/dist/dist-G7WQPZ3Y.js.map +0 -1
  44. package/dist/dist-H6JYGQM4.js.map +0 -1
  45. package/dist/dist-OTJZRQ3Q.js.map +0 -1
  46. package/dist/dist-QMOJM6DV.js.map +0 -1
  47. package/dist/dist-RDFBZ5O6.js.map +0 -1
  48. package/dist/dist-RMK3BS5M.js.map +0 -1
  49. package/dist/dist-VVXVP5EZ.js.map +0 -1
  50. package/dist/dist-YOVM5HEY.js.map +0 -1
package/README.md CHANGED
@@ -50,9 +50,50 @@ emulate list
50
50
  | `-p, --port` | `4000` | Base port (auto-increments per service) |
51
51
  | `-s, --service` | all | Comma-separated services to enable |
52
52
  | `--seed` | auto-detect | Path to seed config (YAML or JSON) |
53
+ | `--base-url` | none | Override advertised base URL (supports `{service}` template) |
54
+ | `--portless` | off | Serve over HTTPS via portless (auto-registers aliases) |
53
55
 
54
56
  The port can also be set via `EMULATE_PORT` or `PORT` environment variables.
55
57
 
58
+ ## HTTPS with portless
59
+
60
+ [portless](https://github.com/vercel-labs/portless) gives emulators trusted HTTPS URLs with auto-generated certs and no browser warnings.
61
+
62
+ ```bash
63
+ # Start the portless proxy (first time only)
64
+ portless proxy start
65
+
66
+ # Start emulate with portless integration
67
+ emulate start --portless
68
+ ```
69
+
70
+ Each service registers as a portless alias and gets a named HTTPS URL:
71
+
72
+ ```
73
+ github https://github.emulate.localhost
74
+ google https://google.emulate.localhost
75
+ slack https://slack.emulate.localhost
76
+ ```
77
+
78
+ If portless is not installed, emulate will prompt to install it (`npm i -g portless`).
79
+
80
+ The `--portless` flag overwrites any existing portless aliases matching `*.emulate`. Aliases are removed automatically when emulate shuts down.
81
+
82
+ For a custom base URL without portless (any reverse proxy), use `--base-url` or the `EMULATE_BASE_URL` env var:
83
+
84
+ ```bash
85
+ emulate start --base-url "https://{service}.myproxy.test"
86
+ ```
87
+
88
+ The `PORTLESS_URL` env var is automatically set by the `portless` CLI wrapper when running a command through it (e.g. `portless github.emulate emulate 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.
89
+
90
+ Per-service overrides are also supported in the seed config (these take highest priority over all other base URL sources):
91
+
92
+ ```yaml
93
+ github:
94
+ baseUrl: https://github.emulate.localhost
95
+ ```
96
+
56
97
  ## Programmatic API
57
98
 
58
99
  ```bash
@@ -103,6 +144,7 @@ afterAll(() => Promise.all([github.close(), vercel.close()]))
103
144
  | `service` | *(required)* | Service name: `'vercel'`, `'github'`, `'google'`, `'slack'`, `'apple'`, `'microsoft'`, or `'aws'` |
104
145
  | `port` | `4000` | Port for the HTTP server |
105
146
  | `seed` | none | Inline seed data (same shape as YAML config) |
147
+ | `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>`. |
106
148
 
107
149
  ### Instance methods
108
150
 
@@ -153,6 +195,9 @@ google:
153
195
  users:
154
196
  - email: testuser@example.com
155
197
  name: Test User
198
+ - email: admin@acme.com
199
+ name: Admin
200
+ hd: acme.com
156
201
  oauth_clients:
157
202
  - client_id: my-client-id.apps.googleusercontent.com
158
203
  client_secret: GOCSPX-secret
@@ -612,18 +657,22 @@ Microsoft Entra ID (Azure AD) v2.0 OAuth 2.0 and OpenID Connect emulation with a
612
657
 
613
658
  ## AWS
614
659
 
615
- S3, SQS, IAM, and STS emulation with REST-style S3 paths and query-style SQS/IAM/STS endpoints. All responses use AWS-compatible XML.
660
+ S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths and query-style SQS/IAM/STS endpoints. All responses use AWS-compatible XML.
616
661
 
617
662
  ### S3
618
- - `GET /s3/` - list all buckets
619
- - `PUT /s3/:bucket` - create bucket
620
- - `DELETE /s3/:bucket` - delete bucket
621
- - `HEAD /s3/:bucket` - check existence
622
- - `GET /s3/:bucket` - list objects (prefix, delimiter, max-keys)
623
- - `PUT /s3/:bucket/:key` - put object (supports copy via `x-amz-copy-source`)
624
- - `GET /s3/:bucket/:key` - get object
625
- - `HEAD /s3/:bucket/:key` - head object
626
- - `DELETE /s3/:bucket/:key` - delete object
663
+
664
+ S3 routes use root paths matching the real AWS S3 wire format, so the official AWS SDK works out of the box with `forcePathStyle: true`. Legacy `/s3/` prefixed paths are also supported for backward compatibility.
665
+
666
+ - `GET /` - list all buckets
667
+ - `PUT /:bucket` - create bucket
668
+ - `DELETE /:bucket` - delete bucket
669
+ - `HEAD /:bucket` - check existence
670
+ - `GET /:bucket` - list objects (prefix, delimiter, max-keys, continuation-token, start-after)
671
+ - `POST /:bucket` - presigned POST upload (browser-style multipart form with policy validation)
672
+ - `PUT /:bucket/:key` - put object (supports copy via `x-amz-copy-source`)
673
+ - `GET /:bucket/:key` - get object
674
+ - `HEAD /:bucket/:key` - head object
675
+ - `DELETE /:bucket/:key` - delete object
627
676
 
628
677
  ### SQS
629
678
  All operations via `POST /sqs/` with `Action` parameter:
package/dist/api.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- declare const SERVICE_NAME_LIST: readonly ["vercel", "github", "google", "slack", "apple", "microsoft", "okta", "aws", "resend", "stripe", "mongoatlas"];
1
+ declare const SERVICE_NAME_LIST: readonly ["vercel", "github", "google", "slack", "apple", "microsoft", "okta", "aws", "resend", "stripe", "mongoatlas", "clerk"];
2
2
  type ServiceName = (typeof SERVICE_NAME_LIST)[number];
3
3
 
4
4
  interface SeedConfig {
@@ -12,6 +12,7 @@ interface EmulatorOptions {
12
12
  service: ServiceName;
13
13
  port?: number;
14
14
  seed?: SeedConfig;
15
+ baseUrl?: string;
15
16
  }
16
17
  interface Emulator {
17
18
  url: string;
package/dist/api.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  Hono,
7
7
  cors
8
- } from "./chunk-TEPNEZ63.js";
8
+ } from "./chunk-AQ2CLRU3.js";
9
9
 
10
10
  // ../@emulators/core/dist/index.js
11
11
  import { createHmac } from "crypto";
@@ -387,9 +387,7 @@ function authMiddleware(tokens, appKeyResolver, fallbackUser) {
387
387
  if (token.startsWith("eyJ") && appKeyResolver) {
388
388
  try {
389
389
  const [, payloadB64] = token.split(".");
390
- const payload = JSON.parse(
391
- Buffer.from(payloadB64, "base64url").toString()
392
- );
390
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
393
391
  const appId = typeof payload.iss === "string" ? parseInt(payload.iss, 10) : payload.iss;
394
392
  if (typeof appId === "number" && !isNaN(appId)) {
395
393
  const appInfo = appKeyResolver(appId);
@@ -426,6 +424,7 @@ var FONTS = {
426
424
  "geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
427
425
  "GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
428
426
  };
427
+ var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
429
428
  function registerFontRoutes(app) {
430
429
  app.get("/_emulate/fonts/:name", (c) => {
431
430
  const name = c.req.param("name");
@@ -439,6 +438,14 @@ function registerFontRoutes(app) {
439
438
  }
440
439
  });
441
440
  });
441
+ app.get("/_emulate/favicon.ico", (c) => {
442
+ return new Response(FAVICON, {
443
+ headers: {
444
+ "Content-Type": "image/x-icon",
445
+ "Cache-Control": "public, max-age=31536000, immutable"
446
+ }
447
+ });
448
+ });
442
449
  }
443
450
  function createServer(plugin, options = {}) {
444
451
  const port = options.port ?? 4e3;
@@ -513,7 +520,7 @@ var SERVICE_REGISTRY = {
513
520
  label: "Vercel REST API emulator",
514
521
  endpoints: "projects, deployments, domains, env vars, users, teams, file uploads, protection bypass",
515
522
  async load() {
516
- const mod = await import("./dist-RDFBZ5O6.js");
523
+ const mod = await import("./dist-4X2KPMAJ.js");
517
524
  return { plugin: mod.vercelPlugin, seedFromConfig: mod.seedFromConfig };
518
525
  },
519
526
  defaultFallback(cfg) {
@@ -525,12 +532,14 @@ var SERVICE_REGISTRY = {
525
532
  users: [{ username: "developer", name: "Developer", email: "dev@example.com" }],
526
533
  teams: [{ slug: "my-team", name: "My Team" }],
527
534
  projects: [{ name: "my-app", team: "my-team", framework: "nextjs" }],
528
- integrations: [{
529
- client_id: "oac_example_client_id",
530
- client_secret: "example_client_secret",
531
- name: "My Vercel App",
532
- redirect_uris: ["http://localhost:3000/api/auth/callback/vercel"]
533
- }]
535
+ integrations: [
536
+ {
537
+ client_id: "oac_example_client_id",
538
+ client_secret: "example_client_secret",
539
+ name: "My Vercel App",
540
+ redirect_uris: ["http://localhost:3000/api/auth/callback/vercel"]
541
+ }
542
+ ]
534
543
  }
535
544
  }
536
545
  },
@@ -538,7 +547,7 @@ var SERVICE_REGISTRY = {
538
547
  label: "GitHub REST API emulator",
539
548
  endpoints: "users, repos, issues, PRs, comments, reviews, labels, milestones, branches, git data, orgs, teams, releases, webhooks, search, actions, checks, rate limit",
540
549
  async load() {
541
- const mod = await import("./dist-H6JYGQM4.js");
550
+ const mod = await import("./dist-REDHDZ3V.js");
542
551
  return {
543
552
  plugin: mod.githubPlugin,
544
553
  seedFromConfig: mod.seedFromConfig,
@@ -562,25 +571,42 @@ var SERVICE_REGISTRY = {
562
571
  },
563
572
  initConfig: {
564
573
  github: {
565
- users: [{
566
- login: "octocat",
567
- name: "The Octocat",
568
- email: "octocat@github.com",
569
- bio: "I am the Octocat",
570
- company: "GitHub",
571
- location: "San Francisco"
572
- }],
574
+ users: [
575
+ {
576
+ login: "octocat",
577
+ name: "The Octocat",
578
+ email: "octocat@github.com",
579
+ bio: "I am the Octocat",
580
+ company: "GitHub",
581
+ location: "San Francisco"
582
+ }
583
+ ],
573
584
  orgs: [{ login: "my-org", name: "My Organization", description: "A test organization" }],
574
585
  repos: [
575
- { owner: "octocat", name: "hello-world", description: "My first repository", language: "JavaScript", topics: ["hello", "world"], auto_init: true },
576
- { owner: "my-org", name: "org-repo", description: "An organization repository", language: "TypeScript", auto_init: true }
586
+ {
587
+ owner: "octocat",
588
+ name: "hello-world",
589
+ description: "My first repository",
590
+ language: "JavaScript",
591
+ topics: ["hello", "world"],
592
+ auto_init: true
593
+ },
594
+ {
595
+ owner: "my-org",
596
+ name: "org-repo",
597
+ description: "An organization repository",
598
+ language: "TypeScript",
599
+ auto_init: true
600
+ }
577
601
  ],
578
- oauth_apps: [{
579
- client_id: "Iv1.example_client_id",
580
- client_secret: "example_client_secret",
581
- name: "My App",
582
- redirect_uris: ["http://localhost:3000/api/auth/callback/github"]
583
- }]
602
+ oauth_apps: [
603
+ {
604
+ client_id: "Iv1.example_client_id",
605
+ client_secret: "example_client_secret",
606
+ name: "My App",
607
+ redirect_uris: ["http://localhost:3000/api/auth/callback/github"]
608
+ }
609
+ ]
584
610
  }
585
611
  }
586
612
  },
@@ -588,7 +614,7 @@ var SERVICE_REGISTRY = {
588
614
  label: "Google OAuth 2.0 / OpenID Connect + Gmail, Calendar, and Drive emulator",
589
615
  endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, token revocation, Gmail messages/drafts/threads/labels/history/settings, Calendar lists/events/freebusy, Drive files/uploads",
590
616
  async load() {
591
- const mod = await import("./dist-6EW7SSOZ.js");
617
+ const mod = await import("./dist-KKTYBE5S.js");
592
618
  return { plugin: mod.googlePlugin, seedFromConfig: mod.seedFromConfig };
593
619
  },
594
620
  defaultFallback(cfg) {
@@ -597,34 +623,72 @@ var SERVICE_REGISTRY = {
597
623
  },
598
624
  initConfig: {
599
625
  google: {
600
- users: [{ email: "testuser@example.com", name: "Test User", picture: "https://lh3.googleusercontent.com/a/default-user", email_verified: true }],
601
- oauth_clients: [{
602
- client_id: "example-client-id.apps.googleusercontent.com",
603
- client_secret: "GOCSPX-example_secret",
604
- name: "Code App (Google)",
605
- redirect_uris: ["http://localhost:3000/api/auth/callback/google"]
606
- }],
607
- labels: [{ id: "Label_ops", user_email: "testuser@example.com", name: "Ops/Review", color_background: "#DDEEFF", color_text: "#111111" }],
608
- messages: [{
609
- id: "msg_welcome",
610
- user_email: "testuser@example.com",
611
- from: "welcome@example.com",
612
- to: "testuser@example.com",
613
- subject: "Welcome to the Gmail emulator",
614
- body_text: "You can now test Gmail, Calendar, and Drive flows locally.",
615
- label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
616
- date: "2025-01-04T10:00:00.000Z"
617
- }],
618
- calendars: [{ id: "primary", user_email: "testuser@example.com", summary: "testuser@example.com", primary: true, selected: true, time_zone: "UTC" }],
619
- calendar_events: [{
620
- id: "evt_kickoff",
621
- user_email: "testuser@example.com",
622
- calendar_id: "primary",
623
- summary: "Project Kickoff",
624
- start_date_time: "2025-01-10T09:00:00.000Z",
625
- end_date_time: "2025-01-10T09:30:00.000Z"
626
- }],
627
- drive_items: [{ id: "drv_docs", user_email: "testuser@example.com", name: "Docs", mime_type: "application/vnd.google-apps.folder", parent_ids: ["root"] }]
626
+ users: [
627
+ {
628
+ email: "testuser@example.com",
629
+ name: "Test User",
630
+ picture: "https://lh3.googleusercontent.com/a/default-user",
631
+ email_verified: true
632
+ }
633
+ ],
634
+ oauth_clients: [
635
+ {
636
+ client_id: "example-client-id.apps.googleusercontent.com",
637
+ client_secret: "GOCSPX-example_secret",
638
+ name: "Code App (Google)",
639
+ redirect_uris: ["http://localhost:3000/api/auth/callback/google"]
640
+ }
641
+ ],
642
+ labels: [
643
+ {
644
+ id: "Label_ops",
645
+ user_email: "testuser@example.com",
646
+ name: "Ops/Review",
647
+ color_background: "#DDEEFF",
648
+ color_text: "#111111"
649
+ }
650
+ ],
651
+ messages: [
652
+ {
653
+ id: "msg_welcome",
654
+ user_email: "testuser@example.com",
655
+ from: "welcome@example.com",
656
+ to: "testuser@example.com",
657
+ subject: "Welcome to the Gmail emulator",
658
+ body_text: "You can now test Gmail, Calendar, and Drive flows locally.",
659
+ label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
660
+ date: "2025-01-04T10:00:00.000Z"
661
+ }
662
+ ],
663
+ calendars: [
664
+ {
665
+ id: "primary",
666
+ user_email: "testuser@example.com",
667
+ summary: "testuser@example.com",
668
+ primary: true,
669
+ selected: true,
670
+ time_zone: "UTC"
671
+ }
672
+ ],
673
+ calendar_events: [
674
+ {
675
+ id: "evt_kickoff",
676
+ user_email: "testuser@example.com",
677
+ calendar_id: "primary",
678
+ summary: "Project Kickoff",
679
+ start_date_time: "2025-01-10T09:00:00.000Z",
680
+ end_date_time: "2025-01-10T09:30:00.000Z"
681
+ }
682
+ ],
683
+ drive_items: [
684
+ {
685
+ id: "drv_docs",
686
+ user_email: "testuser@example.com",
687
+ name: "Docs",
688
+ mime_type: "application/vnd.google-apps.folder",
689
+ parent_ids: ["root"]
690
+ }
691
+ ]
628
692
  }
629
693
  }
630
694
  },
@@ -632,7 +696,7 @@ var SERVICE_REGISTRY = {
632
696
  label: "Slack API emulator",
633
697
  endpoints: "auth, chat, conversations, users, reactions, team, OAuth, incoming webhooks",
634
698
  async load() {
635
- const mod = await import("./dist-G7WQPZ3Y.js");
699
+ const mod = await import("./dist-CE6BUCWQ.js");
636
700
  return { plugin: mod.slackPlugin, seedFromConfig: mod.seedFromConfig };
637
701
  },
638
702
  defaultFallback() {
@@ -642,14 +706,19 @@ var SERVICE_REGISTRY = {
642
706
  slack: {
643
707
  team: { name: "My Workspace", domain: "my-workspace" },
644
708
  users: [{ name: "developer", real_name: "Developer", email: "dev@example.com" }],
645
- channels: [{ name: "general", topic: "General discussion" }, { name: "random", topic: "Random stuff" }],
709
+ channels: [
710
+ { name: "general", topic: "General discussion" },
711
+ { name: "random", topic: "Random stuff" }
712
+ ],
646
713
  bots: [{ name: "my-bot" }],
647
- oauth_apps: [{
648
- client_id: "12345.67890",
649
- client_secret: "example_client_secret",
650
- name: "My Slack App",
651
- redirect_uris: ["http://localhost:3000/api/auth/callback/slack"]
652
- }]
714
+ oauth_apps: [
715
+ {
716
+ client_id: "12345.67890",
717
+ client_secret: "example_client_secret",
718
+ name: "My Slack App",
719
+ redirect_uris: ["http://localhost:3000/api/auth/callback/slack"]
720
+ }
721
+ ]
653
722
  }
654
723
  }
655
724
  },
@@ -657,7 +726,7 @@ var SERVICE_REGISTRY = {
657
726
  label: "Apple Sign In / OAuth emulator",
658
727
  endpoints: "OAuth authorize, token exchange, JWKS",
659
728
  async load() {
660
- const mod = await import("./dist-6JFNJPUU.js");
729
+ const mod = await import("./dist-CFST4X4K.js");
661
730
  return { plugin: mod.applePlugin, seedFromConfig: mod.seedFromConfig };
662
731
  },
663
732
  defaultFallback(cfg) {
@@ -667,12 +736,14 @@ var SERVICE_REGISTRY = {
667
736
  initConfig: {
668
737
  apple: {
669
738
  users: [{ email: "testuser@icloud.com", name: "Test User" }],
670
- oauth_clients: [{
671
- client_id: "com.example.app",
672
- team_id: "TEAM001",
673
- name: "My Apple App",
674
- redirect_uris: ["http://localhost:3000/api/auth/callback/apple"]
675
- }]
739
+ oauth_clients: [
740
+ {
741
+ client_id: "com.example.app",
742
+ team_id: "TEAM001",
743
+ name: "My Apple App",
744
+ redirect_uris: ["http://localhost:3000/api/auth/callback/apple"]
745
+ }
746
+ ]
676
747
  }
677
748
  }
678
749
  },
@@ -680,7 +751,7 @@ var SERVICE_REGISTRY = {
680
751
  label: "Microsoft Entra ID OAuth 2.0 / OpenID Connect emulator",
681
752
  endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, Graph /me, logout, token revocation",
682
753
  async load() {
683
- const mod = await import("./dist-RMK3BS5M.js");
754
+ const mod = await import("./dist-ETHHYBGF.js");
684
755
  return { plugin: mod.microsoftPlugin, seedFromConfig: mod.seedFromConfig };
685
756
  },
686
757
  defaultFallback(cfg) {
@@ -690,12 +761,14 @@ var SERVICE_REGISTRY = {
690
761
  initConfig: {
691
762
  microsoft: {
692
763
  users: [{ email: "testuser@outlook.com", name: "Test User" }],
693
- oauth_clients: [{
694
- client_id: "example-client-id",
695
- client_secret: "example-client-secret",
696
- name: "My Microsoft App",
697
- redirect_uris: ["http://localhost:3000/api/auth/callback/microsoft-entra-id"]
698
- }]
764
+ oauth_clients: [
765
+ {
766
+ client_id: "example-client-id",
767
+ client_secret: "example-client-secret",
768
+ name: "My Microsoft App",
769
+ redirect_uris: ["http://localhost:3000/api/auth/callback/microsoft-entra-id"]
770
+ }
771
+ ]
699
772
  }
700
773
  }
701
774
  },
@@ -703,7 +776,7 @@ var SERVICE_REGISTRY = {
703
776
  label: "Okta OAuth 2.0 / OpenID Connect + management API emulator",
704
777
  endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo/introspect/revoke/logout, users, groups, apps, authorization servers",
705
778
  async load() {
706
- const mod = await import("./dist-OTJZRQ3Q.js");
779
+ const mod = await import("./dist-5JVGPOL3.js");
707
780
  return { plugin: mod.oktaPlugin, seedFromConfig: mod.seedFromConfig };
708
781
  },
709
782
  defaultFallback(cfg) {
@@ -715,13 +788,15 @@ var SERVICE_REGISTRY = {
715
788
  users: [{ login: "testuser@okta.local", email: "testuser@okta.local", first_name: "Test", last_name: "User" }],
716
789
  groups: [{ name: "Everyone", description: "All users", type: "BUILT_IN", okta_id: "00g_everyone" }],
717
790
  authorization_servers: [{ id: "default", name: "default", audiences: ["api://default"] }],
718
- oauth_clients: [{
719
- client_id: "okta-test-client",
720
- client_secret: "okta-test-secret",
721
- name: "Sample OIDC Client",
722
- redirect_uris: ["http://localhost:3000/callback"],
723
- auth_server_id: "default"
724
- }]
791
+ oauth_clients: [
792
+ {
793
+ client_id: "okta-test-client",
794
+ client_secret: "okta-test-secret",
795
+ name: "Sample OIDC Client",
796
+ redirect_uris: ["http://localhost:3000/callback"],
797
+ auth_server_id: "default"
798
+ }
799
+ ]
725
800
  }
726
801
  }
727
802
  },
@@ -729,7 +804,7 @@ var SERVICE_REGISTRY = {
729
804
  label: "AWS cloud service emulator",
730
805
  endpoints: "S3 (buckets, objects), SQS (queues, messages), IAM (users, roles, access keys), STS (assume role, caller identity)",
731
806
  async load() {
732
- const mod = await import("./dist-VVXVP5EZ.js");
807
+ const mod = await import("./dist-LDUHEJAN.js");
733
808
  return { plugin: mod.awsPlugin, seedFromConfig: mod.seedFromConfig };
734
809
  },
735
810
  defaultFallback() {
@@ -751,7 +826,7 @@ var SERVICE_REGISTRY = {
751
826
  label: "Resend email API emulator",
752
827
  endpoints: "emails, domains, contacts, API keys, inbox UI",
753
828
  async load() {
754
- const mod = await import("./dist-QMOJM6DV.js");
829
+ const mod = await import("./dist-IBXD3O6A.js");
755
830
  return { plugin: mod.resendPlugin, seedFromConfig: mod.seedFromConfig };
756
831
  },
757
832
  defaultFallback() {
@@ -766,9 +841,9 @@ var SERVICE_REGISTRY = {
766
841
  },
767
842
  stripe: {
768
843
  label: "Stripe payments emulator",
769
- endpoints: "customers, payment intents, charges, products, prices, checkout sessions, webhooks",
844
+ endpoints: "customers, payment methods, customer sessions, payment intents, charges, products, prices, checkout sessions, webhooks",
770
845
  async load() {
771
- const mod = await import("./dist-YOVM5HEY.js");
846
+ const mod = await import("./dist-ENKE2S7V.js");
772
847
  return { plugin: mod.stripePlugin, seedFromConfig: mod.seedFromConfig };
773
848
  },
774
849
  defaultFallback() {
@@ -786,7 +861,7 @@ var SERVICE_REGISTRY = {
786
861
  label: "MongoDB Atlas service emulator",
787
862
  endpoints: "Atlas Admin API v2 (projects, clusters, database users, databases, collections), Atlas Data API v1 (findOne, find, insertOne, insertMany, updateOne, updateMany, deleteOne, deleteMany, aggregate)",
788
863
  async load() {
789
- const mod = await import("./dist-B674PYKV.js");
864
+ const mod = await import("./dist-PWGOAQC6.js");
790
865
  return { plugin: mod.mongoatlasPlugin, seedFromConfig: mod.seedFromConfig };
791
866
  },
792
867
  defaultFallback() {
@@ -800,11 +875,71 @@ var SERVICE_REGISTRY = {
800
875
  databases: [{ cluster: "Cluster0", name: "test", collections: ["items"] }]
801
876
  }
802
877
  }
878
+ },
879
+ clerk: {
880
+ label: "Clerk authentication and user management emulator",
881
+ endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo, users, email addresses, organizations, memberships, invitations, sessions",
882
+ async load() {
883
+ const mod = await import("./dist-J6LHUR52.js");
884
+ return { plugin: mod.clerkPlugin, seedFromConfig: mod.seedFromConfig };
885
+ },
886
+ defaultFallback(cfg) {
887
+ const firstEmail = cfg?.users?.[0]?.email_addresses?.[0] ?? "test@example.com";
888
+ return { login: firstEmail, id: 1, scopes: [] };
889
+ },
890
+ initConfig: {
891
+ clerk: {
892
+ users: [
893
+ {
894
+ first_name: "Test",
895
+ last_name: "User",
896
+ email_addresses: ["test@example.com"],
897
+ password: "clerk_test_password"
898
+ }
899
+ ],
900
+ organizations: [
901
+ {
902
+ name: "My Company",
903
+ slug: "my-company",
904
+ members: [{ email: "test@example.com", role: "admin" }]
905
+ }
906
+ ],
907
+ oauth_applications: [
908
+ {
909
+ client_id: "clerk_emulate_client",
910
+ client_secret: "clerk_emulate_secret",
911
+ name: "Emulate App",
912
+ redirect_uris: ["http://localhost:3000/api/auth/callback/clerk"]
913
+ }
914
+ ]
915
+ }
916
+ }
803
917
  }
804
918
  };
805
919
 
806
920
  // src/api.ts
807
921
  import { serve } from "@hono/node-server";
922
+
923
+ // src/base-url.ts
924
+ function resolveBaseUrl(opts) {
925
+ if (opts.seedBaseUrl) {
926
+ return opts.seedBaseUrl.replace(/\{service\}/g, opts.service);
927
+ }
928
+ if (opts.baseUrl) {
929
+ return opts.baseUrl.replace(/\{service\}/g, opts.service);
930
+ }
931
+ const envBaseUrl = process.env.EMULATE_BASE_URL;
932
+ if (envBaseUrl) {
933
+ return envBaseUrl.replace(/\{service\}/g, opts.service);
934
+ }
935
+ const portlessUrl = process.env.PORTLESS_URL;
936
+ if (portlessUrl) {
937
+ return portlessUrl.replace(/\{service\}/g, opts.service);
938
+ }
939
+ return `http://localhost:${opts.port}`;
940
+ }
941
+
942
+ // src/api.ts
808
943
  async function createEmulator(options) {
809
944
  const { service, port = 4e3, seed: seedConfig } = options;
810
945
  const entry = SERVICE_REGISTRY[service];
@@ -821,17 +956,18 @@ async function createEmulator(options) {
821
956
  } else {
822
957
  tokens["test_token_admin"] = { login: "admin", id: 2, scopes: ["repo", "user", "admin:org", "admin:repo_hook"] };
823
958
  }
824
- const baseUrl = `http://localhost:${port}`;
959
+ const svcSeedConfig = seedConfig?.[service];
960
+ const seedBaseUrl = typeof svcSeedConfig?.baseUrl === "string" && svcSeedConfig.baseUrl.length > 0 ? svcSeedConfig.baseUrl : void 0;
961
+ const baseUrl = resolveBaseUrl({ service, port, baseUrl: options.baseUrl, seedBaseUrl });
825
962
  let cachedResolver;
826
963
  const appKeyResolver = loaded.createAppKeyResolver ? (appId) => cachedResolver(appId) : void 0;
827
- const svcSeedConfig = seedConfig?.[service];
828
964
  const fallbackUser = entry.defaultFallback(svcSeedConfig);
829
- const { app, store } = createServer(loaded.plugin, { port, baseUrl, tokens, appKeyResolver, fallbackUser });
965
+ const { app, store, webhooks } = createServer(loaded.plugin, { port, baseUrl, tokens, appKeyResolver, fallbackUser });
830
966
  cachedResolver = loaded.createAppKeyResolver?.(store);
831
967
  const seed = () => {
832
968
  loaded.plugin.seed?.(store, baseUrl);
833
969
  if (svcSeedConfig && loaded.seedFromConfig) {
834
- loaded.seedFromConfig(store, baseUrl, svcSeedConfig);
970
+ loaded.seedFromConfig(store, baseUrl, svcSeedConfig, webhooks);
835
971
  }
836
972
  };
837
973
  seed();