emulate 0.4.1 → 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 (50) hide show
  1. package/README.md +198 -27
  2. package/dist/api.d.ts +2 -1
  3. package/dist/api.js +675 -103
  4. package/dist/api.js.map +1 -1
  5. package/dist/chunk-WVQMFHQM.js +83 -0
  6. package/dist/chunk-WVQMFHQM.js.map +1 -0
  7. package/dist/{dist-B674PYKV.js → dist-2ZZGNPJI.js} +22 -43
  8. package/dist/dist-2ZZGNPJI.js.map +1 -0
  9. package/dist/{dist-RDFBZ5O6.js → dist-CXRPM6BK.js} +211 -48
  10. package/dist/dist-CXRPM6BK.js.map +1 -0
  11. package/dist/{dist-VVXVP5EZ.js → dist-DSJSF3GY.js} +551 -91
  12. package/dist/dist-DSJSF3GY.js.map +1 -0
  13. package/dist/{dist-RMK3BS5M.js → dist-IFULY5LE.js} +196 -33
  14. package/dist/dist-IFULY5LE.js.map +1 -0
  15. package/dist/dist-IRUBHCZU.js +1898 -0
  16. package/dist/dist-IRUBHCZU.js.map +1 -0
  17. package/dist/{dist-YOVM5HEY.js → dist-NJJLJT2N.js} +520 -61
  18. package/dist/dist-NJJLJT2N.js.map +1 -0
  19. package/dist/dist-OGSAVJ25.js +4874 -0
  20. package/dist/dist-OGSAVJ25.js.map +1 -0
  21. package/dist/{dist-H6JYGQM4.js → dist-PO4CL5SJ.js} +271 -158
  22. package/dist/dist-PO4CL5SJ.js.map +1 -0
  23. package/dist/{dist-QMOJM6DV.js → dist-R3TNKUIE.js} +238 -55
  24. package/dist/dist-R3TNKUIE.js.map +1 -0
  25. package/dist/{dist-6JFNJPUU.js → dist-WACHAAVU.js} +171 -22
  26. package/dist/dist-WACHAAVU.js.map +1 -0
  27. package/dist/{dist-OTJZRQ3Q.js → dist-XWWZVLQQ.js} +216 -75
  28. package/dist/dist-XWWZVLQQ.js.map +1 -0
  29. package/dist/{dist-6EW7SSOZ.js → dist-ZY5SZSJ2.js} +397 -223
  30. package/dist/dist-ZY5SZSJ2.js.map +1 -0
  31. package/dist/fonts/favicon.ico +0 -0
  32. package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
  33. package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
  34. package/dist/index.js +812 -117
  35. package/dist/index.js.map +1 -1
  36. package/package.json +17 -15
  37. package/dist/chunk-TEPNEZ63.js +0 -2143
  38. package/dist/chunk-TEPNEZ63.js.map +0 -1
  39. package/dist/dist-6EW7SSOZ.js.map +0 -1
  40. package/dist/dist-6JFNJPUU.js.map +0 -1
  41. package/dist/dist-B674PYKV.js.map +0 -1
  42. package/dist/dist-G7WQPZ3Y.js +0 -1287
  43. package/dist/dist-G7WQPZ3Y.js.map +0 -1
  44. package/dist/dist-H6JYGQM4.js.map +0 -1
  45. package/dist/dist-OTJZRQ3Q.js.map +0 -1
  46. package/dist/dist-QMOJM6DV.js.map +0 -1
  47. package/dist/dist-RDFBZ5O6.js.map +0 -1
  48. package/dist/dist-RMK3BS5M.js.map +0 -1
  49. package/dist/dist-VVXVP5EZ.js.map +0 -1
  50. package/dist/dist-YOVM5HEY.js.map +0 -1
package/README.md CHANGED
@@ -22,25 +22,25 @@ All services start with sensible defaults. No config file needed:
22
22
 
23
23
  ```bash
24
24
  # Start all services (zero-config)
25
- emulate
25
+ npx emulate
26
26
 
27
27
  # Start specific services
28
- emulate --service vercel,github
28
+ npx emulate --service vercel,github
29
29
 
30
30
  # Custom port
31
- emulate --port 3000
31
+ npx emulate --port 3000
32
32
 
33
33
  # Use a seed config file
34
- emulate --seed config.yaml
34
+ npx emulate --seed config.yaml
35
35
 
36
36
  # Generate a starter config
37
- emulate init
37
+ npx emulate init
38
38
 
39
39
  # Generate config for a specific service
40
- emulate init --service vercel
40
+ npx emulate init --service vercel
41
41
 
42
42
  # List available services
43
- emulate list
43
+ npx emulate list
44
44
  ```
45
45
 
46
46
  ### Options
@@ -50,9 +50,50 @@ emulate list
50
50
  | `-p, --port` | `4000` | Base port (auto-increments per service) |
51
51
  | `-s, --service` | all | Comma-separated services to enable |
52
52
  | `--seed` | auto-detect | Path to seed config (YAML or JSON) |
53
+ | `--base-url` | none | Override advertised base URL (supports `{service}` template) |
54
+ | `--portless` | off | Serve over HTTPS via portless (auto-registers aliases) |
53
55
 
54
56
  The port can also be set via `EMULATE_PORT` or `PORT` environment variables.
55
57
 
58
+ ## HTTPS with portless
59
+
60
+ [portless](https://github.com/vercel-labs/portless) gives emulators trusted HTTPS URLs with auto-generated certs and no browser warnings.
61
+
62
+ ```bash
63
+ # Start the portless proxy (first time only)
64
+ portless proxy start
65
+
66
+ # Start emulate with portless integration
67
+ npx emulate start --portless
68
+ ```
69
+
70
+ Each service registers as a portless alias and gets a named HTTPS URL:
71
+
72
+ ```
73
+ github https://github.emulate.localhost
74
+ google https://google.emulate.localhost
75
+ slack https://slack.emulate.localhost
76
+ ```
77
+
78
+ If portless is not installed, emulate will prompt to install it (`npm i -g portless`).
79
+
80
+ The `--portless` flag overwrites any existing portless aliases matching `*.emulate`. Aliases are removed automatically when emulate shuts down.
81
+
82
+ For a custom base URL without portless (any reverse proxy), use `--base-url` or the `EMULATE_BASE_URL` env var:
83
+
84
+ ```bash
85
+ npx emulate start --base-url "https://{service}.myproxy.test"
86
+ ```
87
+
88
+ The `PORTLESS_URL` env var is automatically set by the `portless` CLI wrapper when running a command through it (e.g. `portless github.emulate emulate start`), typically to a value like `https://{service}.emulate.localhost`. It supports `{service}` interpolation, just like `--base-url` and `EMULATE_BASE_URL`. When no explicit `baseUrl` is provided, it is used as a fallback.
89
+
90
+ Per-service overrides are also supported in the seed config (these take highest priority over all other base URL sources):
91
+
92
+ ```yaml
93
+ github:
94
+ baseUrl: https://github.emulate.localhost
95
+ ```
96
+
56
97
  ## Programmatic API
57
98
 
58
99
  ```bash
@@ -103,6 +144,7 @@ afterAll(() => Promise.all([github.close(), vercel.close()]))
103
144
  | `service` | *(required)* | Service name: `'vercel'`, `'github'`, `'google'`, `'slack'`, `'apple'`, `'microsoft'`, or `'aws'` |
104
145
  | `port` | `4000` | Port for the HTTP server |
105
146
  | `seed` | none | Inline seed data (same shape as YAML config) |
147
+ | `baseUrl` | none | Override advertised base URL. Per-service `baseUrl` in seed config takes highest priority, then this option, then `EMULATE_BASE_URL` env var (supports `{service}`), then `PORTLESS_URL` (supports `{service}`, automatically set by the `portless` CLI wrapper), then `http://localhost:<port>`. |
106
148
 
107
149
  ### Instance methods
108
150
 
@@ -114,7 +156,7 @@ afterAll(() => Promise.all([github.close(), vercel.close()]))
114
156
 
115
157
  ## Configuration
116
158
 
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.
159
+ 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.
118
160
 
119
161
  ```yaml
120
162
  tokens:
@@ -153,6 +195,9 @@ google:
153
195
  users:
154
196
  - email: testuser@example.com
155
197
  name: Test User
198
+ - email: admin@acme.com
199
+ name: Admin
200
+ hd: acme.com
156
201
  oauth_clients:
157
202
  - client_id: my-client-id.apps.googleusercontent.com
158
203
  client_secret: GOCSPX-secret
@@ -201,6 +246,11 @@ slack:
201
246
  - name: developer
202
247
  real_name: Developer
203
248
  email: dev@example.com
249
+ profile:
250
+ title: Local Developer
251
+ status_text: Testing locally
252
+ status_emoji: ":computer:"
253
+ presence: active
204
254
  channels:
205
255
  - name: general
206
256
  topic: General discussion
@@ -211,9 +261,76 @@ slack:
211
261
  oauth_apps:
212
262
  - client_id: "12345.67890"
213
263
  client_secret: example_client_secret
264
+ app_id: A000000001
214
265
  name: My Slack App
215
266
  redirect_uris:
216
267
  - http://localhost:3000/api/auth/callback/slack
268
+ scopes:
269
+ - chat:write
270
+ - channels:read
271
+ - channels:history
272
+ - channels:join
273
+ - channels:manage
274
+ - channels:write
275
+ - groups:read
276
+ - groups:history
277
+ - groups:write
278
+ - im:read
279
+ - im:history
280
+ - im:write
281
+ - mpim:read
282
+ - mpim:history
283
+ - mpim:write
284
+ - users:read
285
+ - users:read.email
286
+ - users.profile:read
287
+ - users.profile:write
288
+ - users:write
289
+ - files:read
290
+ - files:write
291
+ - pins:read
292
+ - pins:write
293
+ - bookmarks:read
294
+ - bookmarks:write
295
+ - reactions:read
296
+ - reactions:write
297
+ - team:read
298
+ user_scopes: [users:read, users.profile:read]
299
+ bot_name: my-bot
300
+ tokens:
301
+ - token: xoxb-local-test
302
+ user: developer
303
+ scopes:
304
+ - chat:write
305
+ - channels:read
306
+ - channels:history
307
+ - channels:join
308
+ - channels:manage
309
+ - channels:write
310
+ - groups:read
311
+ - groups:history
312
+ - groups:write
313
+ - im:read
314
+ - im:history
315
+ - im:write
316
+ - mpim:read
317
+ - mpim:history
318
+ - mpim:write
319
+ - users:read
320
+ - users:read.email
321
+ - users.profile:read
322
+ - users.profile:write
323
+ - users:write
324
+ - files:read
325
+ - files:write
326
+ - pins:read
327
+ - pins:write
328
+ - bookmarks:read
329
+ - bookmarks:write
330
+ - reactions:read
331
+ - reactions:write
332
+ - team:read
333
+ strict_scopes: false
217
334
 
218
335
  apple:
219
336
  users:
@@ -553,39 +670,89 @@ OAuth 2.0, OpenID Connect, and mutable Google Workspace-style surfaces for local
553
670
 
554
671
  ## Slack API
555
672
 
556
- Fully stateful Slack Web API emulation with channels, messages, threads, reactions, OAuth v2, and incoming webhooks.
673
+ 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.
557
674
 
558
675
  ### Auth & Chat
559
676
  - `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
677
+ - `POST /api/chat.postMessage` - post message with text or rich payload fields (supports threads via `thread_ts` and DM user IDs)
678
+ - `POST /api/chat.postEphemeral` - post ephemeral message outside channel history
679
+ - `POST /api/chat.update` - update message text and rich payload fields
562
680
  - `POST /api/chat.delete` - delete message
681
+ - `GET /api/chat.getPermalink` / `POST /api/chat.getPermalink` - get message permalink
682
+ - `POST /api/chat.scheduleMessage` - schedule pending message
683
+ - `POST /api/chat.deleteScheduledMessage` - delete pending scheduled message
684
+ - `POST /api/chat.scheduledMessages.list` - list pending scheduled messages
563
685
  - `POST /api/chat.meMessage` - /me message
564
686
 
565
687
  ### Conversations
566
- - `POST /api/conversations.list` - list channels (cursor pagination)
688
+ - `POST /api/conversations.list` - list conversations (cursor pagination, `types`, `exclude_archived`)
567
689
  - `POST /api/conversations.info` - get channel info
568
690
  - `POST /api/conversations.create` - create channel
569
- - `POST /api/conversations.history` - channel history
570
- - `POST /api/conversations.replies` - thread replies
691
+ - `POST /api/conversations.archive` / `conversations.unarchive` - archive/restore channel
692
+ - `POST /api/conversations.rename` - rename channel
693
+ - `POST /api/conversations.setTopic` / `conversations.setPurpose` - update topic/purpose
694
+ - `POST /api/conversations.history` - channel history with rich message fields
695
+ - `POST /api/conversations.replies` - thread replies with rich message fields
571
696
  - `POST /api/conversations.join` / `conversations.leave` - join/leave
697
+ - `POST /api/conversations.invite` / `conversations.kick` - manage membership
698
+ - `POST /api/conversations.open` / `conversations.close` - open/close DMs and MPIMs
699
+ - `POST /api/conversations.mark` - mark read cursor
572
700
  - `POST /api/conversations.members` - list members
573
701
 
574
702
  ### Users & Reactions
575
703
  - `POST /api/users.list` - list users (cursor pagination)
576
704
  - `POST /api/users.info` - get user info
577
705
  - `POST /api/users.lookupByEmail` - lookup by email
706
+ - `GET /api/users.profile.get` / `POST /api/users.profile.get` - get user profile fields
707
+ - `POST /api/users.profile.set` - update profile fields, status, and custom fields
708
+ - `GET /api/users.getPresence` / `POST /api/users.getPresence` - get active or away presence
709
+ - `POST /api/users.setPresence` - set the authed user to away or automatic presence
578
710
  - `POST /api/reactions.add` / `reactions.remove` / `reactions.get` - manage reactions
579
711
 
712
+ ### Files
713
+ - `POST /api/files.getUploadURLExternal` - create a local external upload session
714
+ - `POST /upload/v1/:fileId` - receive raw uploaded file bytes
715
+ - `POST /api/files.completeUploadExternal` - complete uploads and optionally share file messages
716
+ - `GET /api/files.info` / `POST /api/files.info` - get file metadata
717
+ - `GET /api/files.list` / `POST /api/files.list` - list completed files
718
+ - `GET /files-pri/:fileId/:filename` - download file bytes with a bearer token that can access the file
719
+ - `POST /api/files.delete` - delete a completed file
720
+
721
+ ### Pins & Bookmarks
722
+ - `POST /api/pins.add` - pin a message to a channel
723
+ - `GET /api/pins.list` / `POST /api/pins.list` - list pinned message items for a channel
724
+ - `POST /api/pins.remove` - remove a message pin from a channel
725
+ - `POST /api/bookmarks.add` - add a link bookmark to a channel
726
+ - `POST /api/bookmarks.edit` - update a link bookmark
727
+ - `POST /api/bookmarks.list` - list channel bookmarks
728
+ - `POST /api/bookmarks.remove` - remove a bookmark from a channel
729
+
730
+ ### Views
731
+ - `POST /api/views.publish` - publish or update an App Home view for a user
732
+ - `POST /api/views.open` - open a modal view
733
+ - `POST /api/views.update` - update a view by `view_id` or `external_id`
734
+ - `POST /api/views.push` - push a modal view onto the current modal stack
735
+ - `POST /api/views.generateTriggerId` - local helper for tests that need a modal trigger id
736
+
737
+ 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.
738
+
580
739
  ### Team, Bots & Webhooks
581
740
  - `POST /api/team.info` - workspace info
582
741
  - `POST /api/bots.info` - bot info
583
- - `POST /services/:teamId/:botId/:webhookId` - incoming webhook
742
+ - `POST /services/:teamId/:botId/:webhookId` - incoming webhook with text or rich payload fields
584
743
 
585
744
  ### OAuth
586
745
  - `GET /oauth/v2/authorize` - authorization (shows user picker)
746
+ - `POST /oauth/v2/authorize/callback` - local user picker callback that creates the auth code
587
747
  - `POST /api/oauth.v2.access` - token exchange
588
748
 
749
+ ### Inspector
750
+ - `GET /` - tabbed local inspector for conversations, messages, files, views, auth records, incoming webhooks, event subscriptions, and event deliveries
751
+
752
+ 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.
753
+
754
+ 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.
755
+
589
756
  ## Apple Sign In
590
757
 
591
758
  Sign in with Apple emulation with authorization code flow, PKCE support, RS256 ID tokens, and OIDC discovery.
@@ -612,18 +779,22 @@ Microsoft Entra ID (Azure AD) v2.0 OAuth 2.0 and OpenID Connect emulation with a
612
779
 
613
780
  ## AWS
614
781
 
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.
782
+ S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths and query-style SQS/IAM/STS endpoints. All responses use AWS-compatible XML.
616
783
 
617
784
  ### 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
785
+
786
+ 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.
787
+
788
+ - `GET /` - list all buckets
789
+ - `PUT /:bucket` - create bucket
790
+ - `DELETE /:bucket` - delete bucket
791
+ - `HEAD /:bucket` - check existence
792
+ - `GET /:bucket` - list objects (prefix, delimiter, max-keys, continuation-token, start-after)
793
+ - `POST /:bucket` - presigned POST upload (browser-style multipart form with policy validation)
794
+ - `PUT /:bucket/:key` - put object (supports copy via `x-amz-copy-source`)
795
+ - `GET /:bucket/:key` - get object
796
+ - `HEAD /:bucket/:key` - head object
797
+ - `DELETE /:bucket/:key` - delete object
627
798
 
628
799
  ### SQS
629
800
  All operations via `POST /sqs/` with `Action` parameter:
@@ -772,7 +943,7 @@ apps/
772
943
  web/ # Documentation site (Next.js)
773
944
  ```
774
945
 
775
- The core provides a generic `Store` with typed `Collection<T>` instances supporting CRUD, indexing, filtering, and pagination. Each service plugin registers its routes on the shared Hono app and uses the store for state.
946
+ 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.
776
947
 
777
948
  ## Auth
778
949
 
@@ -784,7 +955,7 @@ Tokens are configured in the seed config and map to users. Pass them as `Authori
784
955
 
785
956
  **Google**: Standard OAuth 2.0 authorization code flow. Configure clients in the seed config.
786
957
 
787
- **Slack**: All Web API endpoints require `Authorization: Bearer <token>`. OAuth v2 flow with user picker UI.
958
+ **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.
788
959
 
789
960
  **Apple**: OIDC authorization code flow with RS256 ID tokens. On first auth per user/client pair, a `user` JSON blob is included.
790
961
 
package/dist/api.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- declare const SERVICE_NAME_LIST: readonly ["vercel", "github", "google", "slack", "apple", "microsoft", "okta", "aws", "resend", "stripe", "mongoatlas"];
1
+ declare const SERVICE_NAME_LIST: readonly ["vercel", "github", "google", "slack", "apple", "microsoft", "okta", "aws", "resend", "stripe", "mongoatlas", "clerk"];
2
2
  type ServiceName = (typeof SERVICE_NAME_LIST)[number];
3
3
 
4
4
  interface SeedConfig {
@@ -12,6 +12,7 @@ interface EmulatorOptions {
12
12
  service: ServiceName;
13
13
  port?: number;
14
14
  seed?: SeedConfig;
15
+ baseUrl?: string;
15
16
  }
16
17
  interface Emulator {
17
18
  url: string;