emulate 0.3.0 → 0.4.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 +324 -8
- package/dist/api.d.ts +1 -1
- package/dist/api.js +151 -6
- package/dist/api.js.map +1 -1
- package/dist/{dist-BKXG6HVH.js → dist-6EW7SSOZ.js} +1 -1
- package/dist/dist-6EW7SSOZ.js.map +1 -0
- package/dist/{dist-O4KFIBVU.js → dist-6JFNJPUU.js} +1 -1
- package/dist/dist-6JFNJPUU.js.map +1 -0
- package/dist/dist-B674PYKV.js +961 -0
- package/dist/dist-B674PYKV.js.map +1 -0
- package/dist/{dist-UZSUUE3Y.js → dist-G7WQPZ3Y.js} +1 -1
- package/dist/dist-G7WQPZ3Y.js.map +1 -0
- package/dist/{dist-OCDKIMRJ.js → dist-H6JYGQM4.js} +1 -1
- package/dist/dist-H6JYGQM4.js.map +1 -0
- package/dist/dist-OTJZRQ3Q.js +1956 -0
- package/dist/dist-OTJZRQ3Q.js.map +1 -0
- package/dist/dist-QMOJM6DV.js +774 -0
- package/dist/dist-QMOJM6DV.js.map +1 -0
- package/dist/{dist-JYDZIVC6.js → dist-RDFBZ5O6.js} +1 -1
- package/dist/dist-RDFBZ5O6.js.map +1 -0
- package/dist/{dist-DSSB3LYT.js → dist-RMK3BS5M.js} +56 -1
- package/dist/dist-RMK3BS5M.js.map +1 -0
- package/dist/dist-YOVM5HEY.js +932 -0
- package/dist/dist-YOVM5HEY.js.map +1 -0
- package/dist/index.js +154 -9
- package/dist/index.js.map +1 -1
- package/package.json +13 -9
- package/dist/dist-BKXG6HVH.js.map +0 -1
- package/dist/dist-DSSB3LYT.js.map +0 -1
- package/dist/dist-JYDZIVC6.js.map +0 -1
- package/dist/dist-O4KFIBVU.js.map +0 -1
- package/dist/dist-OCDKIMRJ.js.map +0 -1
- package/dist/dist-UZSUUE3Y.js.map +0 -1
package/README.md
CHANGED
|
@@ -13,6 +13,10 @@ All services start with sensible defaults. No config file needed:
|
|
|
13
13
|
- **Vercel** on `http://localhost:4000`
|
|
14
14
|
- **GitHub** on `http://localhost:4001`
|
|
15
15
|
- **Google** on `http://localhost:4002`
|
|
16
|
+
- **Slack** on `http://localhost:4003`
|
|
17
|
+
- **Apple** on `http://localhost:4004`
|
|
18
|
+
- **Microsoft** on `http://localhost:4005`
|
|
19
|
+
- **AWS** on `http://localhost:4006`
|
|
16
20
|
|
|
17
21
|
## CLI
|
|
18
22
|
|
|
@@ -84,8 +88,8 @@ beforeAll(async () => {
|
|
|
84
88
|
createEmulator({ service: 'github', port: 4001 }),
|
|
85
89
|
createEmulator({ service: 'vercel', port: 4002 }),
|
|
86
90
|
])
|
|
87
|
-
process.env.
|
|
88
|
-
process.env.
|
|
91
|
+
process.env.GITHUB_EMULATOR_URL = github.url
|
|
92
|
+
process.env.VERCEL_EMULATOR_URL = vercel.url
|
|
89
93
|
})
|
|
90
94
|
|
|
91
95
|
afterEach(() => { github.reset(); vercel.reset() })
|
|
@@ -96,7 +100,7 @@ afterAll(() => Promise.all([github.close(), vercel.close()]))
|
|
|
96
100
|
|
|
97
101
|
| Option | Default | Description |
|
|
98
102
|
|--------|---------|-------------|
|
|
99
|
-
| `service` | *(required)* | Service
|
|
103
|
+
| `service` | *(required)* | Service name: `'vercel'`, `'github'`, `'google'`, `'slack'`, `'apple'`, `'microsoft'`, or `'aws'` |
|
|
100
104
|
| `port` | `4000` | Port for the HTTP server |
|
|
101
105
|
| `seed` | none | Inline seed data (same shape as YAML config) |
|
|
102
106
|
|
|
@@ -110,7 +114,7 @@ afterAll(() => Promise.all([github.close(), vercel.close()]))
|
|
|
110
114
|
|
|
111
115
|
## Configuration
|
|
112
116
|
|
|
113
|
-
Configuration is optional.
|
|
117
|
+
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 `emulate init` to generate a starter file.
|
|
114
118
|
|
|
115
119
|
```yaml
|
|
116
120
|
tokens:
|
|
@@ -188,6 +192,68 @@ google:
|
|
|
188
192
|
name: Docs
|
|
189
193
|
mime_type: application/vnd.google-apps.folder
|
|
190
194
|
parent_ids: [root]
|
|
195
|
+
|
|
196
|
+
slack:
|
|
197
|
+
team:
|
|
198
|
+
name: My Workspace
|
|
199
|
+
domain: my-workspace
|
|
200
|
+
users:
|
|
201
|
+
- name: developer
|
|
202
|
+
real_name: Developer
|
|
203
|
+
email: dev@example.com
|
|
204
|
+
channels:
|
|
205
|
+
- name: general
|
|
206
|
+
topic: General discussion
|
|
207
|
+
- name: random
|
|
208
|
+
topic: Random stuff
|
|
209
|
+
bots:
|
|
210
|
+
- name: my-bot
|
|
211
|
+
oauth_apps:
|
|
212
|
+
- client_id: "12345.67890"
|
|
213
|
+
client_secret: example_client_secret
|
|
214
|
+
name: My Slack App
|
|
215
|
+
redirect_uris:
|
|
216
|
+
- http://localhost:3000/api/auth/callback/slack
|
|
217
|
+
|
|
218
|
+
apple:
|
|
219
|
+
users:
|
|
220
|
+
- email: testuser@icloud.com
|
|
221
|
+
name: Test User
|
|
222
|
+
oauth_clients:
|
|
223
|
+
- client_id: com.example.app
|
|
224
|
+
team_id: TEAM001
|
|
225
|
+
name: My Apple App
|
|
226
|
+
redirect_uris:
|
|
227
|
+
- http://localhost:3000/api/auth/callback/apple
|
|
228
|
+
|
|
229
|
+
microsoft:
|
|
230
|
+
users:
|
|
231
|
+
- email: testuser@outlook.com
|
|
232
|
+
name: Test User
|
|
233
|
+
oauth_clients:
|
|
234
|
+
- client_id: example-client-id
|
|
235
|
+
client_secret: example-client-secret
|
|
236
|
+
name: My Microsoft App
|
|
237
|
+
redirect_uris:
|
|
238
|
+
- http://localhost:3000/api/auth/callback/microsoft-entra-id
|
|
239
|
+
|
|
240
|
+
aws:
|
|
241
|
+
region: us-east-1
|
|
242
|
+
s3:
|
|
243
|
+
buckets:
|
|
244
|
+
- name: my-app-bucket
|
|
245
|
+
- name: my-app-uploads
|
|
246
|
+
sqs:
|
|
247
|
+
queues:
|
|
248
|
+
- name: my-app-events
|
|
249
|
+
- name: my-app-dlq
|
|
250
|
+
iam:
|
|
251
|
+
users:
|
|
252
|
+
- user_name: developer
|
|
253
|
+
create_access_key: true
|
|
254
|
+
roles:
|
|
255
|
+
- role_name: lambda-execution-role
|
|
256
|
+
description: Role for Lambda function execution
|
|
191
257
|
```
|
|
192
258
|
|
|
193
259
|
## OAuth & Integrations
|
|
@@ -252,6 +318,42 @@ JWT authentication: sign a JWT with `{ iss: "<app_id>" }` using the app's privat
|
|
|
252
318
|
- All webhook payloads (including repo and org hooks) include an `installation` field with `{ id, node_id }`.
|
|
253
319
|
- If the app has a `webhook_url`, the emulator delivers the event there with the `installation` field and (if configured) an `X-Hub-Signature-256` header signed with `webhook_secret`.
|
|
254
320
|
|
|
321
|
+
### Slack OAuth Apps
|
|
322
|
+
|
|
323
|
+
```yaml
|
|
324
|
+
slack:
|
|
325
|
+
oauth_apps:
|
|
326
|
+
- client_id: "12345.67890"
|
|
327
|
+
client_secret: "example_client_secret"
|
|
328
|
+
name: "My Slack App"
|
|
329
|
+
redirect_uris:
|
|
330
|
+
- "http://localhost:3000/api/auth/callback/slack"
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Apple OAuth Clients
|
|
334
|
+
|
|
335
|
+
```yaml
|
|
336
|
+
apple:
|
|
337
|
+
oauth_clients:
|
|
338
|
+
- client_id: "com.example.app"
|
|
339
|
+
team_id: "TEAM001"
|
|
340
|
+
name: "My Apple App"
|
|
341
|
+
redirect_uris:
|
|
342
|
+
- "http://localhost:3000/api/auth/callback/apple"
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Microsoft OAuth Clients
|
|
346
|
+
|
|
347
|
+
```yaml
|
|
348
|
+
microsoft:
|
|
349
|
+
oauth_clients:
|
|
350
|
+
- client_id: "example-client-id"
|
|
351
|
+
client_secret: "example-client-secret"
|
|
352
|
+
name: "My Microsoft App"
|
|
353
|
+
redirect_uris:
|
|
354
|
+
- "http://localhost:3000/api/auth/callback/microsoft-entra-id"
|
|
355
|
+
```
|
|
356
|
+
|
|
255
357
|
## Vercel API
|
|
256
358
|
|
|
257
359
|
Every endpoint below is fully stateful with Vercel-style JSON responses and cursor-based pagination.
|
|
@@ -424,7 +526,6 @@ Every endpoint below is fully stateful. Creates, updates, and deletes persist in
|
|
|
424
526
|
## Google OAuth + Gmail, Calendar, and Drive APIs
|
|
425
527
|
|
|
426
528
|
OAuth 2.0, OpenID Connect, and mutable Google Workspace-style surfaces for local inbox, calendar, and drive flows.
|
|
427
|
-
This stays under a single `google:` service because the Gmail API is used by both consumer Google accounts and Google Workspace accounts. A separate Workspace-specific service would only make sense once we add admin or tenant-level APIs that do not belong in the basic Google/Gmail emulator.
|
|
428
529
|
|
|
429
530
|
- `GET /o/oauth2/v2/auth` - authorization endpoint
|
|
430
531
|
- `POST /oauth2/token` - token exchange
|
|
@@ -450,18 +551,223 @@ This stays under a single `google:` service because the Gmail API is used by bot
|
|
|
450
551
|
- `GET /calendar/v3/users/:userId/calendarList`, `GET /calendar/v3/calendars/:calendarId/events`, `POST /calendar/v3/calendars/:calendarId/events`, `DELETE /calendar/v3/calendars/:calendarId/events/:eventId`, `POST /calendar/v3/freeBusy`
|
|
451
552
|
- `GET /drive/v3/files`, `GET /drive/v3/files/:fileId`, `POST /drive/v3/files`, `PATCH /drive/v3/files/:fileId`, `PUT /drive/v3/files/:fileId`, `POST /upload/drive/v3/files`
|
|
452
553
|
|
|
453
|
-
|
|
554
|
+
## Slack API
|
|
555
|
+
|
|
556
|
+
Fully stateful Slack Web API emulation with channels, messages, threads, reactions, OAuth v2, and incoming webhooks.
|
|
557
|
+
|
|
558
|
+
### Auth & Chat
|
|
559
|
+
- `POST /api/auth.test` - test authentication
|
|
560
|
+
- `POST /api/chat.postMessage` - post message (supports threads via `thread_ts`)
|
|
561
|
+
- `POST /api/chat.update` - update message
|
|
562
|
+
- `POST /api/chat.delete` - delete message
|
|
563
|
+
- `POST /api/chat.meMessage` - /me message
|
|
564
|
+
|
|
565
|
+
### Conversations
|
|
566
|
+
- `POST /api/conversations.list` - list channels (cursor pagination)
|
|
567
|
+
- `POST /api/conversations.info` - get channel info
|
|
568
|
+
- `POST /api/conversations.create` - create channel
|
|
569
|
+
- `POST /api/conversations.history` - channel history
|
|
570
|
+
- `POST /api/conversations.replies` - thread replies
|
|
571
|
+
- `POST /api/conversations.join` / `conversations.leave` - join/leave
|
|
572
|
+
- `POST /api/conversations.members` - list members
|
|
573
|
+
|
|
574
|
+
### Users & Reactions
|
|
575
|
+
- `POST /api/users.list` - list users (cursor pagination)
|
|
576
|
+
- `POST /api/users.info` - get user info
|
|
577
|
+
- `POST /api/users.lookupByEmail` - lookup by email
|
|
578
|
+
- `POST /api/reactions.add` / `reactions.remove` / `reactions.get` - manage reactions
|
|
579
|
+
|
|
580
|
+
### Team, Bots & Webhooks
|
|
581
|
+
- `POST /api/team.info` - workspace info
|
|
582
|
+
- `POST /api/bots.info` - bot info
|
|
583
|
+
- `POST /services/:teamId/:botId/:webhookId` - incoming webhook
|
|
584
|
+
|
|
585
|
+
### OAuth
|
|
586
|
+
- `GET /oauth/v2/authorize` - authorization (shows user picker)
|
|
587
|
+
- `POST /api/oauth.v2.access` - token exchange
|
|
588
|
+
|
|
589
|
+
## Apple Sign In
|
|
590
|
+
|
|
591
|
+
Sign in with Apple emulation with authorization code flow, PKCE support, RS256 ID tokens, and OIDC discovery.
|
|
592
|
+
|
|
593
|
+
- `GET /.well-known/openid-configuration` - OIDC discovery document
|
|
594
|
+
- `GET /auth/keys` - JSON Web Key Set (JWKS)
|
|
595
|
+
- `GET /auth/authorize` - authorization endpoint (shows user picker)
|
|
596
|
+
- `POST /auth/token` - token exchange (authorization code and refresh token grants)
|
|
597
|
+
- `POST /auth/revoke` - token revocation
|
|
598
|
+
|
|
599
|
+
## Microsoft Entra ID
|
|
600
|
+
|
|
601
|
+
Microsoft Entra ID (Azure AD) v2.0 OAuth 2.0 and OpenID Connect emulation with authorization code flow, PKCE, client credentials, RS256 ID tokens, and OIDC discovery.
|
|
602
|
+
|
|
603
|
+
- `GET /.well-known/openid-configuration` - OIDC discovery document
|
|
604
|
+
- `GET /:tenant/v2.0/.well-known/openid-configuration` - tenant-scoped OIDC discovery
|
|
605
|
+
- `GET /discovery/v2.0/keys` - JSON Web Key Set (JWKS)
|
|
606
|
+
- `GET /oauth2/v2.0/authorize` - authorization endpoint (shows user picker)
|
|
607
|
+
- `POST /oauth2/v2.0/token` - token exchange (authorization code, refresh token, client credentials)
|
|
608
|
+
- `GET /oidc/userinfo` - OpenID Connect user info
|
|
609
|
+
- `GET /v1.0/me` - Microsoft Graph user profile
|
|
610
|
+
- `GET /oauth2/v2.0/logout` - end session / logout
|
|
611
|
+
- `POST /oauth2/v2.0/revoke` - token revocation
|
|
612
|
+
|
|
613
|
+
## AWS
|
|
614
|
+
|
|
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.
|
|
616
|
+
|
|
617
|
+
### 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
|
|
627
|
+
|
|
628
|
+
### SQS
|
|
629
|
+
All operations via `POST /sqs/` with `Action` parameter:
|
|
630
|
+
- `CreateQueue`, `ListQueues`, `GetQueueUrl`, `GetQueueAttributes`
|
|
631
|
+
- `SendMessage`, `ReceiveMessage`, `DeleteMessage`
|
|
632
|
+
- `PurgeQueue`, `DeleteQueue`
|
|
633
|
+
|
|
634
|
+
### IAM
|
|
635
|
+
All operations via `POST /iam/` with `Action` parameter:
|
|
636
|
+
- `CreateUser`, `GetUser`, `ListUsers`, `DeleteUser`
|
|
637
|
+
- `CreateAccessKey`, `ListAccessKeys`, `DeleteAccessKey`
|
|
638
|
+
- `CreateRole`, `GetRole`, `ListRoles`, `DeleteRole`
|
|
639
|
+
|
|
640
|
+
### STS
|
|
641
|
+
All operations via `POST /sts/` with `Action` parameter:
|
|
642
|
+
- `GetCallerIdentity`, `AssumeRole`
|
|
643
|
+
|
|
644
|
+
## Next.js Integration
|
|
645
|
+
|
|
646
|
+
Embed emulators directly in your Next.js app so they run on the same origin. This solves the Vercel preview deployment problem where OAuth callback URLs change with every deployment.
|
|
647
|
+
|
|
648
|
+
### Install
|
|
649
|
+
|
|
650
|
+
```bash
|
|
651
|
+
npm install @emulators/adapter-next @emulators/github @emulators/google
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Only install the emulators you need. Each `@emulators/*` package is published independently.
|
|
655
|
+
|
|
656
|
+
### Route handler
|
|
657
|
+
|
|
658
|
+
Create a catch-all route that serves emulator traffic:
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
// app/emulate/[...path]/route.ts
|
|
662
|
+
import { createEmulateHandler } from '@emulators/adapter-next'
|
|
663
|
+
import * as github from '@emulators/github'
|
|
664
|
+
import * as google from '@emulators/google'
|
|
665
|
+
|
|
666
|
+
export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
|
|
667
|
+
services: {
|
|
668
|
+
github: {
|
|
669
|
+
emulator: github,
|
|
670
|
+
seed: {
|
|
671
|
+
users: [{ login: 'octocat', name: 'The Octocat' }],
|
|
672
|
+
repos: [{ owner: 'octocat', name: 'hello-world', auto_init: true }],
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
google: {
|
|
676
|
+
emulator: google,
|
|
677
|
+
seed: {
|
|
678
|
+
users: [{ email: 'test@example.com', name: 'Test User' }],
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
},
|
|
682
|
+
})
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Auth.js / NextAuth configuration
|
|
686
|
+
|
|
687
|
+
Point your provider at the emulator paths on the same origin:
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
import GitHub from 'next-auth/providers/github'
|
|
691
|
+
|
|
692
|
+
const baseUrl = process.env.VERCEL_URL
|
|
693
|
+
? `https://${process.env.VERCEL_URL}`
|
|
694
|
+
: 'http://localhost:3000'
|
|
695
|
+
|
|
696
|
+
GitHub({
|
|
697
|
+
clientId: 'any-value',
|
|
698
|
+
clientSecret: 'any-value',
|
|
699
|
+
authorization: { url: `${baseUrl}/emulate/github/login/oauth/authorize` },
|
|
700
|
+
token: { url: `${baseUrl}/emulate/github/login/oauth/access_token` },
|
|
701
|
+
userinfo: { url: `${baseUrl}/emulate/github/user` },
|
|
702
|
+
})
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
No `oauth_apps` need to be seeded. When none are configured, the emulator skips `client_id`, `client_secret`, and `redirect_uri` validation.
|
|
706
|
+
|
|
707
|
+
### Font files in serverless
|
|
708
|
+
|
|
709
|
+
Emulator UI pages use bundled fonts. Wrap your Next.js config to include them in the serverless trace:
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
// next.config.mjs
|
|
713
|
+
import { withEmulate } from '@emulators/adapter-next'
|
|
714
|
+
|
|
715
|
+
export default withEmulate({
|
|
716
|
+
// your normal Next.js config
|
|
717
|
+
})
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
If you mount the catch-all at a custom path, pass the matching prefix:
|
|
721
|
+
|
|
722
|
+
```typescript
|
|
723
|
+
export default withEmulate(nextConfig, { routePrefix: '/api/emulate' })
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### Persistence
|
|
727
|
+
|
|
728
|
+
By default, emulator state is in-memory and resets on every cold start. To persist state across restarts, pass a `persistence` adapter:
|
|
729
|
+
|
|
730
|
+
```typescript
|
|
731
|
+
import { createEmulateHandler } from '@emulators/adapter-next'
|
|
732
|
+
import * as github from '@emulators/github'
|
|
733
|
+
|
|
734
|
+
const kvAdapter = {
|
|
735
|
+
async load() { return await kv.get('emulate-state') },
|
|
736
|
+
async save(data: string) { await kv.set('emulate-state', data) },
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
|
|
740
|
+
services: { github: { emulator: github } },
|
|
741
|
+
persistence: kvAdapter,
|
|
742
|
+
})
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
For local development, `@emulators/core` ships `filePersistence`:
|
|
746
|
+
|
|
747
|
+
```typescript
|
|
748
|
+
import { filePersistence } from '@emulators/core'
|
|
749
|
+
|
|
750
|
+
// ...
|
|
751
|
+
persistence: filePersistence('.emulate/state.json'),
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
The persistence adapter is called on cold start (load) and after every mutating request (save). Saves are serialized via an internal queue to prevent race conditions.
|
|
454
755
|
|
|
455
756
|
## Architecture
|
|
456
757
|
|
|
457
758
|
```
|
|
458
759
|
packages/
|
|
459
760
|
emulate/ # CLI entry point (commander)
|
|
460
|
-
|
|
761
|
+
@emulators/
|
|
461
762
|
core/ # HTTP server, in-memory store, plugin interface, middleware
|
|
763
|
+
adapter-next/ # Next.js App Router integration
|
|
462
764
|
vercel/ # Vercel API service
|
|
463
765
|
github/ # GitHub API service
|
|
464
|
-
google/ # Google OAuth 2.0 / OIDC + Gmail, Calendar,
|
|
766
|
+
google/ # Google OAuth 2.0 / OIDC + Gmail, Calendar, Drive
|
|
767
|
+
slack/ # Slack Web API, OAuth v2, incoming webhooks
|
|
768
|
+
apple/ # Apple Sign In / OIDC
|
|
769
|
+
microsoft/ # Microsoft Entra ID OAuth 2.0 / OIDC + Graph /me
|
|
770
|
+
aws/ # AWS S3, SQS, IAM, STS
|
|
465
771
|
apps/
|
|
466
772
|
web/ # Documentation site (Next.js)
|
|
467
773
|
```
|
|
@@ -475,3 +781,13 @@ Tokens are configured in the seed config and map to users. Pass them as `Authori
|
|
|
475
781
|
**Vercel**: All endpoints accept `teamId` or `slug` query params for team scoping. Pagination uses cursor-based `limit`/`since`/`until` with `pagination` response objects.
|
|
476
782
|
|
|
477
783
|
**GitHub**: Public repo endpoints work without auth. Private repos and write operations require a valid token. Pagination uses `page`/`per_page` with `Link` headers.
|
|
784
|
+
|
|
785
|
+
**Google**: Standard OAuth 2.0 authorization code flow. Configure clients in the seed config.
|
|
786
|
+
|
|
787
|
+
**Slack**: All Web API endpoints require `Authorization: Bearer <token>`. OAuth v2 flow with user picker UI.
|
|
788
|
+
|
|
789
|
+
**Apple**: OIDC authorization code flow with RS256 ID tokens. On first auth per user/client pair, a `user` JSON blob is included.
|
|
790
|
+
|
|
791
|
+
**Microsoft**: OIDC authorization code flow with PKCE support. Also supports client credentials grants. Microsoft Graph `/v1.0/me` available.
|
|
792
|
+
|
|
793
|
+
**AWS**: Bearer tokens or IAM access key credentials. Default key pair always seeded: `AKIAIOSFODNN7EXAMPLE` / `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`.
|
package/dist/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const SERVICE_NAME_LIST: readonly ["vercel", "github", "google", "slack", "apple", "microsoft", "aws"];
|
|
1
|
+
declare const SERVICE_NAME_LIST: readonly ["vercel", "github", "google", "slack", "apple", "microsoft", "okta", "aws", "resend", "stripe", "mongoatlas"];
|
|
2
2
|
type ServiceName = (typeof SERVICE_NAME_LIST)[number];
|
|
3
3
|
|
|
4
4
|
interface SeedConfig {
|
package/dist/api.js
CHANGED
|
@@ -12,6 +12,28 @@ import { createHmac } from "crypto";
|
|
|
12
12
|
import { readFileSync } from "fs";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
14
|
import { dirname, join } from "path";
|
|
15
|
+
function serializeValue(value) {
|
|
16
|
+
if (value instanceof Map) {
|
|
17
|
+
return { __type: "Map", entries: [...value.entries()].map(([k, v]) => [k, serializeValue(v)]) };
|
|
18
|
+
}
|
|
19
|
+
if (value instanceof Set) {
|
|
20
|
+
return { __type: "Set", values: [...value.values()] };
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function deserializeValue(value) {
|
|
25
|
+
if (value !== null && typeof value === "object" && "__type" in value) {
|
|
26
|
+
const tagged = value;
|
|
27
|
+
if (tagged.__type === "Map") {
|
|
28
|
+
const entries = tagged.entries;
|
|
29
|
+
return new Map(entries.map(([k, v]) => [k, deserializeValue(v)]));
|
|
30
|
+
}
|
|
31
|
+
if (tagged.__type === "Set") {
|
|
32
|
+
return new Set(tagged.values);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
15
37
|
var Collection = class {
|
|
16
38
|
constructor(indexFields = []) {
|
|
17
39
|
this.indexFields = indexFields;
|
|
@@ -132,6 +154,21 @@ var Collection = class {
|
|
|
132
154
|
}
|
|
133
155
|
this.autoId = 1;
|
|
134
156
|
}
|
|
157
|
+
snapshot() {
|
|
158
|
+
return {
|
|
159
|
+
items: this.all(),
|
|
160
|
+
autoId: this.autoId,
|
|
161
|
+
indexFields: this.fieldNames
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
restore(snap) {
|
|
165
|
+
this.clear();
|
|
166
|
+
this.autoId = snap.autoId;
|
|
167
|
+
for (const item of snap.items) {
|
|
168
|
+
this.items.set(item.id, item);
|
|
169
|
+
this.addToIndex(item);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
135
172
|
};
|
|
136
173
|
var Store = class {
|
|
137
174
|
collections = /* @__PURE__ */ new Map();
|
|
@@ -165,6 +202,34 @@ var Store = class {
|
|
|
165
202
|
}
|
|
166
203
|
this._data.clear();
|
|
167
204
|
}
|
|
205
|
+
snapshot() {
|
|
206
|
+
const collections = {};
|
|
207
|
+
for (const [name, col] of this.collections) {
|
|
208
|
+
collections[name] = col.snapshot();
|
|
209
|
+
}
|
|
210
|
+
const data = {};
|
|
211
|
+
for (const [key, value] of this._data) {
|
|
212
|
+
data[key] = serializeValue(value);
|
|
213
|
+
}
|
|
214
|
+
return { collections, data };
|
|
215
|
+
}
|
|
216
|
+
restore(snap) {
|
|
217
|
+
const snapshotNames = new Set(Object.keys(snap.collections));
|
|
218
|
+
for (const name of this.collections.keys()) {
|
|
219
|
+
if (!snapshotNames.has(name)) {
|
|
220
|
+
this.collections.delete(name);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
for (const [name, colSnap] of Object.entries(snap.collections)) {
|
|
224
|
+
const indexFields = colSnap.indexFields;
|
|
225
|
+
const col = this.collection(name, indexFields);
|
|
226
|
+
col.restore(colSnap);
|
|
227
|
+
}
|
|
228
|
+
this._data.clear();
|
|
229
|
+
for (const [key, value] of Object.entries(snap.data)) {
|
|
230
|
+
this._data.set(key, deserializeValue(value));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
168
233
|
};
|
|
169
234
|
var MAX_DELIVERIES = 1e3;
|
|
170
235
|
var WebhookDispatcher = class {
|
|
@@ -448,7 +513,7 @@ var SERVICE_REGISTRY = {
|
|
|
448
513
|
label: "Vercel REST API emulator",
|
|
449
514
|
endpoints: "projects, deployments, domains, env vars, users, teams, file uploads, protection bypass",
|
|
450
515
|
async load() {
|
|
451
|
-
const mod = await import("./dist-
|
|
516
|
+
const mod = await import("./dist-RDFBZ5O6.js");
|
|
452
517
|
return { plugin: mod.vercelPlugin, seedFromConfig: mod.seedFromConfig };
|
|
453
518
|
},
|
|
454
519
|
defaultFallback(cfg) {
|
|
@@ -473,7 +538,7 @@ var SERVICE_REGISTRY = {
|
|
|
473
538
|
label: "GitHub REST API emulator",
|
|
474
539
|
endpoints: "users, repos, issues, PRs, comments, reviews, labels, milestones, branches, git data, orgs, teams, releases, webhooks, search, actions, checks, rate limit",
|
|
475
540
|
async load() {
|
|
476
|
-
const mod = await import("./dist-
|
|
541
|
+
const mod = await import("./dist-H6JYGQM4.js");
|
|
477
542
|
return {
|
|
478
543
|
plugin: mod.githubPlugin,
|
|
479
544
|
seedFromConfig: mod.seedFromConfig,
|
|
@@ -523,7 +588,7 @@ var SERVICE_REGISTRY = {
|
|
|
523
588
|
label: "Google OAuth 2.0 / OpenID Connect + Gmail, Calendar, and Drive emulator",
|
|
524
589
|
endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, token revocation, Gmail messages/drafts/threads/labels/history/settings, Calendar lists/events/freebusy, Drive files/uploads",
|
|
525
590
|
async load() {
|
|
526
|
-
const mod = await import("./dist-
|
|
591
|
+
const mod = await import("./dist-6EW7SSOZ.js");
|
|
527
592
|
return { plugin: mod.googlePlugin, seedFromConfig: mod.seedFromConfig };
|
|
528
593
|
},
|
|
529
594
|
defaultFallback(cfg) {
|
|
@@ -567,7 +632,7 @@ var SERVICE_REGISTRY = {
|
|
|
567
632
|
label: "Slack API emulator",
|
|
568
633
|
endpoints: "auth, chat, conversations, users, reactions, team, OAuth, incoming webhooks",
|
|
569
634
|
async load() {
|
|
570
|
-
const mod = await import("./dist-
|
|
635
|
+
const mod = await import("./dist-G7WQPZ3Y.js");
|
|
571
636
|
return { plugin: mod.slackPlugin, seedFromConfig: mod.seedFromConfig };
|
|
572
637
|
},
|
|
573
638
|
defaultFallback() {
|
|
@@ -592,7 +657,7 @@ var SERVICE_REGISTRY = {
|
|
|
592
657
|
label: "Apple Sign In / OAuth emulator",
|
|
593
658
|
endpoints: "OAuth authorize, token exchange, JWKS",
|
|
594
659
|
async load() {
|
|
595
|
-
const mod = await import("./dist-
|
|
660
|
+
const mod = await import("./dist-6JFNJPUU.js");
|
|
596
661
|
return { plugin: mod.applePlugin, seedFromConfig: mod.seedFromConfig };
|
|
597
662
|
},
|
|
598
663
|
defaultFallback(cfg) {
|
|
@@ -615,7 +680,7 @@ var SERVICE_REGISTRY = {
|
|
|
615
680
|
label: "Microsoft Entra ID OAuth 2.0 / OpenID Connect emulator",
|
|
616
681
|
endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, Graph /me, logout, token revocation",
|
|
617
682
|
async load() {
|
|
618
|
-
const mod = await import("./dist-
|
|
683
|
+
const mod = await import("./dist-RMK3BS5M.js");
|
|
619
684
|
return { plugin: mod.microsoftPlugin, seedFromConfig: mod.seedFromConfig };
|
|
620
685
|
},
|
|
621
686
|
defaultFallback(cfg) {
|
|
@@ -634,6 +699,32 @@ var SERVICE_REGISTRY = {
|
|
|
634
699
|
}
|
|
635
700
|
}
|
|
636
701
|
},
|
|
702
|
+
okta: {
|
|
703
|
+
label: "Okta OAuth 2.0 / OpenID Connect + management API emulator",
|
|
704
|
+
endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo/introspect/revoke/logout, users, groups, apps, authorization servers",
|
|
705
|
+
async load() {
|
|
706
|
+
const mod = await import("./dist-OTJZRQ3Q.js");
|
|
707
|
+
return { plugin: mod.oktaPlugin, seedFromConfig: mod.seedFromConfig };
|
|
708
|
+
},
|
|
709
|
+
defaultFallback(cfg) {
|
|
710
|
+
const firstLogin = cfg?.users?.[0]?.login ?? cfg?.users?.[0]?.email ?? "testuser@okta.local";
|
|
711
|
+
return { login: firstLogin, id: 1, scopes: ["openid", "profile", "email", "groups"] };
|
|
712
|
+
},
|
|
713
|
+
initConfig: {
|
|
714
|
+
okta: {
|
|
715
|
+
users: [{ login: "testuser@okta.local", email: "testuser@okta.local", first_name: "Test", last_name: "User" }],
|
|
716
|
+
groups: [{ name: "Everyone", description: "All users", type: "BUILT_IN", okta_id: "00g_everyone" }],
|
|
717
|
+
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
|
+
}]
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
},
|
|
637
728
|
aws: {
|
|
638
729
|
label: "AWS cloud service emulator",
|
|
639
730
|
endpoints: "S3 (buckets, objects), SQS (queues, messages), IAM (users, roles, access keys), STS (assume role, caller identity)",
|
|
@@ -655,6 +746,60 @@ var SERVICE_REGISTRY = {
|
|
|
655
746
|
}
|
|
656
747
|
}
|
|
657
748
|
}
|
|
749
|
+
},
|
|
750
|
+
resend: {
|
|
751
|
+
label: "Resend email API emulator",
|
|
752
|
+
endpoints: "emails, domains, contacts, API keys, inbox UI",
|
|
753
|
+
async load() {
|
|
754
|
+
const mod = await import("./dist-QMOJM6DV.js");
|
|
755
|
+
return { plugin: mod.resendPlugin, seedFromConfig: mod.seedFromConfig };
|
|
756
|
+
},
|
|
757
|
+
defaultFallback() {
|
|
758
|
+
return { login: "re_test_admin", id: 1, scopes: [] };
|
|
759
|
+
},
|
|
760
|
+
initConfig: {
|
|
761
|
+
resend: {
|
|
762
|
+
domains: [{ name: "example.com", region: "us-east-1" }],
|
|
763
|
+
contacts: [{ email: "test@example.com", first_name: "Test", last_name: "User" }]
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
stripe: {
|
|
768
|
+
label: "Stripe payments emulator",
|
|
769
|
+
endpoints: "customers, payment intents, charges, products, prices, checkout sessions, webhooks",
|
|
770
|
+
async load() {
|
|
771
|
+
const mod = await import("./dist-YOVM5HEY.js");
|
|
772
|
+
return { plugin: mod.stripePlugin, seedFromConfig: mod.seedFromConfig };
|
|
773
|
+
},
|
|
774
|
+
defaultFallback() {
|
|
775
|
+
return { login: "sk_test_admin", id: 1, scopes: [] };
|
|
776
|
+
},
|
|
777
|
+
initConfig: {
|
|
778
|
+
stripe: {
|
|
779
|
+
customers: [{ email: "test@example.com", name: "Test Customer" }],
|
|
780
|
+
products: [{ name: "Pro Plan", description: "Monthly pro subscription" }],
|
|
781
|
+
prices: [{ product_name: "Pro Plan", currency: "usd", unit_amount: 2e3 }]
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
mongoatlas: {
|
|
786
|
+
label: "MongoDB Atlas service emulator",
|
|
787
|
+
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
|
+
async load() {
|
|
789
|
+
const mod = await import("./dist-B674PYKV.js");
|
|
790
|
+
return { plugin: mod.mongoatlasPlugin, seedFromConfig: mod.seedFromConfig };
|
|
791
|
+
},
|
|
792
|
+
defaultFallback() {
|
|
793
|
+
return { login: "admin", id: 1, scopes: [] };
|
|
794
|
+
},
|
|
795
|
+
initConfig: {
|
|
796
|
+
mongoatlas: {
|
|
797
|
+
projects: [{ name: "Project0" }],
|
|
798
|
+
clusters: [{ name: "Cluster0", project: "Project0" }],
|
|
799
|
+
database_users: [{ username: "admin", project: "Project0" }],
|
|
800
|
+
databases: [{ cluster: "Cluster0", name: "test", collections: ["items"] }]
|
|
801
|
+
}
|
|
802
|
+
}
|
|
658
803
|
}
|
|
659
804
|
};
|
|
660
805
|
|