emulate
v0.3.0
Published
Local drop-in replacement services for CI and no-network sandboxes
Maintainers
Readme
emulate
Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation. Not mocks.
Quick Start
npx emulateAll services start with sensible defaults. No config file needed:
- Vercel on
http://localhost:4000 - GitHub on
http://localhost:4001 - Google on
http://localhost:4002
CLI
# Start all services (zero-config)
emulate
# Start specific services
emulate --service vercel,github
# Custom port
emulate --port 3000
# Use a seed config file
emulate --seed config.yaml
# Generate a starter config
emulate init
# Generate config for a specific service
emulate init --service vercel
# List available services
emulate listOptions
| Flag | Default | Description |
|------|---------|-------------|
| -p, --port | 4000 | Base port (auto-increments per service) |
| -s, --service | all | Comma-separated services to enable |
| --seed | auto-detect | Path to seed config (YAML or JSON) |
The port can also be set via EMULATE_PORT or PORT environment variables.
Programmatic API
npm install emulateEach call to createEmulator starts a single service:
import { createEmulator } from 'emulate'
const github = await createEmulator({ service: 'github', port: 4001 })
const vercel = await createEmulator({ service: 'vercel', port: 4002 })
github.url // 'http://localhost:4001'
vercel.url // 'http://localhost:4002'
await github.close()
await vercel.close()Vitest / Jest setup
// vitest.setup.ts
import { createEmulator, type Emulator } from 'emulate'
let github: Emulator
let vercel: Emulator
beforeAll(async () => {
;[github, vercel] = await Promise.all([
createEmulator({ service: 'github', port: 4001 }),
createEmulator({ service: 'vercel', port: 4002 }),
])
process.env.GITHUB_URL = github.url
process.env.VERCEL_URL = vercel.url
})
afterEach(() => { github.reset(); vercel.reset() })
afterAll(() => Promise.all([github.close(), vercel.close()]))Options
| Option | Default | Description |
|--------|---------|-------------|
| service | (required) | Service to emulate: 'github', 'vercel', or 'google' |
| port | 4000 | Port for the HTTP server |
| seed | none | Inline seed data (same shape as YAML config) |
Instance methods
| Method | Description |
|--------|-------------|
| url | Base URL of the running server |
| reset() | Wipe the store and replay seed data |
| close() | Shut down the HTTP server, returns a Promise |
Configuration
Configuration is optional. To customize seed data, create emulate.config.yaml in your project root (or pass --seed):
tokens:
my_token:
login: admin
scopes: [repo, user]
vercel:
users:
- username: developer
name: Developer
email: [email protected]
teams:
- slug: my-team
name: My Team
projects:
- name: my-app
team: my-team
framework: nextjs
github:
users:
- login: octocat
name: The Octocat
email: [email protected]
orgs:
- login: my-org
name: My Organization
repos:
- owner: octocat
name: hello-world
language: JavaScript
auto_init: true
google:
users:
- email: [email protected]
name: Test User
oauth_clients:
- client_id: my-client-id.apps.googleusercontent.com
client_secret: GOCSPX-secret
redirect_uris:
- http://localhost:3000/api/auth/callback/google
labels:
- id: Label_ops
user_email: [email protected]
name: Ops/Review
color_background: "#DDEEFF"
color_text: "#111111"
messages:
- id: msg_welcome
user_email: [email protected]
from: [email protected]
to: [email protected]
subject: Welcome to the Gmail emulator
body_text: You can now test Gmail, Calendar, and Drive flows locally.
label_ids: [INBOX, UNREAD, CATEGORY_UPDATES]
calendars:
- id: primary
user_email: [email protected]
summary: [email protected]
primary: true
selected: true
time_zone: UTC
calendar_events:
- id: evt_kickoff
user_email: [email protected]
calendar_id: primary
summary: Project Kickoff
start_date_time: 2025-01-10T09:00:00.000Z
end_date_time: 2025-01-10T09:30:00.000Z
drive_items:
- id: drv_docs
user_email: [email protected]
name: Docs
mime_type: application/vnd.google-apps.folder
parent_ids: [root]OAuth & Integrations
The emulator supports configurable OAuth apps and integrations with strict client validation.
Vercel Integrations
vercel:
integrations:
- client_id: "oac_abc123"
client_secret: "secret_abc123"
name: "My Vercel App"
redirect_uris:
- "http://localhost:3000/api/auth/callback/vercel"GitHub OAuth Apps
github:
oauth_apps:
- client_id: "Iv1.abc123"
client_secret: "secret_abc123"
name: "My Web App"
redirect_uris:
- "http://localhost:3000/api/auth/callback/github"If no oauth_apps are configured, the emulator accepts any client_id (backward-compatible). With apps configured, strict validation is enforced.
GitHub Apps
Full GitHub App support with JWT authentication and installation access tokens:
github:
apps:
- app_id: 12345
slug: "my-github-app"
name: "My GitHub App"
private_key: |
-----BEGIN RSA PRIVATE KEY-----
...your PEM key...
-----END RSA PRIVATE KEY-----
permissions:
contents: read
issues: write
events: [push, pull_request]
webhook_url: "http://localhost:3000/webhooks/github"
webhook_secret: "my-secret"
installations:
- installation_id: 100
account: my-org
repository_selection: allJWT authentication: sign a JWT with { iss: "<app_id>" } using the app's private key (RS256). The emulator verifies the signature and resolves the app.
App webhook delivery: When events occur on repos where a GitHub App is installed, the emulator mirrors real GitHub behavior:
- All webhook payloads (including repo and org hooks) include an
installationfield with{ id, node_id }. - If the app has a
webhook_url, the emulator delivers the event there with theinstallationfield and (if configured) anX-Hub-Signature-256header signed withwebhook_secret.
Vercel API
Every endpoint below is fully stateful with Vercel-style JSON responses and cursor-based pagination.
User & Teams
GET /v2/user- authenticated userPATCH /v2/user- update userGET /v2/teams- list teams (cursor paginated)GET /v2/teams/:teamId- get team (by ID or slug)POST /v2/teams- create teamPATCH /v2/teams/:teamId- update teamGET /v2/teams/:teamId/members- list membersPOST /v2/teams/:teamId/members- add member
Projects
POST /v11/projects- create project (with optional env vars and git integration)GET /v10/projects- list projects (search, cursor pagination)GET /v9/projects/:idOrName- get project (includes env vars)PATCH /v9/projects/:idOrName- update projectDELETE /v9/projects/:idOrName- delete project (cascades)GET /v1/projects/:projectId/promote/aliases- promote aliases statusPATCH /v1/projects/:idOrName/protection-bypass- manage bypass secrets
Deployments
POST /v13/deployments- create deployment (auto-transitions to READY)GET /v13/deployments/:idOrUrl- get deployment (by ID or URL)GET /v6/deployments- list deployments (filter by project, target, state)DELETE /v13/deployments/:id- delete deployment (cascades)PATCH /v12/deployments/:id/cancel- cancel building deploymentGET /v2/deployments/:id/aliases- list deployment aliasesGET /v3/deployments/:idOrUrl/events- get build events/logsGET /v6/deployments/:id/files- list deployment filesPOST /v2/files- upload file (by SHA digest)
Domains
POST /v10/projects/:idOrName/domains- add domain (with verification challenge)GET /v9/projects/:idOrName/domains- list domainsGET /v9/projects/:idOrName/domains/:domain- get domainPATCH /v9/projects/:idOrName/domains/:domain- update domainDELETE /v9/projects/:idOrName/domains/:domain- remove domainPOST /v9/projects/:idOrName/domains/:domain/verify- verify domain
Environment Variables
GET /v10/projects/:idOrName/env- list env vars (with decrypt option)POST /v10/projects/:idOrName/env- create env vars (single, batch, upsert)GET /v10/projects/:idOrName/env/:id- get env varPATCH /v9/projects/:idOrName/env/:id- update env varDELETE /v9/projects/:idOrName/env/:id- delete env var
GitHub API
Every endpoint below is fully stateful. Creates, updates, and deletes persist in memory and affect related entities.
Users
GET /user- authenticated userPATCH /user- update profileGET /users/:username- get userGET /users- list usersGET /users/:username/repos- list user reposGET /users/:username/orgs- list user orgsGET /users/:username/followers- list followersGET /users/:username/following- list following
Repositories
GET /repos/:owner/:repo- get repoPOST /user/repos- create user repoPOST /orgs/:org/repos- create org repoPATCH /repos/:owner/:repo- update repoDELETE /repos/:owner/:repo- delete repo (cascades)GET/PUT /repos/:owner/:repo/topics- get/replace topicsGET /repos/:owner/:repo/languages- languagesGET /repos/:owner/:repo/contributors- contributorsGET /repos/:owner/:repo/forks- list forksPOST /repos/:owner/:repo/forks- create forkGET/PUT/DELETE /repos/:owner/:repo/collaborators/:username- collaboratorsGET /repos/:owner/:repo/collaborators/:username/permissionPOST /repos/:owner/:repo/transfer- transfer repoGET /repos/:owner/:repo/tags- list tags
Issues
GET /repos/:owner/:repo/issues- list (filter by state, labels, assignee, milestone, creator, since)POST /repos/:owner/:repo/issues- createGET /repos/:owner/:repo/issues/:number- getPATCH /repos/:owner/:repo/issues/:number- update (state transitions, events)PUT/DELETE /repos/:owner/:repo/issues/:number/lock- lock/unlockGET /repos/:owner/:repo/issues/:number/timeline- timeline eventsGET /repos/:owner/:repo/issues/:number/events- eventsPOST/DELETE /repos/:owner/:repo/issues/:number/assignees- manage assignees
Pull Requests
GET /repos/:owner/:repo/pulls- list (filter by state, head, base)POST /repos/:owner/:repo/pulls- createGET /repos/:owner/:repo/pulls/:number- getPATCH /repos/:owner/:repo/pulls/:number- updatePUT /repos/:owner/:repo/pulls/:number/merge- merge (with branch protection enforcement)GET /repos/:owner/:repo/pulls/:number/commits- list commitsGET /repos/:owner/:repo/pulls/:number/files- list filesPOST/DELETE /repos/:owner/:repo/pulls/:number/requested_reviewers- manage reviewersPUT /repos/:owner/:repo/pulls/:number/update-branch- update branch
Comments
- Issue comments: full CRUD on
/repos/:owner/:repo/issues/:number/comments - Review comments: full CRUD on
/repos/:owner/:repo/pulls/:number/comments - Commit comments: full CRUD on
/repos/:owner/:repo/commits/:sha/comments - Repo-wide listings for each type
Reviews
GET /repos/:owner/:repo/pulls/:number/reviews- listPOST /repos/:owner/:repo/pulls/:number/reviews- create (with inline comments)GET/PUT /repos/:owner/:repo/pulls/:number/reviews/:id- get/updatePOST /repos/:owner/:repo/pulls/:number/reviews/:id/events- submitPUT /repos/:owner/:repo/pulls/:number/reviews/:id/dismissals- dismiss
Labels & Milestones
- Labels: full CRUD, add/remove from issues, replace all
- Milestones: full CRUD, state transitions, issue counts
Branches & Git Data
- Branches: list, get, protection CRUD (status checks, PR reviews, enforce admins)
- Refs: get, match, create, update, delete
- Commits: get, create
- Trees: get (with recursive), create (with inline content)
- Blobs: get, create
- Tags: get, create
Organizations & Teams
- Orgs: get, update, list
- Org members: list, check, remove, get/set membership
- Teams: full CRUD, members, repos
Releases
- Releases: full CRUD, latest, by tag
- Release assets: full CRUD, upload
- Generate release notes
Webhooks
- Repo webhooks: full CRUD, ping, test, deliveries
- Org webhooks: full CRUD, ping
- Real HTTP delivery to registered URLs on all state changes
Search
GET /search/repositories- full query syntax (user, org, language, topic, stars, forks, etc.)GET /search/issues- issues + PRs (repo, is, author, label, milestone, state, etc.)GET /search/users- users + orgsGET /search/code- blob content searchGET /search/commits- commit message searchGET /search/topics- topic searchGET /search/labels- label search
Actions
- Workflows: list, get, enable/disable, dispatch
- Workflow runs: list, get, cancel, rerun, delete, logs
- Jobs: list, get, logs
- Artifacts: list, get, delete
- Secrets: repo + org CRUD
Checks
- Check runs: create, update, get, annotations, rerequest, list by ref/suite
- Check suites: create, get, preferences, rerequest, list by ref
- Automatic suite status rollup from check run results
Misc
GET /rate_limit- rate limit statusGET /meta- server metadataGET /octocat- ASCII artGET /emojis- emoji URLsGET /zen- random zen phraseGET /versions- API versions
Google OAuth + Gmail, Calendar, and Drive APIs
OAuth 2.0, OpenID Connect, and mutable Google Workspace-style surfaces for local inbox, calendar, and drive flows.
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.
GET /o/oauth2/v2/auth- authorization endpointPOST /oauth2/token- token exchangeGET /oauth2/v2/userinfo- get user infoGET /.well-known/openid-configuration- OIDC discovery documentGET /oauth2/v3/certs- JSON Web Key Set (JWKS)GET /gmail/v1/users/:userId/messages- list messages withq,labelIds,maxResults, andpageTokenGET /gmail/v1/users/:userId/messages/:id- fetch a Gmail-style message payload infull,metadata,minimal, orrawformatsGET /gmail/v1/users/:userId/messages/:messageId/attachments/:id- fetch attachment bodiesPOST /gmail/v1/users/:userId/messages/send- create sent mail fromrawMIME or structured fieldsPOST /gmail/v1/users/:userId/messages/import- import inbox mailPOST /gmail/v1/users/:userId/messages- insert a message directlyPOST /gmail/v1/users/:userId/messages/:id/modify- add/remove labels on one messagePOST /gmail/v1/users/:userId/messages/batchModify- add/remove labels across many messagesPOST /gmail/v1/users/:userId/messages/:id/trashandPOST /gmail/v1/users/:userId/messages/:id/untrashGET /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/:idPOST /gmail/v1/users/:userId/threads/:id/modify- add/remove labels across a threadGET /gmail/v1/users/:userId/threadsandGET /gmail/v1/users/:userId/threads/:idGET /gmail/v1/users/:userId/labels,POST /gmail/v1/users/:userId/labels,PATCH /gmail/v1/users/:userId/labels/:id,DELETE /gmail/v1/users/:userId/labels/:idGET /gmail/v1/users/:userId/history,POST /gmail/v1/users/:userId/watch,POST /gmail/v1/users/:userId/stopGET /gmail/v1/users/:userId/settings/filters,POST /gmail/v1/users/:userId/settings/filters,DELETE /gmail/v1/users/:userId/settings/filters/:idGET /gmail/v1/users/:userId/settings/forwardingAddresses,GET /gmail/v1/users/:userId/settings/sendAsGET /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/freeBusyGET /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
The Google plugin still does not cover every Google API edge case, but Gmail, Calendar, and Drive now have enough mutable surface to support realistic local automation flows without stuffing everything into static seed config.
Architecture
packages/
emulate/ # CLI entry point (commander)
@emulators/
core/ # HTTP server, in-memory store, plugin interface, middleware
vercel/ # Vercel API service
github/ # GitHub API service
google/ # Google OAuth 2.0 / OIDC + Gmail, Calendar, and Drive APIs
apps/
web/ # Documentation site (Next.js)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.
Auth
Tokens are configured in the seed config and map to users. Pass them as Authorization: Bearer <token> or Authorization: token <token>.
Vercel: All endpoints accept teamId or slug query params for team scoping. Pagination uses cursor-based limit/since/until with pagination response objects.
GitHub: Public repo endpoints work without auth. Private repos and write operations require a valid token. Pagination uses page/per_page with Link headers.
