emulate 0.1.0 → 0.2.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 ADDED
@@ -0,0 +1,416 @@
1
+ # emulate
2
+
3
+ Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation. Not mocks.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx emulate
9
+ ```
10
+
11
+ All services start with sensible defaults. No config file needed:
12
+
13
+ - **Vercel** on `http://localhost:4000`
14
+ - **GitHub** on `http://localhost:4001`
15
+ - **Google** on `http://localhost:4002`
16
+
17
+ ## CLI
18
+
19
+ ```bash
20
+ # Start all services (zero-config)
21
+ emulate
22
+
23
+ # Start specific services
24
+ emulate --service vercel,github
25
+
26
+ # Custom port
27
+ emulate --port 3000
28
+
29
+ # Use a seed config file
30
+ emulate --seed config.yaml
31
+
32
+ # Generate a starter config
33
+ emulate init
34
+
35
+ # Generate config for a specific service
36
+ emulate init --service vercel
37
+
38
+ # List available services
39
+ emulate list
40
+ ```
41
+
42
+ ### Options
43
+
44
+ | Flag | Default | Description |
45
+ |------|---------|-------------|
46
+ | `-p, --port` | `4000` | Base port (auto-increments per service) |
47
+ | `-s, --service` | all | Comma-separated services to enable |
48
+ | `--seed` | auto-detect | Path to seed config (YAML or JSON) |
49
+
50
+ The port can also be set via `EMULATE_PORT` or `PORT` environment variables.
51
+
52
+ ## Programmatic API
53
+
54
+ ```bash
55
+ npm install emulate
56
+ ```
57
+
58
+ Each call to `createEmulator` starts a single service:
59
+
60
+ ```typescript
61
+ import { createEmulator } from 'emulate'
62
+
63
+ const github = await createEmulator({ service: 'github', port: 4001 })
64
+ const vercel = await createEmulator({ service: 'vercel', port: 4002 })
65
+
66
+ github.url // 'http://localhost:4001'
67
+ vercel.url // 'http://localhost:4002'
68
+
69
+ await github.close()
70
+ await vercel.close()
71
+ ```
72
+
73
+ ### Vitest / Jest setup
74
+
75
+ ```typescript
76
+ // vitest.setup.ts
77
+ import { createEmulator, type Emulator } from 'emulate'
78
+
79
+ let github: Emulator
80
+ let vercel: Emulator
81
+
82
+ beforeAll(async () => {
83
+ ;[github, vercel] = await Promise.all([
84
+ createEmulator({ service: 'github', port: 4001 }),
85
+ createEmulator({ service: 'vercel', port: 4002 }),
86
+ ])
87
+ process.env.GITHUB_URL = github.url
88
+ process.env.VERCEL_URL = vercel.url
89
+ })
90
+
91
+ afterEach(() => { github.reset(); vercel.reset() })
92
+ afterAll(() => Promise.all([github.close(), vercel.close()]))
93
+ ```
94
+
95
+ ### Options
96
+
97
+ | Option | Default | Description |
98
+ |--------|---------|-------------|
99
+ | `service` | *(required)* | Service to emulate: `'github'`, `'vercel'`, or `'google'` |
100
+ | `port` | `4000` | Port for the HTTP server |
101
+ | `seed` | none | Inline seed data (same shape as YAML config) |
102
+
103
+ ### Instance methods
104
+
105
+ | Method | Description |
106
+ |--------|-------------|
107
+ | `url` | Base URL of the running server |
108
+ | `reset()` | Wipe the store and replay seed data |
109
+ | `close()` | Shut down the HTTP server, returns a Promise |
110
+
111
+ ## Configuration
112
+
113
+ Configuration is optional. To customize seed data, create `emulate.config.yaml` in your project root (or pass `--seed`):
114
+
115
+ ```yaml
116
+ tokens:
117
+ my_token:
118
+ login: admin
119
+ scopes: [repo, user]
120
+
121
+ vercel:
122
+ users:
123
+ - username: developer
124
+ name: Developer
125
+ email: dev@example.com
126
+ teams:
127
+ - slug: my-team
128
+ name: My Team
129
+ projects:
130
+ - name: my-app
131
+ team: my-team
132
+ framework: nextjs
133
+
134
+ github:
135
+ users:
136
+ - login: octocat
137
+ name: The Octocat
138
+ email: octocat@github.com
139
+ orgs:
140
+ - login: my-org
141
+ name: My Organization
142
+ repos:
143
+ - owner: octocat
144
+ name: hello-world
145
+ language: JavaScript
146
+ auto_init: true
147
+
148
+ google:
149
+ users:
150
+ - email: testuser@example.com
151
+ name: Test User
152
+ oauth_clients:
153
+ - client_id: my-client-id.apps.googleusercontent.com
154
+ client_secret: GOCSPX-secret
155
+ redirect_uris:
156
+ - http://localhost:3000/api/auth/callback/google
157
+ ```
158
+
159
+ ## OAuth & Integrations
160
+
161
+ The emulator supports configurable OAuth apps and integrations with strict client validation.
162
+
163
+ ### Vercel Integrations
164
+
165
+ ```yaml
166
+ vercel:
167
+ integrations:
168
+ - client_id: "oac_abc123"
169
+ client_secret: "secret_abc123"
170
+ name: "My Vercel App"
171
+ redirect_uris:
172
+ - "http://localhost:3000/api/auth/callback/vercel"
173
+ ```
174
+
175
+ ### GitHub OAuth Apps
176
+
177
+ ```yaml
178
+ github:
179
+ oauth_apps:
180
+ - client_id: "Iv1.abc123"
181
+ client_secret: "secret_abc123"
182
+ name: "My Web App"
183
+ redirect_uris:
184
+ - "http://localhost:3000/api/auth/callback/github"
185
+ ```
186
+
187
+ If no `oauth_apps` are configured, the emulator accepts any `client_id` (backward-compatible). With apps configured, strict validation is enforced.
188
+
189
+ ### GitHub Apps
190
+
191
+ Full GitHub App support with JWT authentication and installation access tokens:
192
+
193
+ ```yaml
194
+ github:
195
+ apps:
196
+ - app_id: 12345
197
+ slug: "my-github-app"
198
+ name: "My GitHub App"
199
+ private_key: |
200
+ -----BEGIN RSA PRIVATE KEY-----
201
+ ...your PEM key...
202
+ -----END RSA PRIVATE KEY-----
203
+ permissions:
204
+ contents: read
205
+ issues: write
206
+ events: [push, pull_request]
207
+ installations:
208
+ - installation_id: 100
209
+ account: my-org
210
+ repository_selection: all
211
+ ```
212
+
213
+ 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.
214
+
215
+ ## Vercel API
216
+
217
+ Every endpoint below is fully stateful with Vercel-style JSON responses and cursor-based pagination.
218
+
219
+ ### User & Teams
220
+ - `GET /v2/user` - authenticated user
221
+ - `PATCH /v2/user` - update user
222
+ - `GET /v2/teams` - list teams (cursor paginated)
223
+ - `GET /v2/teams/:teamId` - get team (by ID or slug)
224
+ - `POST /v2/teams` - create team
225
+ - `PATCH /v2/teams/:teamId` - update team
226
+ - `GET /v2/teams/:teamId/members` - list members
227
+ - `POST /v2/teams/:teamId/members` - add member
228
+
229
+ ### Projects
230
+ - `POST /v11/projects` - create project (with optional env vars and git integration)
231
+ - `GET /v10/projects` - list projects (search, cursor pagination)
232
+ - `GET /v9/projects/:idOrName` - get project (includes env vars)
233
+ - `PATCH /v9/projects/:idOrName` - update project
234
+ - `DELETE /v9/projects/:idOrName` - delete project (cascades)
235
+ - `GET /v1/projects/:projectId/promote/aliases` - promote aliases status
236
+ - `PATCH /v1/projects/:idOrName/protection-bypass` - manage bypass secrets
237
+
238
+ ### Deployments
239
+ - `POST /v13/deployments` - create deployment (auto-transitions to READY)
240
+ - `GET /v13/deployments/:idOrUrl` - get deployment (by ID or URL)
241
+ - `GET /v6/deployments` - list deployments (filter by project, target, state)
242
+ - `DELETE /v13/deployments/:id` - delete deployment (cascades)
243
+ - `PATCH /v12/deployments/:id/cancel` - cancel building deployment
244
+ - `GET /v2/deployments/:id/aliases` - list deployment aliases
245
+ - `GET /v3/deployments/:idOrUrl/events` - get build events/logs
246
+ - `GET /v6/deployments/:id/files` - list deployment files
247
+ - `POST /v2/files` - upload file (by SHA digest)
248
+
249
+ ### Domains
250
+ - `POST /v10/projects/:idOrName/domains` - add domain (with verification challenge)
251
+ - `GET /v9/projects/:idOrName/domains` - list domains
252
+ - `GET /v9/projects/:idOrName/domains/:domain` - get domain
253
+ - `PATCH /v9/projects/:idOrName/domains/:domain` - update domain
254
+ - `DELETE /v9/projects/:idOrName/domains/:domain` - remove domain
255
+ - `POST /v9/projects/:idOrName/domains/:domain/verify` - verify domain
256
+
257
+ ### Environment Variables
258
+ - `GET /v10/projects/:idOrName/env` - list env vars (with decrypt option)
259
+ - `POST /v10/projects/:idOrName/env` - create env vars (single, batch, upsert)
260
+ - `GET /v10/projects/:idOrName/env/:id` - get env var
261
+ - `PATCH /v9/projects/:idOrName/env/:id` - update env var
262
+ - `DELETE /v9/projects/:idOrName/env/:id` - delete env var
263
+
264
+ ## GitHub API
265
+
266
+ Every endpoint below is fully stateful. Creates, updates, and deletes persist in memory and affect related entities.
267
+
268
+ ### Users
269
+ - `GET /user` - authenticated user
270
+ - `PATCH /user` - update profile
271
+ - `GET /users/:username` - get user
272
+ - `GET /users` - list users
273
+ - `GET /users/:username/repos` - list user repos
274
+ - `GET /users/:username/orgs` - list user orgs
275
+ - `GET /users/:username/followers` - list followers
276
+ - `GET /users/:username/following` - list following
277
+
278
+ ### Repositories
279
+ - `GET /repos/:owner/:repo` - get repo
280
+ - `POST /user/repos` - create user repo
281
+ - `POST /orgs/:org/repos` - create org repo
282
+ - `PATCH /repos/:owner/:repo` - update repo
283
+ - `DELETE /repos/:owner/:repo` - delete repo (cascades)
284
+ - `GET/PUT /repos/:owner/:repo/topics` - get/replace topics
285
+ - `GET /repos/:owner/:repo/languages` - languages
286
+ - `GET /repos/:owner/:repo/contributors` - contributors
287
+ - `GET /repos/:owner/:repo/forks` - list forks
288
+ - `POST /repos/:owner/:repo/forks` - create fork
289
+ - `GET/PUT/DELETE /repos/:owner/:repo/collaborators/:username` - collaborators
290
+ - `GET /repos/:owner/:repo/collaborators/:username/permission`
291
+ - `POST /repos/:owner/:repo/transfer` - transfer repo
292
+ - `GET /repos/:owner/:repo/tags` - list tags
293
+
294
+ ### Issues
295
+ - `GET /repos/:owner/:repo/issues` - list (filter by state, labels, assignee, milestone, creator, since)
296
+ - `POST /repos/:owner/:repo/issues` - create
297
+ - `GET /repos/:owner/:repo/issues/:number` - get
298
+ - `PATCH /repos/:owner/:repo/issues/:number` - update (state transitions, events)
299
+ - `PUT/DELETE /repos/:owner/:repo/issues/:number/lock` - lock/unlock
300
+ - `GET /repos/:owner/:repo/issues/:number/timeline` - timeline events
301
+ - `GET /repos/:owner/:repo/issues/:number/events` - events
302
+ - `POST/DELETE /repos/:owner/:repo/issues/:number/assignees` - manage assignees
303
+
304
+ ### Pull Requests
305
+ - `GET /repos/:owner/:repo/pulls` - list (filter by state, head, base)
306
+ - `POST /repos/:owner/:repo/pulls` - create
307
+ - `GET /repos/:owner/:repo/pulls/:number` - get
308
+ - `PATCH /repos/:owner/:repo/pulls/:number` - update
309
+ - `PUT /repos/:owner/:repo/pulls/:number/merge` - merge (with branch protection enforcement)
310
+ - `GET /repos/:owner/:repo/pulls/:number/commits` - list commits
311
+ - `GET /repos/:owner/:repo/pulls/:number/files` - list files
312
+ - `POST/DELETE /repos/:owner/:repo/pulls/:number/requested_reviewers` - manage reviewers
313
+ - `PUT /repos/:owner/:repo/pulls/:number/update-branch` - update branch
314
+
315
+ ### Comments
316
+ - Issue comments: full CRUD on `/repos/:owner/:repo/issues/:number/comments`
317
+ - Review comments: full CRUD on `/repos/:owner/:repo/pulls/:number/comments`
318
+ - Commit comments: full CRUD on `/repos/:owner/:repo/commits/:sha/comments`
319
+ - Repo-wide listings for each type
320
+
321
+ ### Reviews
322
+ - `GET /repos/:owner/:repo/pulls/:number/reviews` - list
323
+ - `POST /repos/:owner/:repo/pulls/:number/reviews` - create (with inline comments)
324
+ - `GET/PUT /repos/:owner/:repo/pulls/:number/reviews/:id` - get/update
325
+ - `POST /repos/:owner/:repo/pulls/:number/reviews/:id/events` - submit
326
+ - `PUT /repos/:owner/:repo/pulls/:number/reviews/:id/dismissals` - dismiss
327
+
328
+ ### Labels & Milestones
329
+ - Labels: full CRUD, add/remove from issues, replace all
330
+ - Milestones: full CRUD, state transitions, issue counts
331
+
332
+ ### Branches & Git Data
333
+ - Branches: list, get, protection CRUD (status checks, PR reviews, enforce admins)
334
+ - Refs: get, match, create, update, delete
335
+ - Commits: get, create
336
+ - Trees: get (with recursive), create (with inline content)
337
+ - Blobs: get, create
338
+ - Tags: get, create
339
+
340
+ ### Organizations & Teams
341
+ - Orgs: get, update, list
342
+ - Org members: list, check, remove, get/set membership
343
+ - Teams: full CRUD, members, repos
344
+
345
+ ### Releases
346
+ - Releases: full CRUD, latest, by tag
347
+ - Release assets: full CRUD, upload
348
+ - Generate release notes
349
+
350
+ ### Webhooks
351
+ - Repo webhooks: full CRUD, ping, test, deliveries
352
+ - Org webhooks: full CRUD, ping
353
+ - Real HTTP delivery to registered URLs on all state changes
354
+
355
+ ### Search
356
+ - `GET /search/repositories` - full query syntax (user, org, language, topic, stars, forks, etc.)
357
+ - `GET /search/issues` - issues + PRs (repo, is, author, label, milestone, state, etc.)
358
+ - `GET /search/users` - users + orgs
359
+ - `GET /search/code` - blob content search
360
+ - `GET /search/commits` - commit message search
361
+ - `GET /search/topics` - topic search
362
+ - `GET /search/labels` - label search
363
+
364
+ ### Actions
365
+ - Workflows: list, get, enable/disable, dispatch
366
+ - Workflow runs: list, get, cancel, rerun, delete, logs
367
+ - Jobs: list, get, logs
368
+ - Artifacts: list, get, delete
369
+ - Secrets: repo + org CRUD
370
+
371
+ ### Checks
372
+ - Check runs: create, update, get, annotations, rerequest, list by ref/suite
373
+ - Check suites: create, get, preferences, rerequest, list by ref
374
+ - Automatic suite status rollup from check run results
375
+
376
+ ### Misc
377
+ - `GET /rate_limit` - rate limit status
378
+ - `GET /meta` - server metadata
379
+ - `GET /octocat` - ASCII art
380
+ - `GET /emojis` - emoji URLs
381
+ - `GET /zen` - random zen phrase
382
+ - `GET /versions` - API versions
383
+
384
+ ## Google API
385
+
386
+ OAuth 2.0 and OpenID Connect emulation.
387
+
388
+ - `GET /o/oauth2/v2/auth` - authorization endpoint
389
+ - `POST /oauth2/v4/token` - token exchange
390
+ - `GET /oauth2/v2/userinfo` - get user info
391
+ - `GET /.well-known/openid-configuration` - OIDC discovery document
392
+ - `GET /oauth2/v3/certs` - JSON Web Key Set (JWKS)
393
+
394
+ ## Architecture
395
+
396
+ ```
397
+ packages/
398
+ emulate/ # CLI entry point (commander)
399
+ @internal/
400
+ core/ # HTTP server, in-memory store, plugin interface, middleware
401
+ vercel/ # Vercel API service
402
+ github/ # GitHub API service
403
+ google/ # Google OAuth 2.0 / OIDC
404
+ apps/
405
+ web/ # Documentation site (Next.js)
406
+ ```
407
+
408
+ 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.
409
+
410
+ ## Auth
411
+
412
+ Tokens are configured in the seed config and map to users. Pass them as `Authorization: Bearer <token>` or `Authorization: token <token>`.
413
+
414
+ **Vercel**: All endpoints accept `teamId` or `slug` query params for team scoping. Pagination uses cursor-based `limit`/`since`/`until` with `pagination` response objects.
415
+
416
+ **GitHub**: Public repo endpoints work without auth. Private repos and write operations require a valid token. Pagination uses `page`/`per_page` with `Link` headers.
package/dist/api.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import * as _internal_core from '@internal/core';
2
+ import { VercelSeedConfig } from '@internal/vercel';
3
+ import { GitHubSeedConfig } from '@internal/github';
4
+ import { GoogleSeedConfig } from '@internal/google';
5
+
6
+ declare const SERVICE_PLUGINS: {
7
+ readonly vercel: _internal_core.ServicePlugin;
8
+ readonly github: _internal_core.ServicePlugin;
9
+ readonly google: _internal_core.ServicePlugin;
10
+ };
11
+ type ServiceName = keyof typeof SERVICE_PLUGINS;
12
+ interface SeedConfig {
13
+ tokens?: Record<string, {
14
+ login: string;
15
+ scopes?: string[];
16
+ }>;
17
+ vercel?: VercelSeedConfig;
18
+ github?: GitHubSeedConfig;
19
+ google?: GoogleSeedConfig;
20
+ }
21
+ interface EmulatorOptions {
22
+ service: ServiceName;
23
+ port?: number;
24
+ seed?: SeedConfig;
25
+ }
26
+ interface Emulator {
27
+ url: string;
28
+ reset(): void;
29
+ close(): Promise<void>;
30
+ }
31
+ declare function createEmulator(options: EmulatorOptions): Promise<Emulator>;
32
+
33
+ export { type Emulator, type EmulatorOptions, type SeedConfig, type ServiceName, createEmulator };