emulate 0.4.0 → 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/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  Hono,
8
8
  cors
9
- } from "./chunk-TEPNEZ63.js";
9
+ } from "./chunk-AQ2CLRU3.js";
10
10
 
11
11
  // src/index.ts
12
12
  import { Command } from "commander";
@@ -391,9 +391,7 @@ function authMiddleware(tokens, appKeyResolver, fallbackUser) {
391
391
  if (token.startsWith("eyJ") && appKeyResolver) {
392
392
  try {
393
393
  const [, payloadB64] = token.split(".");
394
- const payload = JSON.parse(
395
- Buffer.from(payloadB64, "base64url").toString()
396
- );
394
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
397
395
  const appId = typeof payload.iss === "string" ? parseInt(payload.iss, 10) : payload.iss;
398
396
  if (typeof appId === "number" && !isNaN(appId)) {
399
397
  const appInfo = appKeyResolver(appId);
@@ -430,6 +428,7 @@ var FONTS = {
430
428
  "geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
431
429
  "GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
432
430
  };
431
+ var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
433
432
  function registerFontRoutes(app) {
434
433
  app.get("/_emulate/fonts/:name", (c) => {
435
434
  const name = c.req.param("name");
@@ -443,6 +442,14 @@ function registerFontRoutes(app) {
443
442
  }
444
443
  });
445
444
  });
445
+ app.get("/_emulate/favicon.ico", (c) => {
446
+ return new Response(FAVICON, {
447
+ headers: {
448
+ "Content-Type": "image/x-icon",
449
+ "Cache-Control": "public, max-age=31536000, immutable"
450
+ }
451
+ });
452
+ });
446
453
  }
447
454
  function createServer(plugin, options = {}) {
448
455
  const port = options.port ?? 4e3;
@@ -512,14 +519,27 @@ function createServer(plugin, options = {}) {
512
519
  }
513
520
 
514
521
  // src/registry.ts
515
- var SERVICE_NAME_LIST = ["vercel", "github", "google", "slack", "apple", "microsoft", "okta", "aws", "resend", "stripe", "mongoatlas"];
522
+ var SERVICE_NAME_LIST = [
523
+ "vercel",
524
+ "github",
525
+ "google",
526
+ "slack",
527
+ "apple",
528
+ "microsoft",
529
+ "okta",
530
+ "aws",
531
+ "resend",
532
+ "stripe",
533
+ "mongoatlas",
534
+ "clerk"
535
+ ];
516
536
  var SERVICE_NAMES = SERVICE_NAME_LIST;
517
537
  var SERVICE_REGISTRY = {
518
538
  vercel: {
519
539
  label: "Vercel REST API emulator",
520
540
  endpoints: "projects, deployments, domains, env vars, users, teams, file uploads, protection bypass",
521
541
  async load() {
522
- const mod = await import("./dist-RDFBZ5O6.js");
542
+ const mod = await import("./dist-4X2KPMAJ.js");
523
543
  return { plugin: mod.vercelPlugin, seedFromConfig: mod.seedFromConfig };
524
544
  },
525
545
  defaultFallback(cfg) {
@@ -531,12 +551,14 @@ var SERVICE_REGISTRY = {
531
551
  users: [{ username: "developer", name: "Developer", email: "dev@example.com" }],
532
552
  teams: [{ slug: "my-team", name: "My Team" }],
533
553
  projects: [{ name: "my-app", team: "my-team", framework: "nextjs" }],
534
- integrations: [{
535
- client_id: "oac_example_client_id",
536
- client_secret: "example_client_secret",
537
- name: "My Vercel App",
538
- redirect_uris: ["http://localhost:3000/api/auth/callback/vercel"]
539
- }]
554
+ integrations: [
555
+ {
556
+ client_id: "oac_example_client_id",
557
+ client_secret: "example_client_secret",
558
+ name: "My Vercel App",
559
+ redirect_uris: ["http://localhost:3000/api/auth/callback/vercel"]
560
+ }
561
+ ]
540
562
  }
541
563
  }
542
564
  },
@@ -544,7 +566,7 @@ var SERVICE_REGISTRY = {
544
566
  label: "GitHub REST API emulator",
545
567
  endpoints: "users, repos, issues, PRs, comments, reviews, labels, milestones, branches, git data, orgs, teams, releases, webhooks, search, actions, checks, rate limit",
546
568
  async load() {
547
- const mod = await import("./dist-H6JYGQM4.js");
569
+ const mod = await import("./dist-REDHDZ3V.js");
548
570
  return {
549
571
  plugin: mod.githubPlugin,
550
572
  seedFromConfig: mod.seedFromConfig,
@@ -568,25 +590,42 @@ var SERVICE_REGISTRY = {
568
590
  },
569
591
  initConfig: {
570
592
  github: {
571
- users: [{
572
- login: "octocat",
573
- name: "The Octocat",
574
- email: "octocat@github.com",
575
- bio: "I am the Octocat",
576
- company: "GitHub",
577
- location: "San Francisco"
578
- }],
593
+ users: [
594
+ {
595
+ login: "octocat",
596
+ name: "The Octocat",
597
+ email: "octocat@github.com",
598
+ bio: "I am the Octocat",
599
+ company: "GitHub",
600
+ location: "San Francisco"
601
+ }
602
+ ],
579
603
  orgs: [{ login: "my-org", name: "My Organization", description: "A test organization" }],
580
604
  repos: [
581
- { owner: "octocat", name: "hello-world", description: "My first repository", language: "JavaScript", topics: ["hello", "world"], auto_init: true },
582
- { owner: "my-org", name: "org-repo", description: "An organization repository", language: "TypeScript", auto_init: true }
605
+ {
606
+ owner: "octocat",
607
+ name: "hello-world",
608
+ description: "My first repository",
609
+ language: "JavaScript",
610
+ topics: ["hello", "world"],
611
+ auto_init: true
612
+ },
613
+ {
614
+ owner: "my-org",
615
+ name: "org-repo",
616
+ description: "An organization repository",
617
+ language: "TypeScript",
618
+ auto_init: true
619
+ }
583
620
  ],
584
- oauth_apps: [{
585
- client_id: "Iv1.example_client_id",
586
- client_secret: "example_client_secret",
587
- name: "My App",
588
- redirect_uris: ["http://localhost:3000/api/auth/callback/github"]
589
- }]
621
+ oauth_apps: [
622
+ {
623
+ client_id: "Iv1.example_client_id",
624
+ client_secret: "example_client_secret",
625
+ name: "My App",
626
+ redirect_uris: ["http://localhost:3000/api/auth/callback/github"]
627
+ }
628
+ ]
590
629
  }
591
630
  }
592
631
  },
@@ -594,7 +633,7 @@ var SERVICE_REGISTRY = {
594
633
  label: "Google OAuth 2.0 / OpenID Connect + Gmail, Calendar, and Drive emulator",
595
634
  endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, token revocation, Gmail messages/drafts/threads/labels/history/settings, Calendar lists/events/freebusy, Drive files/uploads",
596
635
  async load() {
597
- const mod = await import("./dist-6EW7SSOZ.js");
636
+ const mod = await import("./dist-KKTYBE5S.js");
598
637
  return { plugin: mod.googlePlugin, seedFromConfig: mod.seedFromConfig };
599
638
  },
600
639
  defaultFallback(cfg) {
@@ -603,34 +642,72 @@ var SERVICE_REGISTRY = {
603
642
  },
604
643
  initConfig: {
605
644
  google: {
606
- users: [{ email: "testuser@example.com", name: "Test User", picture: "https://lh3.googleusercontent.com/a/default-user", email_verified: true }],
607
- oauth_clients: [{
608
- client_id: "example-client-id.apps.googleusercontent.com",
609
- client_secret: "GOCSPX-example_secret",
610
- name: "Code App (Google)",
611
- redirect_uris: ["http://localhost:3000/api/auth/callback/google"]
612
- }],
613
- labels: [{ id: "Label_ops", user_email: "testuser@example.com", name: "Ops/Review", color_background: "#DDEEFF", color_text: "#111111" }],
614
- messages: [{
615
- id: "msg_welcome",
616
- user_email: "testuser@example.com",
617
- from: "welcome@example.com",
618
- to: "testuser@example.com",
619
- subject: "Welcome to the Gmail emulator",
620
- body_text: "You can now test Gmail, Calendar, and Drive flows locally.",
621
- label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
622
- date: "2025-01-04T10:00:00.000Z"
623
- }],
624
- calendars: [{ id: "primary", user_email: "testuser@example.com", summary: "testuser@example.com", primary: true, selected: true, time_zone: "UTC" }],
625
- calendar_events: [{
626
- id: "evt_kickoff",
627
- user_email: "testuser@example.com",
628
- calendar_id: "primary",
629
- summary: "Project Kickoff",
630
- start_date_time: "2025-01-10T09:00:00.000Z",
631
- end_date_time: "2025-01-10T09:30:00.000Z"
632
- }],
633
- drive_items: [{ id: "drv_docs", user_email: "testuser@example.com", name: "Docs", mime_type: "application/vnd.google-apps.folder", parent_ids: ["root"] }]
645
+ users: [
646
+ {
647
+ email: "testuser@example.com",
648
+ name: "Test User",
649
+ picture: "https://lh3.googleusercontent.com/a/default-user",
650
+ email_verified: true
651
+ }
652
+ ],
653
+ oauth_clients: [
654
+ {
655
+ client_id: "example-client-id.apps.googleusercontent.com",
656
+ client_secret: "GOCSPX-example_secret",
657
+ name: "Code App (Google)",
658
+ redirect_uris: ["http://localhost:3000/api/auth/callback/google"]
659
+ }
660
+ ],
661
+ labels: [
662
+ {
663
+ id: "Label_ops",
664
+ user_email: "testuser@example.com",
665
+ name: "Ops/Review",
666
+ color_background: "#DDEEFF",
667
+ color_text: "#111111"
668
+ }
669
+ ],
670
+ messages: [
671
+ {
672
+ id: "msg_welcome",
673
+ user_email: "testuser@example.com",
674
+ from: "welcome@example.com",
675
+ to: "testuser@example.com",
676
+ subject: "Welcome to the Gmail emulator",
677
+ body_text: "You can now test Gmail, Calendar, and Drive flows locally.",
678
+ label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
679
+ date: "2025-01-04T10:00:00.000Z"
680
+ }
681
+ ],
682
+ calendars: [
683
+ {
684
+ id: "primary",
685
+ user_email: "testuser@example.com",
686
+ summary: "testuser@example.com",
687
+ primary: true,
688
+ selected: true,
689
+ time_zone: "UTC"
690
+ }
691
+ ],
692
+ calendar_events: [
693
+ {
694
+ id: "evt_kickoff",
695
+ user_email: "testuser@example.com",
696
+ calendar_id: "primary",
697
+ summary: "Project Kickoff",
698
+ start_date_time: "2025-01-10T09:00:00.000Z",
699
+ end_date_time: "2025-01-10T09:30:00.000Z"
700
+ }
701
+ ],
702
+ drive_items: [
703
+ {
704
+ id: "drv_docs",
705
+ user_email: "testuser@example.com",
706
+ name: "Docs",
707
+ mime_type: "application/vnd.google-apps.folder",
708
+ parent_ids: ["root"]
709
+ }
710
+ ]
634
711
  }
635
712
  }
636
713
  },
@@ -638,7 +715,7 @@ var SERVICE_REGISTRY = {
638
715
  label: "Slack API emulator",
639
716
  endpoints: "auth, chat, conversations, users, reactions, team, OAuth, incoming webhooks",
640
717
  async load() {
641
- const mod = await import("./dist-G7WQPZ3Y.js");
718
+ const mod = await import("./dist-CE6BUCWQ.js");
642
719
  return { plugin: mod.slackPlugin, seedFromConfig: mod.seedFromConfig };
643
720
  },
644
721
  defaultFallback() {
@@ -648,14 +725,19 @@ var SERVICE_REGISTRY = {
648
725
  slack: {
649
726
  team: { name: "My Workspace", domain: "my-workspace" },
650
727
  users: [{ name: "developer", real_name: "Developer", email: "dev@example.com" }],
651
- channels: [{ name: "general", topic: "General discussion" }, { name: "random", topic: "Random stuff" }],
728
+ channels: [
729
+ { name: "general", topic: "General discussion" },
730
+ { name: "random", topic: "Random stuff" }
731
+ ],
652
732
  bots: [{ name: "my-bot" }],
653
- oauth_apps: [{
654
- client_id: "12345.67890",
655
- client_secret: "example_client_secret",
656
- name: "My Slack App",
657
- redirect_uris: ["http://localhost:3000/api/auth/callback/slack"]
658
- }]
733
+ oauth_apps: [
734
+ {
735
+ client_id: "12345.67890",
736
+ client_secret: "example_client_secret",
737
+ name: "My Slack App",
738
+ redirect_uris: ["http://localhost:3000/api/auth/callback/slack"]
739
+ }
740
+ ]
659
741
  }
660
742
  }
661
743
  },
@@ -663,7 +745,7 @@ var SERVICE_REGISTRY = {
663
745
  label: "Apple Sign In / OAuth emulator",
664
746
  endpoints: "OAuth authorize, token exchange, JWKS",
665
747
  async load() {
666
- const mod = await import("./dist-6JFNJPUU.js");
748
+ const mod = await import("./dist-CFST4X4K.js");
667
749
  return { plugin: mod.applePlugin, seedFromConfig: mod.seedFromConfig };
668
750
  },
669
751
  defaultFallback(cfg) {
@@ -673,12 +755,14 @@ var SERVICE_REGISTRY = {
673
755
  initConfig: {
674
756
  apple: {
675
757
  users: [{ email: "testuser@icloud.com", name: "Test User" }],
676
- oauth_clients: [{
677
- client_id: "com.example.app",
678
- team_id: "TEAM001",
679
- name: "My Apple App",
680
- redirect_uris: ["http://localhost:3000/api/auth/callback/apple"]
681
- }]
758
+ oauth_clients: [
759
+ {
760
+ client_id: "com.example.app",
761
+ team_id: "TEAM001",
762
+ name: "My Apple App",
763
+ redirect_uris: ["http://localhost:3000/api/auth/callback/apple"]
764
+ }
765
+ ]
682
766
  }
683
767
  }
684
768
  },
@@ -686,7 +770,7 @@ var SERVICE_REGISTRY = {
686
770
  label: "Microsoft Entra ID OAuth 2.0 / OpenID Connect emulator",
687
771
  endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, Graph /me, logout, token revocation",
688
772
  async load() {
689
- const mod = await import("./dist-RMK3BS5M.js");
773
+ const mod = await import("./dist-ETHHYBGF.js");
690
774
  return { plugin: mod.microsoftPlugin, seedFromConfig: mod.seedFromConfig };
691
775
  },
692
776
  defaultFallback(cfg) {
@@ -696,12 +780,14 @@ var SERVICE_REGISTRY = {
696
780
  initConfig: {
697
781
  microsoft: {
698
782
  users: [{ email: "testuser@outlook.com", name: "Test User" }],
699
- oauth_clients: [{
700
- client_id: "example-client-id",
701
- client_secret: "example-client-secret",
702
- name: "My Microsoft App",
703
- redirect_uris: ["http://localhost:3000/api/auth/callback/microsoft-entra-id"]
704
- }]
783
+ oauth_clients: [
784
+ {
785
+ client_id: "example-client-id",
786
+ client_secret: "example-client-secret",
787
+ name: "My Microsoft App",
788
+ redirect_uris: ["http://localhost:3000/api/auth/callback/microsoft-entra-id"]
789
+ }
790
+ ]
705
791
  }
706
792
  }
707
793
  },
@@ -709,7 +795,7 @@ var SERVICE_REGISTRY = {
709
795
  label: "Okta OAuth 2.0 / OpenID Connect + management API emulator",
710
796
  endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo/introspect/revoke/logout, users, groups, apps, authorization servers",
711
797
  async load() {
712
- const mod = await import("./dist-OTJZRQ3Q.js");
798
+ const mod = await import("./dist-5JVGPOL3.js");
713
799
  return { plugin: mod.oktaPlugin, seedFromConfig: mod.seedFromConfig };
714
800
  },
715
801
  defaultFallback(cfg) {
@@ -721,13 +807,15 @@ var SERVICE_REGISTRY = {
721
807
  users: [{ login: "testuser@okta.local", email: "testuser@okta.local", first_name: "Test", last_name: "User" }],
722
808
  groups: [{ name: "Everyone", description: "All users", type: "BUILT_IN", okta_id: "00g_everyone" }],
723
809
  authorization_servers: [{ id: "default", name: "default", audiences: ["api://default"] }],
724
- oauth_clients: [{
725
- client_id: "okta-test-client",
726
- client_secret: "okta-test-secret",
727
- name: "Sample OIDC Client",
728
- redirect_uris: ["http://localhost:3000/callback"],
729
- auth_server_id: "default"
730
- }]
810
+ oauth_clients: [
811
+ {
812
+ client_id: "okta-test-client",
813
+ client_secret: "okta-test-secret",
814
+ name: "Sample OIDC Client",
815
+ redirect_uris: ["http://localhost:3000/callback"],
816
+ auth_server_id: "default"
817
+ }
818
+ ]
731
819
  }
732
820
  }
733
821
  },
@@ -735,7 +823,7 @@ var SERVICE_REGISTRY = {
735
823
  label: "AWS cloud service emulator",
736
824
  endpoints: "S3 (buckets, objects), SQS (queues, messages), IAM (users, roles, access keys), STS (assume role, caller identity)",
737
825
  async load() {
738
- const mod = await import("./dist-VVXVP5EZ.js");
826
+ const mod = await import("./dist-LDUHEJAN.js");
739
827
  return { plugin: mod.awsPlugin, seedFromConfig: mod.seedFromConfig };
740
828
  },
741
829
  defaultFallback() {
@@ -757,7 +845,7 @@ var SERVICE_REGISTRY = {
757
845
  label: "Resend email API emulator",
758
846
  endpoints: "emails, domains, contacts, API keys, inbox UI",
759
847
  async load() {
760
- const mod = await import("./dist-QMOJM6DV.js");
848
+ const mod = await import("./dist-IBXD3O6A.js");
761
849
  return { plugin: mod.resendPlugin, seedFromConfig: mod.seedFromConfig };
762
850
  },
763
851
  defaultFallback() {
@@ -772,9 +860,9 @@ var SERVICE_REGISTRY = {
772
860
  },
773
861
  stripe: {
774
862
  label: "Stripe payments emulator",
775
- endpoints: "customers, payment intents, charges, products, prices, checkout sessions, webhooks",
863
+ endpoints: "customers, payment methods, customer sessions, payment intents, charges, products, prices, checkout sessions, webhooks",
776
864
  async load() {
777
- const mod = await import("./dist-YOVM5HEY.js");
865
+ const mod = await import("./dist-ENKE2S7V.js");
778
866
  return { plugin: mod.stripePlugin, seedFromConfig: mod.seedFromConfig };
779
867
  },
780
868
  defaultFallback() {
@@ -792,7 +880,7 @@ var SERVICE_REGISTRY = {
792
880
  label: "MongoDB Atlas service emulator",
793
881
  endpoints: "Atlas Admin API v2 (projects, clusters, database users, databases, collections), Atlas Data API v1 (findOne, find, insertOne, insertMany, updateOne, updateMany, deleteOne, deleteMany, aggregate)",
794
882
  async load() {
795
- const mod = await import("./dist-B674PYKV.js");
883
+ const mod = await import("./dist-PWGOAQC6.js");
796
884
  return { plugin: mod.mongoatlasPlugin, seedFromConfig: mod.seedFromConfig };
797
885
  },
798
886
  defaultFallback() {
@@ -806,15 +894,54 @@ var SERVICE_REGISTRY = {
806
894
  databases: [{ cluster: "Cluster0", name: "test", collections: ["items"] }]
807
895
  }
808
896
  }
897
+ },
898
+ clerk: {
899
+ label: "Clerk authentication and user management emulator",
900
+ endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo, users, email addresses, organizations, memberships, invitations, sessions",
901
+ async load() {
902
+ const mod = await import("./dist-J6LHUR52.js");
903
+ return { plugin: mod.clerkPlugin, seedFromConfig: mod.seedFromConfig };
904
+ },
905
+ defaultFallback(cfg) {
906
+ const firstEmail = cfg?.users?.[0]?.email_addresses?.[0] ?? "test@example.com";
907
+ return { login: firstEmail, id: 1, scopes: [] };
908
+ },
909
+ initConfig: {
910
+ clerk: {
911
+ users: [
912
+ {
913
+ first_name: "Test",
914
+ last_name: "User",
915
+ email_addresses: ["test@example.com"],
916
+ password: "clerk_test_password"
917
+ }
918
+ ],
919
+ organizations: [
920
+ {
921
+ name: "My Company",
922
+ slug: "my-company",
923
+ members: [{ email: "test@example.com", role: "admin" }]
924
+ }
925
+ ],
926
+ oauth_applications: [
927
+ {
928
+ client_id: "clerk_emulate_client",
929
+ client_secret: "clerk_emulate_secret",
930
+ name: "Emulate App",
931
+ redirect_uris: ["http://localhost:3000/api/auth/callback/clerk"]
932
+ }
933
+ ]
934
+ }
935
+ }
809
936
  }
810
937
  };
811
938
  var DEFAULT_TOKENS = {
812
939
  tokens: {
813
- "test_token_admin": {
940
+ test_token_admin: {
814
941
  login: "admin",
815
942
  scopes: ["repo", "user", "admin:org", "admin:repo_hook"]
816
943
  },
817
- "test_token_user1": {
944
+ test_token_user1: {
818
945
  login: "octocat",
819
946
  scopes: ["repo", "user"]
820
947
  }
@@ -827,7 +954,106 @@ import { readFileSync as readFileSync2, existsSync } from "fs";
827
954
  import { resolve } from "path";
828
955
  import { parse as parseYaml } from "yaml";
829
956
  import pc from "picocolors";
830
- var pkg = { version: "0.4.0" };
957
+
958
+ // src/portless.ts
959
+ import { execSync, spawnSync } from "child_process";
960
+ import { createInterface } from "readline";
961
+ function isInteractive() {
962
+ return Boolean(process.stdin.isTTY) && !process.env.CI;
963
+ }
964
+ function hasPortless() {
965
+ const result = spawnSync("portless", ["--version"], { stdio: "ignore" });
966
+ return result.status === 0;
967
+ }
968
+ function promptYesNo(question) {
969
+ return new Promise((resolve3) => {
970
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
971
+ rl.question(question, (answer) => {
972
+ rl.close();
973
+ const normalized = answer.trim().toLowerCase();
974
+ resolve3(normalized === "" || normalized === "y" || normalized === "yes");
975
+ });
976
+ });
977
+ }
978
+ function isProxyRunning() {
979
+ const result = spawnSync("portless", ["list"], { stdio: "ignore" });
980
+ return result.status === 0;
981
+ }
982
+ async function ensurePortless() {
983
+ if (!hasPortless()) {
984
+ if (!isInteractive()) {
985
+ console.error("portless is required but not installed. Run: npm i -g portless");
986
+ process.exit(1);
987
+ }
988
+ const yes = await promptYesNo("portless is not installed. Install it now? (npm i -g portless) [Y/n] ");
989
+ if (!yes) {
990
+ console.error("Cannot continue without portless.");
991
+ process.exit(1);
992
+ }
993
+ try {
994
+ execSync("npm i -g portless", { stdio: "inherit" });
995
+ } catch {
996
+ console.error("Failed to install portless.");
997
+ process.exit(1);
998
+ }
999
+ if (!hasPortless()) {
1000
+ console.error("portless was installed but could not be found on PATH.");
1001
+ process.exit(1);
1002
+ }
1003
+ }
1004
+ if (!isProxyRunning()) {
1005
+ console.error("portless proxy is not running. Start it with: portless proxy start");
1006
+ process.exit(1);
1007
+ }
1008
+ }
1009
+ function registerAliases(aliases) {
1010
+ const registered = [];
1011
+ for (const { name, port } of aliases) {
1012
+ const result = spawnSync("portless", ["alias", name, String(port), "--force"], {
1013
+ stdio: "inherit"
1014
+ });
1015
+ if (result.status !== 0) {
1016
+ if (registered.length > 0) {
1017
+ removeAliases(registered);
1018
+ }
1019
+ throw new Error(`Failed to register portless alias: ${name} -> ${port}`);
1020
+ }
1021
+ registered.push({ name, port });
1022
+ }
1023
+ }
1024
+ function removeAliases(aliases) {
1025
+ for (const { name } of aliases) {
1026
+ const result = spawnSync("portless", ["alias", "--remove", name], { stdio: "ignore" });
1027
+ if (result.status !== 0) {
1028
+ console.error(`Warning: failed to remove portless alias: ${name}`);
1029
+ }
1030
+ }
1031
+ }
1032
+ function portlessBaseUrl(serviceName) {
1033
+ return `https://${serviceName}.emulate.localhost`;
1034
+ }
1035
+
1036
+ // src/base-url.ts
1037
+ function resolveBaseUrl(opts) {
1038
+ if (opts.seedBaseUrl) {
1039
+ return opts.seedBaseUrl.replace(/\{service\}/g, opts.service);
1040
+ }
1041
+ if (opts.baseUrl) {
1042
+ return opts.baseUrl.replace(/\{service\}/g, opts.service);
1043
+ }
1044
+ const envBaseUrl = process.env.EMULATE_BASE_URL;
1045
+ if (envBaseUrl) {
1046
+ return envBaseUrl.replace(/\{service\}/g, opts.service);
1047
+ }
1048
+ const portlessUrl = process.env.PORTLESS_URL;
1049
+ if (portlessUrl) {
1050
+ return portlessUrl.replace(/\{service\}/g, opts.service);
1051
+ }
1052
+ return `http://localhost:${opts.port}`;
1053
+ }
1054
+
1055
+ // src/commands/start.ts
1056
+ var pkg = { version: "0.5.0" };
831
1057
  function loadSeedConfig(seedPath) {
832
1058
  if (seedPath) {
833
1059
  const fullPath = resolve(seedPath);
@@ -869,10 +1095,14 @@ function loadSeedConfig(seedPath) {
869
1095
  }
870
1096
  function inferServicesFromConfig(config) {
871
1097
  const found = SERVICE_NAMES.filter((k) => k in config);
872
- return found.length > 0 ? found : null;
1098
+ return found.length > 0 ? [...found] : null;
873
1099
  }
874
1100
  async function startCommand(options) {
875
1101
  const { port: basePort } = options;
1102
+ if (options.portless && options.baseUrl) {
1103
+ console.error("--portless and --base-url are mutually exclusive.");
1104
+ process.exit(1);
1105
+ }
876
1106
  const loaded = loadSeedConfig(options.seed);
877
1107
  const seedConfig = loaded?.config ?? null;
878
1108
  const configSource = loaded?.source ?? null;
@@ -880,9 +1110,9 @@ async function startCommand(options) {
880
1110
  if (options.service) {
881
1111
  services = options.service.split(",").map((s) => s.trim());
882
1112
  } else if (seedConfig) {
883
- services = inferServicesFromConfig(seedConfig) ?? SERVICE_NAMES;
1113
+ services = inferServicesFromConfig(seedConfig) ?? [...SERVICE_NAMES];
884
1114
  } else {
885
- services = SERVICE_NAMES;
1115
+ services = [...SERVICE_NAMES];
886
1116
  }
887
1117
  for (const svc of services) {
888
1118
  if (!SERVICE_REGISTRY[svc]) {
@@ -899,26 +1129,48 @@ async function startCommand(options) {
899
1129
  } else {
900
1130
  tokens["test_token_admin"] = { login: "admin", id: 2, scopes: ["repo", "user", "admin:org", "admin:repo_hook"] };
901
1131
  }
902
- const serviceUrls = [];
903
- const stores = [];
904
- const httpServers = [];
1132
+ if (options.portless) {
1133
+ await ensurePortless();
1134
+ }
1135
+ const portlessAliases = [];
1136
+ const prepared = [];
905
1137
  for (let i = 0; i < services.length; i++) {
906
1138
  const svc = services[i];
907
1139
  const entry = SERVICE_REGISTRY[svc];
908
1140
  const loadedSvc = await entry.load();
909
1141
  const svcSeedConfig = seedConfig?.[svc];
910
1142
  const port = svcSeedConfig?.port ?? basePort + i;
911
- const baseUrl = `http://localhost:${port}`;
1143
+ if (options.portless) {
1144
+ portlessAliases.push({ name: `${svc}.emulate`, port });
1145
+ }
1146
+ const seedBaseUrl = typeof svcSeedConfig?.baseUrl === "string" && svcSeedConfig.baseUrl.length > 0 ? svcSeedConfig.baseUrl : void 0;
1147
+ const effectiveBaseUrl = options.portless ? portlessBaseUrl(svc) : options.baseUrl;
1148
+ const baseUrl = resolveBaseUrl({ service: svc, port, baseUrl: effectiveBaseUrl, seedBaseUrl });
1149
+ prepared.push({ svc, entry, loadedSvc, svcSeedConfig, port, baseUrl });
1150
+ }
1151
+ if (portlessAliases.length > 0) {
1152
+ registerAliases(portlessAliases);
1153
+ }
1154
+ const serviceUrls = [];
1155
+ const stores = [];
1156
+ const httpServers = [];
1157
+ for (const { svc, entry, loadedSvc, svcSeedConfig, port, baseUrl } of prepared) {
912
1158
  serviceUrls.push({ name: svc, url: baseUrl });
913
1159
  let cachedResolver;
914
1160
  const appKeyResolver = loadedSvc.createAppKeyResolver ? (appId) => cachedResolver(appId) : void 0;
915
1161
  const fallbackUser = entry.defaultFallback(svcSeedConfig);
916
- const { app, store } = createServer(loadedSvc.plugin, { port, baseUrl, tokens, appKeyResolver, fallbackUser });
1162
+ const { app, store, webhooks } = createServer(loadedSvc.plugin, {
1163
+ port,
1164
+ baseUrl,
1165
+ tokens,
1166
+ appKeyResolver,
1167
+ fallbackUser
1168
+ });
917
1169
  cachedResolver = loadedSvc.createAppKeyResolver?.(store);
918
1170
  stores.push(store);
919
1171
  loadedSvc.plugin.seed?.(store, baseUrl);
920
1172
  if (svcSeedConfig && loadedSvc.seedFromConfig) {
921
- loadedSvc.seedFromConfig(store, baseUrl, svcSeedConfig);
1173
+ loadedSvc.seedFromConfig(store, baseUrl, svcSeedConfig, webhooks);
922
1174
  }
923
1175
  const httpServer = serve({ fetch: app.fetch, port });
924
1176
  httpServers.push(httpServer);
@@ -927,6 +1179,9 @@ async function startCommand(options) {
927
1179
  const shutdown = () => {
928
1180
  console.log(`
929
1181
  ${pc.dim("Shutting down...")}`);
1182
+ if (portlessAliases.length > 0) {
1183
+ removeAliases(portlessAliases);
1184
+ }
930
1185
  for (const store of stores) {
931
1186
  store.reset();
932
1187
  }
@@ -1008,11 +1263,11 @@ function listCommand() {
1008
1263
  }
1009
1264
 
1010
1265
  // src/index.ts
1011
- var pkg2 = { version: "0.4.0" };
1266
+ var pkg2 = { version: "0.5.0" };
1012
1267
  var defaultPort = process.env.EMULATE_PORT ?? process.env.PORT ?? "4000";
1013
1268
  var program = new Command();
1014
1269
  program.name("emulate").description("Local drop-in replacement services for CI and no-network sandboxes").version(pkg2.version);
1015
- program.command("start", { isDefault: true }).description("Start the emulator server").option("-p, --port <port>", "Base port", defaultPort).option("-s, --service <services>", "Comma-separated services to enable").option("--seed <file>", "Path to seed config file").action(async (opts) => {
1270
+ program.command("start", { isDefault: true }).description("Start the emulator server").option("-p, --port <port>", "Base port", defaultPort).option("-s, --service <services>", "Comma-separated services to enable").option("--seed <file>", "Path to seed config file").option("--base-url <url>", "Override advertised base URL (supports {service} template)").option("--portless", "Serve over HTTPS via portless (auto-registers aliases)").action(async (opts) => {
1016
1271
  const port = parseInt(opts.port, 10);
1017
1272
  if (Number.isNaN(port) || port < 1 || port > 65535) {
1018
1273
  console.error(`Invalid port: ${opts.port}`);
@@ -1021,7 +1276,9 @@ program.command("start", { isDefault: true }).description("Start the emulator se
1021
1276
  await startCommand({
1022
1277
  port,
1023
1278
  service: opts.service,
1024
- seed: opts.seed
1279
+ seed: opts.seed,
1280
+ baseUrl: opts.baseUrl,
1281
+ portless: opts.portless
1025
1282
  });
1026
1283
  });
1027
1284
  program.command("init").description("Generate a starter config file").option("-s, --service <service>", "Service to generate config for", "all").action((opts) => {