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.
- package/README.md +59 -10
- package/dist/api.d.ts +2 -1
- package/dist/api.js +232 -96
- package/dist/api.js.map +1 -1
- package/dist/{chunk-TEPNEZ63.js → chunk-AQ2CLRU3.js} +26 -23
- package/dist/chunk-AQ2CLRU3.js.map +1 -0
- package/dist/chunk-WVQMFHQM.js +83 -0
- package/dist/chunk-WVQMFHQM.js.map +1 -0
- package/dist/{dist-RDFBZ5O6.js → dist-4X2KPMAJ.js} +212 -47
- package/dist/dist-4X2KPMAJ.js.map +1 -0
- package/dist/{dist-OTJZRQ3Q.js → dist-5JVGPOL3.js} +217 -75
- package/dist/dist-5JVGPOL3.js.map +1 -0
- package/dist/{dist-G7WQPZ3Y.js → dist-CE6BUCWQ.js} +211 -60
- package/dist/dist-CE6BUCWQ.js.map +1 -0
- package/dist/{dist-6JFNJPUU.js → dist-CFST4X4K.js} +172 -22
- package/dist/dist-CFST4X4K.js.map +1 -0
- package/dist/{dist-YOVM5HEY.js → dist-ENKE2S7V.js} +521 -60
- package/dist/dist-ENKE2S7V.js.map +1 -0
- package/dist/{dist-RMK3BS5M.js → dist-ETHHYBGF.js} +197 -33
- package/dist/dist-ETHHYBGF.js.map +1 -0
- package/dist/{dist-QMOJM6DV.js → dist-IBXD3O6A.js} +239 -54
- package/dist/dist-IBXD3O6A.js.map +1 -0
- package/dist/dist-J6LHUR52.js +1899 -0
- package/dist/dist-J6LHUR52.js.map +1 -0
- package/dist/{dist-6EW7SSOZ.js → dist-KKTYBE5S.js} +391 -222
- package/dist/dist-KKTYBE5S.js.map +1 -0
- package/dist/{dist-VVXVP5EZ.js → dist-LDUHEJAN.js} +553 -91
- package/dist/dist-LDUHEJAN.js.map +1 -0
- package/dist/{dist-B674PYKV.js → dist-PWGOAQC6.js} +22 -43
- package/dist/dist-PWGOAQC6.js.map +1 -0
- package/dist/{dist-H6JYGQM4.js → dist-REDHDZ3V.js} +272 -157
- package/dist/dist-REDHDZ3V.js.map +1 -0
- package/dist/fonts/favicon.ico +0 -0
- package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
- package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
- package/dist/index.js +365 -108
- package/dist/index.js.map +1 -1
- package/package.json +17 -14
- package/dist/chunk-TEPNEZ63.js.map +0 -1
- package/dist/dist-6EW7SSOZ.js.map +0 -1
- package/dist/dist-6JFNJPUU.js.map +0 -1
- package/dist/dist-B674PYKV.js.map +0 -1
- package/dist/dist-G7WQPZ3Y.js.map +0 -1
- package/dist/dist-H6JYGQM4.js.map +0 -1
- package/dist/dist-OTJZRQ3Q.js.map +0 -1
- package/dist/dist-QMOJM6DV.js.map +0 -1
- package/dist/dist-RDFBZ5O6.js.map +0 -1
- package/dist/dist-RMK3BS5M.js.map +0 -1
- package/dist/dist-VVXVP5EZ.js.map +0 -1
- 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-
|
|
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 = [
|
|
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-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
{
|
|
582
|
-
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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-
|
|
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: [
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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-
|
|
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: [
|
|
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
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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-
|
|
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
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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-
|
|
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
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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-
|
|
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
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
940
|
+
test_token_admin: {
|
|
814
941
|
login: "admin",
|
|
815
942
|
scopes: ["repo", "user", "admin:org", "admin:repo_hook"]
|
|
816
943
|
},
|
|
817
|
-
|
|
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
|
-
|
|
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
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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
|
-
|
|
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, {
|
|
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.
|
|
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) => {
|