@executor-js/emulate 0.6.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 (48) hide show
  1. package/README.md +1044 -0
  2. package/dist/api.d.ts +24 -0
  3. package/dist/api.js +2665 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/chunk-D6EKRYGP.js +1615 -0
  6. package/dist/chunk-D6EKRYGP.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-7FDUSG5I.js +24368 -0
  10. package/dist/dist-7FDUSG5I.js.map +1 -0
  11. package/dist/dist-7N4COJHK.js +1814 -0
  12. package/dist/dist-7N4COJHK.js.map +1 -0
  13. package/dist/dist-BTEY33DJ.js +2334 -0
  14. package/dist/dist-BTEY33DJ.js.map +1 -0
  15. package/dist/dist-DK26ESP2.js +595 -0
  16. package/dist/dist-DK26ESP2.js.map +1 -0
  17. package/dist/dist-IYZPDKJW.js +1284 -0
  18. package/dist/dist-IYZPDKJW.js.map +1 -0
  19. package/dist/dist-JJ2ZRCAX.js +189 -0
  20. package/dist/dist-JJ2ZRCAX.js.map +1 -0
  21. package/dist/dist-K4CVTD6K.js +1570 -0
  22. package/dist/dist-K4CVTD6K.js.map +1 -0
  23. package/dist/dist-M3GVASMR.js +1254 -0
  24. package/dist/dist-M3GVASMR.js.map +1 -0
  25. package/dist/dist-OYYGWKZQ.js +1533 -0
  26. package/dist/dist-OYYGWKZQ.js.map +1 -0
  27. package/dist/dist-P3SBBRFR.js +3169 -0
  28. package/dist/dist-P3SBBRFR.js.map +1 -0
  29. package/dist/dist-RMPDKZUA.js +1183 -0
  30. package/dist/dist-RMPDKZUA.js.map +1 -0
  31. package/dist/dist-WBKONLOE.js +2154 -0
  32. package/dist/dist-WBKONLOE.js.map +1 -0
  33. package/dist/dist-XM5HSBDC.js +1090 -0
  34. package/dist/dist-XM5HSBDC.js.map +1 -0
  35. package/dist/dist-XVVIYXQG.js +4241 -0
  36. package/dist/dist-XVVIYXQG.js.map +1 -0
  37. package/dist/dist-YPRJYQHW.js +5109 -0
  38. package/dist/dist-YPRJYQHW.js.map +1 -0
  39. package/dist/dist-ZEC77OKZ.js +913 -0
  40. package/dist/dist-ZEC77OKZ.js.map +1 -0
  41. package/dist/fonts/GeistPixel-Square.woff2 +0 -0
  42. package/dist/fonts/favicon.ico +0 -0
  43. package/dist/fonts/geist-sans.woff2 +0 -0
  44. package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
  45. package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
  46. package/dist/index.js +3005 -0
  47. package/dist/index.js.map +1 -0
  48. package/package.json +83 -0
package/README.md ADDED
@@ -0,0 +1,1044 @@
1
+ # emulate
2
+
3
+ This repository is forked from Vercel Labs' `emulate` project. Useful Software
4
+ Co maintains this fork to support deployable emulator surfaces and product
5
+ testing flows for our own development and agent-driven use cases.
6
+
7
+ Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation. Not mocks.
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ npx emulate
13
+ ```
14
+
15
+ All services start with sensible defaults. No config file needed:
16
+
17
+ - **Vercel** on `http://localhost:4000`
18
+ - **GitHub** on `http://localhost:4001`
19
+ - **Google** on `http://localhost:4002`
20
+ - **Slack** on `http://localhost:4003`
21
+ - **Apple** on `http://localhost:4004`
22
+ - **Microsoft** on `http://localhost:4005`
23
+ - **Okta** on `http://localhost:4006`
24
+ - **AWS** on `http://localhost:4007`
25
+ - **Resend** on `http://localhost:4008`
26
+ - **Stripe** on `http://localhost:4009`
27
+ - **MongoDB Atlas** on `http://localhost:4010`
28
+ - **Clerk** on `http://localhost:4011`
29
+ - **Spotify** on `http://localhost:4012`
30
+
31
+ Every running service also exposes a public control plane under `/_emulate`:
32
+
33
+ | Route | Purpose |
34
+ |-------|---------|
35
+ | `GET /_emulate` | Human-readable landing page for the service instance |
36
+ | `GET /_emulate/manifest` | Machine-readable service manifest, including supported surfaces, auth capabilities, spec coverage, and resolved connection snippets |
37
+ | `GET /_emulate/quickstart` | Plain-text instructions for humans and agents |
38
+ | `GET /_emulate/specs` | Advertised specs and protocol surfaces |
39
+ | `GET /_emulate/coverage` | Per-operation coverage report with a summary grouped by status (generated, hand-authored, partial, unsupported) |
40
+ | `GET /_emulate/connections` | Copyable SDK, CLI, env, and curl snippets resolved against this instance (optional `?token=`, `?client_id=`, `?client_secret=`) |
41
+ | `GET /_emulate/openapi` | Redirect to the advertised OpenAPI document when one exists |
42
+ | `GET /_emulate/graphql` | Return the GraphQL endpoint when the service exposes one |
43
+ | `GET /_emulate/mcp` | Return the MCP endpoint when the service exposes one |
44
+ | `GET /_emulate/ledger` | Recent API calls with sensitive fields redacted |
45
+ | `DELETE /_emulate/ledger` | Clear the request ledger |
46
+ | `GET /_emulate/logs` | Webhook deliveries plus recent requests |
47
+ | `GET /_emulate/state` | Current emulator store snapshot |
48
+ | `POST /_emulate/reset` | Reset state, webhooks, and request logs, then replay seed data |
49
+ | `POST /_emulate/seed` | Add runtime seed data using the service seed schema |
50
+ | `POST /_emulate/credentials` | Create bearer tokens, API keys, OAuth clients, or client-credentials apps where supported |
51
+ | `POST /_emulate/instances` | Return URLs for a lazily created hosted instance |
52
+
53
+ The manifest is the machine-readable single source of truth for a service. Each plugin package owns its manifest and serves it at `/_emulate/manifest`. It describes service identity, supported surfaces, auth capabilities, specs with per-operation coverage, scenarios, seed schema, state model, reset behavior, inspector tabs, request ledger capabilities, copyable connection snippets, and a docs link. OpenAPI, GraphQL, MCP, discovery documents, and OAuth metadata can inform those surfaces, but the emulator only advertises protocols that match the real service shape.
54
+
55
+ ### Request ledger
56
+
57
+ The request ledger is a core feature, not a debug afterthought. Each entry records a correlation id (honored from `X-Correlation-Id` or `X-Request-Id`, echoed back in the `X-Correlation-Id` response header, otherwise generated), the matched route and operation id, method, host, path, query, sanitized request headers and body, the authenticated identity, the response status with a one-line summary, recorded side effects, webhook deliveries, and the request duration. On the hosted Cloudflare surface the ledger is persisted across Durable Object eviction, so it survives instance restarts.
58
+
59
+ Credential creation follows each service's real shape. For example, GitHub can mint a bearer token for a user, Spotify creates a client credentials app, Google/Microsoft/Apple/Okta/Clerk create OAuth/OIDC clients, Stripe and Resend create API-key style credentials, and AWS advertises provider-specific SDK credentials instead of pretending to be OAuth.
60
+
61
+ ## CLI
62
+
63
+ ```bash
64
+ # Start all services (zero-config)
65
+ npx emulate
66
+
67
+ # Start specific services
68
+ npx emulate --service vercel,github
69
+
70
+ # Custom port
71
+ npx emulate --port 3000
72
+
73
+ # Use a seed config file
74
+ npx emulate --seed config.yaml
75
+
76
+ # Generate a starter config
77
+ npx emulate init
78
+
79
+ # Generate config for a specific service
80
+ npx emulate init --service vercel
81
+
82
+ # List available services
83
+ npx emulate list
84
+ ```
85
+
86
+ ### Options
87
+
88
+ | Flag | Default | Description |
89
+ |------|---------|-------------|
90
+ | `-p, --port` | `4000` | Base port (auto-increments per service) |
91
+ | `-s, --service` | all | Comma-separated services to enable |
92
+ | `--seed` | auto-detect | Path to seed config (YAML or JSON) |
93
+ | `--base-url` | none | Override advertised base URL (supports `{service}` template) |
94
+ | `--portless` | off | Serve over HTTPS via portless (auto-registers aliases) |
95
+
96
+ The port can also be set via `EMULATE_PORT` or `PORT` environment variables.
97
+
98
+ ## HTTPS with portless
99
+
100
+ [portless](https://github.com/vercel-labs/portless) gives emulators trusted HTTPS URLs with auto-generated certs and no browser warnings.
101
+
102
+ ```bash
103
+ # Start the portless proxy (first time only)
104
+ portless proxy start
105
+
106
+ # Start emulate with portless integration
107
+ npx emulate start --portless
108
+ ```
109
+
110
+ Each service registers as a portless alias and gets a named HTTPS URL:
111
+
112
+ ```
113
+ github https://github.emulate.localhost
114
+ google https://google.emulate.localhost
115
+ slack https://slack.emulate.localhost
116
+ ```
117
+
118
+ If portless is not installed, emulate will prompt to install it (`npm i -g portless`).
119
+
120
+ The `--portless` flag overwrites any existing portless aliases matching `*.emulate`. Aliases are removed automatically when emulate shuts down.
121
+
122
+ For a custom base URL without portless (any reverse proxy), use `--base-url` or the `EMULATE_BASE_URL` env var:
123
+
124
+ ```bash
125
+ npx emulate start --base-url "https://{service}.myproxy.test"
126
+ ```
127
+
128
+ The `PORTLESS_URL` env var is automatically set by the `portless` CLI wrapper when running a command through it (e.g. `portless github.emulate emulate start`), typically to a value like `https://{service}.emulate.localhost`. It supports `{service}` interpolation, just like `--base-url` and `EMULATE_BASE_URL`. When no explicit `baseUrl` is provided, it is used as a fallback.
129
+
130
+ Per-service overrides are also supported in the seed config (these take highest priority over all other base URL sources):
131
+
132
+ ```yaml
133
+ github:
134
+ baseUrl: https://github.emulate.localhost
135
+ ```
136
+
137
+ ## Deployed Instances
138
+
139
+ All services are available on host-based routing when deployed: `github`, `vercel`, `google`, `okta`, `microsoft`, `spotify`, `slack`, `apple`, `aws`, `resend`, `stripe`, `mongoatlas`, `clerk`, `x`, `workos`, and `autumn`. Each one supports three addressing forms:
140
+
141
+ ```text
142
+ https://github.emulators.dev # service host (no instance)
143
+ https://github.my-instance.emulators.dev # instance host
144
+ https://emulators.dev/github/my-instance # local/path form
145
+ ```
146
+
147
+ The instance host and path form route to the same stateful service instance. The subdomain form is preferred for public examples because the provider base URL is the origin itself, which better matches services such as GitHub that expose API, OAuth, GraphQL, and MCP surfaces under service-owned hosts. The instance control plane is available at `https://github.my-instance.emulators.dev/_emulate`.
148
+
149
+ ### Useful without an instance
150
+
151
+ The bare service host (for example `https://github.emulators.dev`) serves a service-level control plane so a human or agent can learn what the service is and connect without first creating an instance. It responds to `GET /_emulate`, `/_emulate/manifest`, `/_emulate/quickstart`, `/_emulate/specs`, `/_emulate/coverage`, `/_emulate/connections`, `/_emulate/openapi`, and `POST /_emulate/instances`.
152
+
153
+ A global catalog lists every hosted service from any host, including the apex:
154
+
155
+ ```text
156
+ GET /_emulate/services
157
+ ```
158
+
159
+ It returns each service's id, name, description, service host, instance host pattern, path form, and manifest URL, so agents can discover the full surface without repository context.
160
+
161
+ The apex `https://emulators.dev` is the emulator catalog: a links-out landing page that lists every emulator and links to each one's service host. The console builds it from `GET /_emulate/services`. The apex is not the docs site.
162
+
163
+ ### Docs
164
+
165
+ Documentation is a separate site at `https://docs.emulators.dev`. Per-service docs live at `https://docs.emulators.dev/<service>`, which is the `docsUrl` convention each manifest advertises.
166
+
167
+ ### Credentials on hosted instances
168
+
169
+ `POST /_emulate/credentials` is the canonical, uniform way to mint a credential for any service (a bearer token, API key, or OAuth client depending on the service's auth shape). On the hosted Cloudflare worker the legacy `/__seed`, `/__token`, and `/__reset` endpoints still work, but the `/_emulate/*` routes are canonical.
170
+
171
+ ### Deployment
172
+
173
+ The emulator worker is named `emulate-hosts`. It serves `emulators.dev/*` (the apex catalog) and `*.emulators.dev/*`, that is every service and instance subdomain. The `EmulatorDurableObject` class is declared through a wrangler `migrations` entry (`new_classes`). Each stateful instance is backed by one `EmulatorDurableObject`; both the store snapshot and the request ledger are persisted to Durable Object storage so instances survive eviction.
174
+
175
+ The docs site (`apps/web`, worker name `emulate-docs`) serves `docs.emulators.dev`. Because that host is more specific than `*.emulators.dev`, it wins for docs traffic.
176
+
177
+ ## Programmatic API
178
+
179
+ ```bash
180
+ npm install emulate
181
+ ```
182
+
183
+ Each call to `createEmulator` starts a single service:
184
+
185
+ ```typescript
186
+ import { createEmulator } from 'emulate'
187
+
188
+ const github = await createEmulator({ service: 'github', port: 4001 })
189
+ const vercel = await createEmulator({ service: 'vercel', port: 4002 })
190
+
191
+ github.url // 'http://localhost:4001'
192
+ vercel.url // 'http://localhost:4002'
193
+
194
+ await github.close()
195
+ await vercel.close()
196
+ ```
197
+
198
+ ### Vitest / Jest setup
199
+
200
+ ```typescript
201
+ // vitest.setup.ts
202
+ import { createEmulator, type Emulator } from 'emulate'
203
+
204
+ let github: Emulator
205
+ let vercel: Emulator
206
+
207
+ beforeAll(async () => {
208
+ ;[github, vercel] = await Promise.all([
209
+ createEmulator({ service: 'github', port: 4001 }),
210
+ createEmulator({ service: 'vercel', port: 4002 }),
211
+ ])
212
+ process.env.GITHUB_EMULATOR_URL = github.url
213
+ process.env.VERCEL_EMULATOR_URL = vercel.url
214
+ })
215
+
216
+ afterEach(() => { github.reset(); vercel.reset() })
217
+ afterAll(() => Promise.all([github.close(), vercel.close()]))
218
+ ```
219
+
220
+ ### Options
221
+
222
+ | Option | Default | Description |
223
+ |--------|---------|-------------|
224
+ | `service` | *(required)* | Service name: `'vercel'`, `'github'`, `'google'`, `'slack'`, `'apple'`, `'microsoft'`, `'okta'`, `'aws'`, `'resend'`, `'stripe'`, `'mongoatlas'`, `'clerk'`, `'spotify'`, `'x'`, `'workos'`, or `'autumn'` |
225
+ | `port` | `4000` | Port for the HTTP server |
226
+ | `seed` | none | Inline seed data (same shape as YAML config) |
227
+ | `baseUrl` | none | Override advertised base URL. Per-service `baseUrl` in seed config takes highest priority, then this option, then `EMULATE_BASE_URL` env var (supports `{service}`), then `PORTLESS_URL` (supports `{service}`, automatically set by the `portless` CLI wrapper), then `http://localhost:<port>`. |
228
+
229
+ ### Instance methods
230
+
231
+ | Method | Description |
232
+ |--------|-------------|
233
+ | `url` | Base URL of the running server |
234
+ | `reset()` | Wipe the store and replay seed data |
235
+ | `close()` | Shut down the HTTP server, returns a Promise |
236
+
237
+ ## Configuration
238
+
239
+ Configuration is optional. The CLI auto-detects config files in this order: `emulate.config.yaml` / `.yml`, `emulate.config.json`, `service-emulator.config.yaml` / `.yml`, `service-emulator.config.json`. Or pass `--seed <file>` explicitly. Run `npx emulate init` to generate a starter file.
240
+
241
+ ```yaml
242
+ tokens:
243
+ my_token:
244
+ login: admin
245
+ scopes: [repo, user]
246
+
247
+ vercel:
248
+ users:
249
+ - username: developer
250
+ name: Developer
251
+ email: dev@example.com
252
+ teams:
253
+ - slug: my-team
254
+ name: My Team
255
+ projects:
256
+ - name: my-app
257
+ team: my-team
258
+ framework: nextjs
259
+
260
+ github:
261
+ users:
262
+ - login: octocat
263
+ name: The Octocat
264
+ email: octocat@github.com
265
+ orgs:
266
+ - login: my-org
267
+ name: My Organization
268
+ repos:
269
+ - owner: octocat
270
+ name: hello-world
271
+ language: JavaScript
272
+ auto_init: true
273
+
274
+ google:
275
+ users:
276
+ - email: testuser@example.com
277
+ name: Test User
278
+ - email: admin@acme.com
279
+ name: Admin
280
+ hd: acme.com
281
+ oauth_clients:
282
+ - client_id: my-client-id.apps.googleusercontent.com
283
+ client_secret: GOCSPX-secret
284
+ redirect_uris:
285
+ - http://localhost:3000/api/auth/callback/google
286
+ labels:
287
+ - id: Label_ops
288
+ user_email: testuser@example.com
289
+ name: Ops/Review
290
+ color_background: "#DDEEFF"
291
+ color_text: "#111111"
292
+ messages:
293
+ - id: msg_welcome
294
+ user_email: testuser@example.com
295
+ from: welcome@example.com
296
+ to: testuser@example.com
297
+ subject: Welcome to the Gmail emulator
298
+ body_text: You can now test Gmail, Calendar, and Drive flows locally.
299
+ label_ids: [INBOX, UNREAD, CATEGORY_UPDATES]
300
+ calendars:
301
+ - id: primary
302
+ user_email: testuser@example.com
303
+ summary: testuser@example.com
304
+ primary: true
305
+ selected: true
306
+ time_zone: UTC
307
+ calendar_events:
308
+ - id: evt_kickoff
309
+ user_email: testuser@example.com
310
+ calendar_id: primary
311
+ summary: Project Kickoff
312
+ start_date_time: 2025-01-10T09:00:00.000Z
313
+ end_date_time: 2025-01-10T09:30:00.000Z
314
+ drive_items:
315
+ - id: drv_docs
316
+ user_email: testuser@example.com
317
+ name: Docs
318
+ mime_type: application/vnd.google-apps.folder
319
+ parent_ids: [root]
320
+
321
+ slack:
322
+ team:
323
+ name: My Workspace
324
+ domain: my-workspace
325
+ users:
326
+ - name: developer
327
+ real_name: Developer
328
+ email: dev@example.com
329
+ profile:
330
+ title: Local Developer
331
+ status_text: Testing locally
332
+ status_emoji: ":computer:"
333
+ presence: active
334
+ channels:
335
+ - name: general
336
+ topic: General discussion
337
+ - name: random
338
+ topic: Random stuff
339
+ bots:
340
+ - name: my-bot
341
+ oauth_apps:
342
+ - client_id: "12345.67890"
343
+ client_secret: example_client_secret
344
+ app_id: A000000001
345
+ name: My Slack App
346
+ redirect_uris:
347
+ - http://localhost:3000/api/auth/callback/slack
348
+ scopes:
349
+ - chat:write
350
+ - channels:read
351
+ - channels:history
352
+ - channels:join
353
+ - channels:manage
354
+ - channels:write
355
+ - groups:read
356
+ - groups:history
357
+ - groups:write
358
+ - im:read
359
+ - im:history
360
+ - im:write
361
+ - mpim:read
362
+ - mpim:history
363
+ - mpim:write
364
+ - users:read
365
+ - users:read.email
366
+ - users.profile:read
367
+ - users.profile:write
368
+ - users:write
369
+ - files:read
370
+ - files:write
371
+ - pins:read
372
+ - pins:write
373
+ - bookmarks:read
374
+ - bookmarks:write
375
+ - reactions:read
376
+ - reactions:write
377
+ - team:read
378
+ user_scopes: [users:read, users.profile:read]
379
+ bot_name: my-bot
380
+ tokens:
381
+ - token: xoxb-local-test
382
+ user: developer
383
+ scopes:
384
+ - chat:write
385
+ - channels:read
386
+ - channels:history
387
+ - channels:join
388
+ - channels:manage
389
+ - channels:write
390
+ - groups:read
391
+ - groups:history
392
+ - groups:write
393
+ - im:read
394
+ - im:history
395
+ - im:write
396
+ - mpim:read
397
+ - mpim:history
398
+ - mpim:write
399
+ - users:read
400
+ - users:read.email
401
+ - users.profile:read
402
+ - users.profile:write
403
+ - users:write
404
+ - files:read
405
+ - files:write
406
+ - pins:read
407
+ - pins:write
408
+ - bookmarks:read
409
+ - bookmarks:write
410
+ - reactions:read
411
+ - reactions:write
412
+ - team:read
413
+ strict_scopes: false
414
+
415
+ apple:
416
+ users:
417
+ - email: testuser@icloud.com
418
+ name: Test User
419
+ oauth_clients:
420
+ - client_id: com.example.app
421
+ team_id: TEAM001
422
+ name: My Apple App
423
+ redirect_uris:
424
+ - http://localhost:3000/api/auth/callback/apple
425
+
426
+ microsoft:
427
+ users:
428
+ - email: testuser@outlook.com
429
+ name: Test User
430
+ oauth_clients:
431
+ - client_id: example-client-id
432
+ client_secret: example-client-secret
433
+ name: My Microsoft App
434
+ redirect_uris:
435
+ - http://localhost:3000/api/auth/callback/microsoft-entra-id
436
+
437
+ aws:
438
+ region: us-east-1
439
+ s3:
440
+ buckets:
441
+ - name: my-app-bucket
442
+ - name: my-app-uploads
443
+ sqs:
444
+ queues:
445
+ - name: my-app-events
446
+ - name: my-app-dlq
447
+ iam:
448
+ users:
449
+ - user_name: developer
450
+ create_access_key: true
451
+ roles:
452
+ - role_name: lambda-execution-role
453
+ description: Role for Lambda function execution
454
+ ```
455
+
456
+ ## OAuth & Integrations
457
+
458
+ The emulator supports configurable OAuth apps and integrations with strict client validation.
459
+
460
+ ### Vercel Integrations
461
+
462
+ ```yaml
463
+ vercel:
464
+ integrations:
465
+ - client_id: "oac_abc123"
466
+ client_secret: "secret_abc123"
467
+ name: "My Vercel App"
468
+ redirect_uris:
469
+ - "http://localhost:3000/api/auth/callback/vercel"
470
+ ```
471
+
472
+ ### GitHub OAuth Apps
473
+
474
+ ```yaml
475
+ github:
476
+ oauth_apps:
477
+ - client_id: "Iv1.abc123"
478
+ client_secret: "secret_abc123"
479
+ name: "My Web App"
480
+ redirect_uris:
481
+ - "http://localhost:3000/api/auth/callback/github"
482
+ ```
483
+
484
+ If no `oauth_apps` are configured, the emulator accepts any `client_id` (backward-compatible). With apps configured, strict validation is enforced.
485
+
486
+ ### GitHub Apps
487
+
488
+ Full GitHub App support with JWT authentication and installation access tokens:
489
+
490
+ ```yaml
491
+ github:
492
+ apps:
493
+ - app_id: 12345
494
+ slug: "my-github-app"
495
+ name: "My GitHub App"
496
+ private_key: |
497
+ -----BEGIN RSA PRIVATE KEY-----
498
+ ...your PEM key...
499
+ -----END RSA PRIVATE KEY-----
500
+ permissions:
501
+ contents: read
502
+ issues: write
503
+ events: [push, pull_request]
504
+ webhook_url: "http://localhost:3000/webhooks/github"
505
+ webhook_secret: "my-secret"
506
+ installations:
507
+ - installation_id: 100
508
+ account: my-org
509
+ repository_selection: all
510
+ ```
511
+
512
+ JWT authentication: sign a JWT with `{ iss: "<app_id>" }` using the app's private key (RS256). The emulator verifies the signature and resolves the app.
513
+
514
+ **App webhook delivery**: When events occur on repos where a GitHub App is installed, the emulator mirrors real GitHub behavior:
515
+ - All webhook payloads (including repo and org hooks) include an `installation` field with `{ id, node_id }`.
516
+ - 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`.
517
+
518
+ ### Slack OAuth Apps
519
+
520
+ ```yaml
521
+ slack:
522
+ oauth_apps:
523
+ - client_id: "12345.67890"
524
+ client_secret: "example_client_secret"
525
+ name: "My Slack App"
526
+ redirect_uris:
527
+ - "http://localhost:3000/api/auth/callback/slack"
528
+ ```
529
+
530
+ ### Apple OAuth Clients
531
+
532
+ ```yaml
533
+ apple:
534
+ oauth_clients:
535
+ - client_id: "com.example.app"
536
+ team_id: "TEAM001"
537
+ name: "My Apple App"
538
+ redirect_uris:
539
+ - "http://localhost:3000/api/auth/callback/apple"
540
+ ```
541
+
542
+ ### Microsoft OAuth Clients
543
+
544
+ ```yaml
545
+ microsoft:
546
+ oauth_clients:
547
+ - client_id: "example-client-id"
548
+ client_secret: "example-client-secret"
549
+ name: "My Microsoft App"
550
+ redirect_uris:
551
+ - "http://localhost:3000/api/auth/callback/microsoft-entra-id"
552
+ ```
553
+
554
+ ## Vercel API
555
+
556
+ Every endpoint below is fully stateful with Vercel-style JSON responses and cursor-based pagination.
557
+
558
+ ### User & Teams
559
+ - `GET /v2/user` - authenticated user
560
+ - `PATCH /v2/user` - update user
561
+ - `GET /v2/teams` - list teams (cursor paginated)
562
+ - `GET /v2/teams/:teamId` - get team (by ID or slug)
563
+ - `POST /v2/teams` - create team
564
+ - `PATCH /v2/teams/:teamId` - update team
565
+ - `GET /v2/teams/:teamId/members` - list members
566
+ - `POST /v2/teams/:teamId/members` - add member
567
+
568
+ ### Projects
569
+ - `POST /v11/projects` - create project (with optional env vars and git integration)
570
+ - `GET /v10/projects` - list projects (search, cursor pagination)
571
+ - `GET /v9/projects/:idOrName` - get project (includes env vars)
572
+ - `PATCH /v9/projects/:idOrName` - update project
573
+ - `DELETE /v9/projects/:idOrName` - delete project (cascades)
574
+ - `GET /v1/projects/:projectId/promote/aliases` - promote aliases status
575
+ - `PATCH /v1/projects/:idOrName/protection-bypass` - manage bypass secrets
576
+
577
+ ### Deployments
578
+ - `POST /v13/deployments` - create deployment (auto-transitions to READY)
579
+ - `GET /v13/deployments/:idOrUrl` - get deployment (by ID or URL)
580
+ - `GET /v6/deployments` - list deployments (filter by project, target, state)
581
+ - `DELETE /v13/deployments/:id` - delete deployment (cascades)
582
+ - `PATCH /v12/deployments/:id/cancel` - cancel building deployment
583
+ - `GET /v2/deployments/:id/aliases` - list deployment aliases
584
+ - `GET /v3/deployments/:idOrUrl/events` - get build events/logs
585
+ - `GET /v6/deployments/:id/files` - list deployment files
586
+ - `POST /v2/files` - upload file (by SHA digest)
587
+
588
+ ### Domains
589
+ - `POST /v10/projects/:idOrName/domains` - add domain (with verification challenge)
590
+ - `GET /v9/projects/:idOrName/domains` - list domains
591
+ - `GET /v9/projects/:idOrName/domains/:domain` - get domain
592
+ - `PATCH /v9/projects/:idOrName/domains/:domain` - update domain
593
+ - `DELETE /v9/projects/:idOrName/domains/:domain` - remove domain
594
+ - `POST /v9/projects/:idOrName/domains/:domain/verify` - verify domain
595
+
596
+ ### Environment Variables
597
+ - `GET /v10/projects/:idOrName/env` - list env vars (with decrypt option)
598
+ - `POST /v10/projects/:idOrName/env` - create env vars (single, batch, upsert)
599
+ - `GET /v10/projects/:idOrName/env/:id` - get env var
600
+ - `PATCH /v9/projects/:idOrName/env/:id` - update env var
601
+ - `DELETE /v9/projects/:idOrName/env/:id` - delete env var
602
+
603
+ ## GitHub API
604
+
605
+ Every endpoint below is fully stateful. Creates, updates, and deletes persist in memory and affect related entities.
606
+
607
+ ### Users
608
+ - `GET /user` - authenticated user
609
+ - `PATCH /user` - update profile
610
+ - `GET /users/:username` - get user
611
+ - `GET /users` - list users
612
+ - `GET /users/:username/repos` - list user repos
613
+ - `GET /users/:username/orgs` - list user orgs
614
+ - `GET /users/:username/followers` - list followers
615
+ - `GET /users/:username/following` - list following
616
+
617
+ ### Repositories
618
+ - `GET /repos/:owner/:repo` - get repo
619
+ - `POST /user/repos` - create user repo
620
+ - `POST /orgs/:org/repos` - create org repo
621
+ - `PATCH /repos/:owner/:repo` - update repo
622
+ - `DELETE /repos/:owner/:repo` - delete repo (cascades)
623
+ - `GET/PUT /repos/:owner/:repo/topics` - get/replace topics
624
+ - `GET /repos/:owner/:repo/languages` - languages
625
+ - `GET /repos/:owner/:repo/contributors` - contributors
626
+ - `GET /repos/:owner/:repo/forks` - list forks
627
+ - `POST /repos/:owner/:repo/forks` - create fork
628
+ - `GET/PUT/DELETE /repos/:owner/:repo/collaborators/:username` - collaborators
629
+ - `GET /repos/:owner/:repo/collaborators/:username/permission`
630
+ - `POST /repos/:owner/:repo/transfer` - transfer repo
631
+ - `GET /repos/:owner/:repo/tags` - list tags
632
+
633
+ ### Issues
634
+ - `GET /repos/:owner/:repo/issues` - list (filter by state, labels, assignee, milestone, creator, since)
635
+ - `POST /repos/:owner/:repo/issues` - create
636
+ - `GET /repos/:owner/:repo/issues/:number` - get
637
+ - `PATCH /repos/:owner/:repo/issues/:number` - update (state transitions, events)
638
+ - `PUT/DELETE /repos/:owner/:repo/issues/:number/lock` - lock/unlock
639
+ - `GET /repos/:owner/:repo/issues/:number/timeline` - timeline events
640
+ - `GET /repos/:owner/:repo/issues/:number/events` - events
641
+ - `POST/DELETE /repos/:owner/:repo/issues/:number/assignees` - manage assignees
642
+
643
+ ### Pull Requests
644
+ - `GET /repos/:owner/:repo/pulls` - list (filter by state, head, base)
645
+ - `POST /repos/:owner/:repo/pulls` - create
646
+ - `GET /repos/:owner/:repo/pulls/:number` - get
647
+ - `PATCH /repos/:owner/:repo/pulls/:number` - update
648
+ - `PUT /repos/:owner/:repo/pulls/:number/merge` - merge (with branch protection enforcement)
649
+ - `GET /repos/:owner/:repo/pulls/:number/commits` - list commits
650
+ - `GET /repos/:owner/:repo/pulls/:number/files` - list files
651
+ - `POST/DELETE /repos/:owner/:repo/pulls/:number/requested_reviewers` - manage reviewers
652
+ - `PUT /repos/:owner/:repo/pulls/:number/update-branch` - update branch
653
+
654
+ ### Comments
655
+ - Issue comments: full CRUD on `/repos/:owner/:repo/issues/:number/comments`
656
+ - Review comments: full CRUD on `/repos/:owner/:repo/pulls/:number/comments`
657
+ - Commit comments: full CRUD on `/repos/:owner/:repo/commits/:sha/comments`
658
+ - Repo-wide listings for each type
659
+
660
+ ### Reviews
661
+ - `GET /repos/:owner/:repo/pulls/:number/reviews` - list
662
+ - `POST /repos/:owner/:repo/pulls/:number/reviews` - create (with inline comments)
663
+ - `GET/PUT /repos/:owner/:repo/pulls/:number/reviews/:id` - get/update
664
+ - `POST /repos/:owner/:repo/pulls/:number/reviews/:id/events` - submit
665
+ - `PUT /repos/:owner/:repo/pulls/:number/reviews/:id/dismissals` - dismiss
666
+
667
+ ### Labels & Milestones
668
+ - Labels: full CRUD, add/remove from issues, replace all
669
+ - Milestones: full CRUD, state transitions, issue counts
670
+
671
+ ### Branches & Git Data
672
+ - Branches: list, get, protection CRUD (status checks, PR reviews, enforce admins)
673
+ - Refs: get, match, create, update, delete
674
+ - Commits: get, create
675
+ - Trees: get (with recursive), create (with inline content)
676
+ - Blobs: get, create
677
+ - Tags: get, create
678
+
679
+ ### Organizations & Teams
680
+ - Orgs: get, update, list
681
+ - Org members: list, check, remove, get/set membership
682
+ - Teams: full CRUD, members, repos
683
+
684
+ ### Releases
685
+ - Releases: full CRUD, latest, by tag
686
+ - Release assets: full CRUD, upload
687
+ - Generate release notes
688
+
689
+ ### Webhooks
690
+ - Repo webhooks: full CRUD, ping, test, deliveries
691
+ - Org webhooks: full CRUD, ping
692
+ - Real HTTP delivery to registered URLs on all state changes
693
+
694
+ ### Search
695
+ - `GET /search/repositories` - full query syntax (user, org, language, topic, stars, forks, etc.)
696
+ - `GET /search/issues` - issues + PRs (repo, is, author, label, milestone, state, etc.)
697
+ - `GET /search/users` - users + orgs
698
+ - `GET /search/code` - blob content search
699
+ - `GET /search/commits` - commit message search
700
+ - `GET /search/topics` - topic search
701
+ - `GET /search/labels` - label search
702
+
703
+ ### Actions
704
+ - Workflows: list, get, enable/disable, dispatch
705
+ - Workflow runs: list, get, cancel, rerun, delete, logs
706
+ - Jobs: list, get, logs
707
+ - Artifacts: list, get, delete
708
+ - Secrets: repo + org CRUD
709
+
710
+ ### Checks
711
+ - Check runs: create, update, get, annotations, rerequest, list by ref/suite
712
+ - Check suites: create, get, preferences, rerequest, list by ref
713
+ - Automatic suite status rollup from check run results
714
+
715
+ ### Misc
716
+ - `GET /rate_limit` - rate limit status
717
+ - `GET /meta` - server metadata
718
+ - `GET /octocat` - ASCII art
719
+ - `GET /emojis` - emoji URLs
720
+ - `GET /zen` - random zen phrase
721
+ - `GET /versions` - API versions
722
+
723
+ ## Google OAuth + Gmail, Calendar, and Drive APIs
724
+
725
+ OAuth 2.0, OpenID Connect, and mutable Google Workspace-style surfaces for local inbox, calendar, and drive flows.
726
+
727
+ - `GET /o/oauth2/v2/auth` - authorization endpoint
728
+ - `POST /oauth2/token` - token exchange
729
+ - `GET /oauth2/v2/userinfo` - get user info
730
+ - `GET /.well-known/openid-configuration` - OIDC discovery document
731
+ - `GET /oauth2/v3/certs` - JSON Web Key Set (JWKS)
732
+ - `GET /gmail/v1/users/:userId/messages` - list messages with `q`, `labelIds`, `maxResults`, and `pageToken`
733
+ - `GET /gmail/v1/users/:userId/messages/:id` - fetch a Gmail-style message payload in `full`, `metadata`, `minimal`, or `raw` formats
734
+ - `GET /gmail/v1/users/:userId/messages/:messageId/attachments/:id` - fetch attachment bodies
735
+ - `POST /gmail/v1/users/:userId/messages/send` - create sent mail from `raw` MIME or structured fields
736
+ - `POST /gmail/v1/users/:userId/messages/import` - import inbox mail
737
+ - `POST /gmail/v1/users/:userId/messages` - insert a message directly
738
+ - `POST /gmail/v1/users/:userId/messages/:id/modify` - add/remove labels on one message
739
+ - `POST /gmail/v1/users/:userId/messages/batchModify` - add/remove labels across many messages
740
+ - `POST /gmail/v1/users/:userId/messages/:id/trash` and `POST /gmail/v1/users/:userId/messages/:id/untrash`
741
+ - `GET /gmail/v1/users/:userId/drafts`, `POST /gmail/v1/users/:userId/drafts`, `GET /gmail/v1/users/:userId/drafts/:id`, `PUT /gmail/v1/users/:userId/drafts/:id`, `POST /gmail/v1/users/:userId/drafts/:id/send`, `DELETE /gmail/v1/users/:userId/drafts/:id`
742
+ - `POST /gmail/v1/users/:userId/threads/:id/modify` - add/remove labels across a thread
743
+ - `GET /gmail/v1/users/:userId/threads` and `GET /gmail/v1/users/:userId/threads/:id`
744
+ - `GET /gmail/v1/users/:userId/labels`, `POST /gmail/v1/users/:userId/labels`, `PATCH /gmail/v1/users/:userId/labels/:id`, `DELETE /gmail/v1/users/:userId/labels/:id`
745
+ - `GET /gmail/v1/users/:userId/history`, `POST /gmail/v1/users/:userId/watch`, `POST /gmail/v1/users/:userId/stop`
746
+ - `GET /gmail/v1/users/:userId/settings/filters`, `POST /gmail/v1/users/:userId/settings/filters`, `DELETE /gmail/v1/users/:userId/settings/filters/:id`
747
+ - `GET /gmail/v1/users/:userId/settings/forwardingAddresses`, `GET /gmail/v1/users/:userId/settings/sendAs`
748
+ - `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`
749
+ - `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`
750
+
751
+ ## Slack API
752
+
753
+ Fully stateful Slack Web API emulation with channels, messages, threads, reactions, user profiles, presence, modern file uploads, pins, bookmarks, views, OAuth v2, and incoming webhooks. Chat writes preserve common rich message fields such as `blocks`, `attachments`, `metadata`, formatting flags, unfurl flags, and client message ids. Conversation writes update archive state, names, topics, purposes, membership, DMs, MPIMs, and read cursors. User writes update profile fields, status, custom fields, and deterministic active or away presence. File writes support the current external upload flow with local upload URLs, file share messages, reads, lists, downloads, and deletes. Pin and bookmark writes support channel message pins and link bookmarks. View writes support App Home publishing and modal stacks. Seeded OAuth apps and OAuth installs create bot users and installation records. OAuth exchanges and explicit token seeds create scoped token records. Supported write state changes dispatch Slack `event_callback` payloads to configured webhook URLs.
754
+
755
+ ### Auth & Chat
756
+ - `POST /api/auth.test` - test authentication
757
+ - `POST /api/chat.postMessage` - post message with text or rich payload fields (supports threads via `thread_ts` and DM user IDs)
758
+ - `POST /api/chat.postEphemeral` - post ephemeral message outside channel history
759
+ - `POST /api/chat.update` - update message text and rich payload fields
760
+ - `POST /api/chat.delete` - delete message
761
+ - `GET /api/chat.getPermalink` / `POST /api/chat.getPermalink` - get message permalink
762
+ - `POST /api/chat.scheduleMessage` - schedule pending message
763
+ - `POST /api/chat.deleteScheduledMessage` - delete pending scheduled message
764
+ - `POST /api/chat.scheduledMessages.list` - list pending scheduled messages
765
+ - `POST /api/chat.meMessage` - /me message
766
+
767
+ ### Conversations
768
+ - `POST /api/conversations.list` - list conversations (cursor pagination, `types`, `exclude_archived`)
769
+ - `POST /api/conversations.info` - get channel info
770
+ - `POST /api/conversations.create` - create channel
771
+ - `POST /api/conversations.archive` / `conversations.unarchive` - archive/restore channel
772
+ - `POST /api/conversations.rename` - rename channel
773
+ - `POST /api/conversations.setTopic` / `conversations.setPurpose` - update topic/purpose
774
+ - `POST /api/conversations.history` - channel history with rich message fields
775
+ - `POST /api/conversations.replies` - thread replies with rich message fields
776
+ - `POST /api/conversations.join` / `conversations.leave` - join/leave
777
+ - `POST /api/conversations.invite` / `conversations.kick` - manage membership
778
+ - `POST /api/conversations.open` / `conversations.close` - open/close DMs and MPIMs
779
+ - `POST /api/conversations.mark` - mark read cursor
780
+ - `POST /api/conversations.members` - list members
781
+
782
+ ### Users & Reactions
783
+ - `POST /api/users.list` - list users (cursor pagination)
784
+ - `POST /api/users.info` - get user info
785
+ - `POST /api/users.lookupByEmail` - lookup by email
786
+ - `GET /api/users.profile.get` / `POST /api/users.profile.get` - get user profile fields
787
+ - `POST /api/users.profile.set` - update profile fields, status, and custom fields
788
+ - `GET /api/users.getPresence` / `POST /api/users.getPresence` - get active or away presence
789
+ - `POST /api/users.setPresence` - set the authed user to away or automatic presence
790
+ - `POST /api/reactions.add` / `reactions.remove` / `reactions.get` - manage reactions
791
+
792
+ ### Files
793
+ - `POST /api/files.getUploadURLExternal` - create a local external upload session
794
+ - `POST /upload/v1/:fileId` - receive raw uploaded file bytes
795
+ - `POST /api/files.completeUploadExternal` - complete uploads and optionally share file messages
796
+ - `GET /api/files.info` / `POST /api/files.info` - get file metadata
797
+ - `GET /api/files.list` / `POST /api/files.list` - list completed files
798
+ - `GET /files-pri/:fileId/:filename` - download file bytes with a bearer token that can access the file
799
+ - `POST /api/files.delete` - delete a completed file
800
+
801
+ ### Pins & Bookmarks
802
+ - `POST /api/pins.add` - pin a message to a channel
803
+ - `GET /api/pins.list` / `POST /api/pins.list` - list pinned message items for a channel
804
+ - `POST /api/pins.remove` - remove a message pin from a channel
805
+ - `POST /api/bookmarks.add` - add a link bookmark to a channel
806
+ - `POST /api/bookmarks.edit` - update a link bookmark
807
+ - `POST /api/bookmarks.list` - list channel bookmarks
808
+ - `POST /api/bookmarks.remove` - remove a bookmark from a channel
809
+
810
+ ### Views
811
+ - `POST /api/views.publish` - publish or update an App Home view for a user
812
+ - `POST /api/views.open` - open a modal view
813
+ - `POST /api/views.update` - update a view by `view_id` or `external_id`
814
+ - `POST /api/views.push` - push a modal view onto the current modal stack
815
+ - `POST /api/views.generateTriggerId` - local helper for tests that need a modal trigger id
816
+
817
+ Modal opens and pushes require values from `/api/views.generateTriggerId`. Pass the returned value as `trigger_id` or `interactivity_pointer`; generate push values with an existing `view_id` and use them within 3 seconds.
818
+
819
+ ### Team, Bots & Webhooks
820
+ - `POST /api/team.info` - workspace info
821
+ - `POST /api/bots.info` - bot info
822
+ - `POST /services/:teamId/:botId/:webhookId` - incoming webhook with text or rich payload fields
823
+
824
+ ### OAuth
825
+ - `GET /oauth/v2/authorize` - authorization (shows user picker)
826
+ - `POST /oauth/v2/authorize/callback` - local user picker callback that creates the auth code
827
+ - `POST /api/oauth.v2.access` - token exchange
828
+
829
+ ### Inspector
830
+ - `GET /` - tabbed local inspector for conversations, messages, files, views, auth records, incoming webhooks, event subscriptions, and event deliveries
831
+
832
+ Slack scope checks are relaxed by default so local tests can use simple bearer tokens. Set `slack.strict_scopes: true` in seed config to make supported Web API methods return Slack-style `missing_scope` errors with `needed` and `provided` fields. Strict mode checks `chat:write`, `channels:read`, `channels:history`, `channels:join`, `channels:manage`, `channels:write`, `groups:read`, `groups:history`, `groups:write`, `im:read`, `im:history`, `im:write`, `mpim:read`, `mpim:history`, `mpim:write`, `users:read`, `users:read.email`, `users.profile:read`, `users.profile:write`, `users:write`, `files:read`, `files:write`, `pins:read`, `pins:write`, `bookmarks:read`, `bookmarks:write`, `reactions:read`, `reactions:write`, and `team:read`. Slack lists no method-specific scopes for `views.publish`, `views.open`, `views.update`, or `views.push`, so the emulator requires auth but does not add strict-scope checks for those methods.
833
+
834
+ Current Slack limits: Slack Connect, Enterprise Grid admin APIs, Audit Logs API, SCIM, Legal Holds, Socket Mode, slash command and interaction simulation, user groups, reminders, stars, calls, canvases, lists, functions, workflows, chat streaming, legacy `files.upload`, exact rate limiting, and paid-plan behavior are not implemented.
835
+
836
+ ## Apple Sign In
837
+
838
+ Sign in with Apple emulation with authorization code flow, PKCE support, RS256 ID tokens, and OIDC discovery.
839
+
840
+ - `GET /.well-known/openid-configuration` - OIDC discovery document
841
+ - `GET /auth/keys` - JSON Web Key Set (JWKS)
842
+ - `GET /auth/authorize` - authorization endpoint (shows user picker)
843
+ - `POST /auth/token` - token exchange (authorization code and refresh token grants)
844
+ - `POST /auth/revoke` - token revocation
845
+
846
+ ## Microsoft Entra ID
847
+
848
+ 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.
849
+
850
+ - `GET /.well-known/openid-configuration` - OIDC discovery document
851
+ - `GET /:tenant/v2.0/.well-known/openid-configuration` - tenant-scoped OIDC discovery
852
+ - `GET /discovery/v2.0/keys` - JSON Web Key Set (JWKS)
853
+ - `GET /oauth2/v2.0/authorize` - authorization endpoint (shows user picker)
854
+ - `POST /oauth2/v2.0/token` - token exchange (authorization code, refresh token, client credentials)
855
+ - `GET /oidc/userinfo` - OpenID Connect user info
856
+ - `GET /v1.0/me` - Microsoft Graph user profile
857
+ - `GET /oauth2/v2.0/logout` - end session / logout
858
+ - `POST /oauth2/v2.0/revoke` - token revocation
859
+
860
+ ## AWS
861
+
862
+ S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths and query-style SQS/IAM/STS endpoints. All responses use AWS-compatible XML.
863
+
864
+ ### S3
865
+
866
+ S3 routes use root paths matching the real AWS S3 wire format, so the official AWS SDK works out of the box with `forcePathStyle: true`. Legacy `/s3/` prefixed paths are also supported for backward compatibility.
867
+
868
+ - `GET /` - list all buckets
869
+ - `PUT /:bucket` - create bucket
870
+ - `DELETE /:bucket` - delete bucket
871
+ - `HEAD /:bucket` - check existence
872
+ - `GET /:bucket` - list objects (prefix, delimiter, max-keys, continuation-token, start-after)
873
+ - `POST /:bucket` - presigned POST upload (browser-style multipart form with policy validation)
874
+ - `PUT /:bucket/:key` - put object (supports copy via `x-amz-copy-source`)
875
+ - `GET /:bucket/:key` - get object
876
+ - `HEAD /:bucket/:key` - head object
877
+ - `DELETE /:bucket/:key` - delete object
878
+
879
+ ### SQS
880
+ All operations via `POST /sqs/` with `Action` parameter:
881
+ - `CreateQueue`, `ListQueues`, `GetQueueUrl`, `GetQueueAttributes`
882
+ - `SendMessage`, `ReceiveMessage`, `DeleteMessage`
883
+ - `PurgeQueue`, `DeleteQueue`
884
+
885
+ ### IAM
886
+ All operations via `POST /iam/` with `Action` parameter:
887
+ - `CreateUser`, `GetUser`, `ListUsers`, `DeleteUser`
888
+ - `CreateAccessKey`, `ListAccessKeys`, `DeleteAccessKey`
889
+ - `CreateRole`, `GetRole`, `ListRoles`, `DeleteRole`
890
+
891
+ ### STS
892
+ All operations via `POST /sts/` with `Action` parameter:
893
+ - `GetCallerIdentity`, `AssumeRole`
894
+
895
+ ## Next.js Integration
896
+
897
+ 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.
898
+
899
+ ### Install
900
+
901
+ ```bash
902
+ npm install @emulators/adapter-next @emulators/github @emulators/google
903
+ ```
904
+
905
+ Only install the emulators you need. Each `@emulators/*` package is published independently.
906
+
907
+ ### Route handler
908
+
909
+ Create a catch-all route that serves emulator traffic:
910
+
911
+ ```typescript
912
+ // app/emulate/[...path]/route.ts
913
+ import { createEmulateHandler } from '@emulators/adapter-next'
914
+ import * as github from '@emulators/github'
915
+ import * as google from '@emulators/google'
916
+
917
+ export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
918
+ services: {
919
+ github: {
920
+ emulator: github,
921
+ seed: {
922
+ users: [{ login: 'octocat', name: 'The Octocat' }],
923
+ repos: [{ owner: 'octocat', name: 'hello-world', auto_init: true }],
924
+ },
925
+ },
926
+ google: {
927
+ emulator: google,
928
+ seed: {
929
+ users: [{ email: 'test@example.com', name: 'Test User' }],
930
+ },
931
+ },
932
+ },
933
+ })
934
+ ```
935
+
936
+ ### Auth.js / NextAuth configuration
937
+
938
+ Point your provider at the emulator paths on the same origin:
939
+
940
+ ```typescript
941
+ import GitHub from 'next-auth/providers/github'
942
+
943
+ const baseUrl = process.env.VERCEL_URL
944
+ ? `https://${process.env.VERCEL_URL}`
945
+ : 'http://localhost:3000'
946
+
947
+ GitHub({
948
+ clientId: 'any-value',
949
+ clientSecret: 'any-value',
950
+ authorization: { url: `${baseUrl}/emulate/github/login/oauth/authorize` },
951
+ token: { url: `${baseUrl}/emulate/github/login/oauth/access_token` },
952
+ userinfo: { url: `${baseUrl}/emulate/github/user` },
953
+ })
954
+ ```
955
+
956
+ No `oauth_apps` need to be seeded. When none are configured, the emulator skips `client_id`, `client_secret`, and `redirect_uri` validation.
957
+
958
+ ### Font files in serverless
959
+
960
+ Emulator UI pages use bundled fonts. Wrap your Next.js config to include them in the serverless trace:
961
+
962
+ ```typescript
963
+ // next.config.mjs
964
+ import { withEmulate } from '@emulators/adapter-next'
965
+
966
+ export default withEmulate({
967
+ // your normal Next.js config
968
+ })
969
+ ```
970
+
971
+ If you mount the catch-all at a custom path, pass the matching prefix:
972
+
973
+ ```typescript
974
+ export default withEmulate(nextConfig, { routePrefix: '/api/emulate' })
975
+ ```
976
+
977
+ ### Persistence
978
+
979
+ By default, emulator state is in-memory and resets on every cold start. To persist state across restarts, pass a `persistence` adapter:
980
+
981
+ ```typescript
982
+ import { createEmulateHandler } from '@emulators/adapter-next'
983
+ import * as github from '@emulators/github'
984
+
985
+ const kvAdapter = {
986
+ async load() { return await kv.get('emulate-state') },
987
+ async save(data: string) { await kv.set('emulate-state', data) },
988
+ }
989
+
990
+ export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
991
+ services: { github: { emulator: github } },
992
+ persistence: kvAdapter,
993
+ })
994
+ ```
995
+
996
+ For local development, `@emulators/core` ships `filePersistence`:
997
+
998
+ ```typescript
999
+ import { filePersistence } from '@emulators/core'
1000
+
1001
+ // ...
1002
+ persistence: filePersistence('.emulate/state.json'),
1003
+ ```
1004
+
1005
+ 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.
1006
+
1007
+ ## Architecture
1008
+
1009
+ ```
1010
+ packages/
1011
+ emulate/ # CLI entry point (commander)
1012
+ @emulators/
1013
+ core/ # HTTP server, in-memory store, plugin interface, middleware
1014
+ adapter-next/ # Next.js App Router integration
1015
+ vercel/ # Vercel API service
1016
+ github/ # GitHub API service
1017
+ google/ # Google OAuth 2.0 / OIDC + Gmail, Calendar, Drive
1018
+ slack/ # Slack Web API, OAuth v2, incoming webhooks
1019
+ apple/ # Apple Sign In / OIDC
1020
+ microsoft/ # Microsoft Entra ID OAuth 2.0 / OIDC + Graph /me
1021
+ aws/ # AWS S3, SQS, IAM, STS
1022
+ apps/
1023
+ web/ # Documentation site (Next.js), deployed to docs.emulators.dev
1024
+ ```
1025
+
1026
+ The core provides a generic `Store` with typed `Collection<T>` instances supporting CRUD, indexing, filtering, and pagination. Each service plugin registers its routes with the shared internal app and uses the store for state.
1027
+
1028
+ ## Auth
1029
+
1030
+ Tokens are configured in the seed config and map to users. Pass them as `Authorization: Bearer <token>` or `Authorization: token <token>`.
1031
+
1032
+ **Vercel**: All endpoints accept `teamId` or `slug` query params for team scoping. Pagination uses cursor-based `limit`/`since`/`until` with `pagination` response objects.
1033
+
1034
+ **GitHub**: Public repo endpoints work without auth. Private repos and write operations require a valid token. Pagination uses `page`/`per_page` with `Link` headers.
1035
+
1036
+ **Google**: Standard OAuth 2.0 authorization code flow. Configure clients in the seed config.
1037
+
1038
+ **Slack**: All Web API endpoints require `Authorization: Bearer <token>`. Seeded OAuth apps create local installation records, and OAuth v2 flow with user picker UI creates scoped bot tokens. Optional strict scope mode returns `missing_scope` when a token lacks a required method scope.
1039
+
1040
+ **Apple**: OIDC authorization code flow with RS256 ID tokens. On first auth per user/client pair, a `user` JSON blob is included.
1041
+
1042
+ **Microsoft**: OIDC authorization code flow with PKCE support. Also supports client credentials grants. Microsoft Graph `/v1.0/me` available.
1043
+
1044
+ **AWS**: Bearer tokens or IAM access key credentials. Default key pair always seeded: `AKIAIOSFODNN7EXAMPLE` / `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`.