- name
- emulate
- description
- Local drop-in API emulator for Vercel, GitHub, Google, Slack, Apple, Microsoft, AWS, Clerk, Linear, Twilio, and other developer APIs. Use when the user needs to start emulated services, configure seed data, write tests against local APIs, set up CI without network access, or work with the emulate CLI or programmatic API. Triggers include "start the emulator", "emulate services", "mock API locally", "create emulator config", "test against local API", "npx emulate", or any task requiring local service emulation.
- allowed-tools
- Bash(npx emulate:*)
# Service Emulation with emulate
Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation, not mocks.
## Quick Start
```bash
npx emulate
```
All services listen on IPv4 loopback (`127.0.0.1`) by default:
| Service | Default Port |
|-----------|-------------|
| Vercel | 4000 |
| GitHub | 4001 |
| Google | 4002 |
| Slack | 4003 |
| Apple | 4004 |
| Microsoft | 4005 |
| Okta | 4006 |
| AWS | 4007 |
| Resend | 4008 |
| Stripe | 4009 |
| MongoDB Atlas | 4010 |
| Clerk | 4011 |
| Linear | 4012 |
| Twilio | 4013 |
## CLI
```bash
# Start all services (zero-config)
npx emulate
# Start specific services
npx emulate --service vercel,github
# Custom base port (auto-increments per service)
npx emulate --port 3000
# Use a seed config file
npx emulate --seed config.yaml
# Generate omitted service secrets into a private file
npx emulate start --seed config.yaml --generated-secrets-file .emulate-secrets.json
# Generate a starter config
npx emulate init
# Generate config for a specific service
npx emulate init --service vercel
# List available services
npx emulate list
```
### Options
| Flag | Default | Description |
|------|---------|-------------|
| `-p, --port` | `4000` | Base port (auto-increments per service) |
| `--host` | `127.0.0.1` | Listening address; use `0.0.0.0` to allow network access |
| `-s, --service` | all | Comma-separated services to enable |
| `--seed` | auto-detect | Path to seed config (YAML or JSON) |
| `--base-url` | none | Override advertised base URL (supports `{service}` template) |
| `--portless` | off | Serve over HTTPS via portless (auto-registers aliases) |
| `--generated-secrets-file` | none | Generate omitted service secrets and write them to a new owner-only JSON file |
The port can also be set via `EMULATE_PORT` or `PORT` environment variables.
For access from a container or another machine, use `npx emulate start --host 0.0.0.0`. Set `--base-url` to a URL reachable by those clients when using OAuth redirects or other advertised URLs.
The generated-secrets destination must not exist. emulate removes inherited ACLs, verifies effective owner-only access, and publishes complete JSON before opening listeners or configuring portless. Handled startup failures remove the invocation-owned artifact. A hard termination can leave a complete artifact that must be removed manually after confirming no invocation is using it. Only service-generated values appear in the artifact. Linux requires `setfacl` and `getfacl` from the `acl` package. The flag fails closed when access controls cannot be verified and is not supported on Windows.
The advertised base URL (used in OAuth redirects, webhook URLs, etc.) can be overridden via `--base-url`, the `EMULATE_BASE_URL` env var (supports `{service}` template), or per-service `baseUrl` in the seed config. When running under portless, the `PORTLESS_URL` env var is also detected automatically.
## Programmatic API
```bash
npm install emulate
```
Each call to `createEmulator` starts a single service:
```typescript
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()
```
For GitHub App tests, inspect secret-free minted installation-token metadata at `GET /_emulate/installation-tokens`.
### Options
| Option | Default | Description |
|--------|---------|-------------|
| `service` | *(required)* | `'vercel'`, `'github'`, `'google'`, `'slack'`, `'apple'`, `'microsoft'`, `'okta'`, `'aws'`, `'resend'`, `'stripe'`, `'mongoatlas'`, `'clerk'`, `'linear'`, or `'twilio'` |
| `port` | `4000` | Port for the HTTP server |
| `hostname` | `127.0.0.1` | Listening address for built-in and custom emulators |
| `seed` | none | Inline seed data (same shape as YAML config) |
| `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>`. |
Use `hostname: '0.0.0.0'` to allow connections from containers or other machines, and `baseUrl` to advertise a URL reachable by those clients.
### 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 |
## Vitest / Jest Setup
```typescript
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_EMULATOR_URL = github.url
process.env.VERCEL_EMULATOR_URL = vercel.url
})
afterEach(() => { github.reset(); vercel.reset() })
afterAll(() => Promise.all([github.close(), vercel.close()]))
```
## Configuration
Configuration is optional. The CLI auto-detects config files in this order:
1. `emulate.config.yaml` / `.yml`
2. `emulate.config.json`
3. `service-emulator.config.yaml` / `.yml`
4. `service-emulator.config.json`
Or pass `--seed <file>` explicitly. Run `npx emulate init` to generate a starter file.
### Config Structure
```yaml
tokens:
my_token:
login: admin
scopes: [repo, user]
vercel:
users:
- username: developer
name: Developer
email: dev@example.com
teams:
- slug: my-team
name: My Team
projects:
- name: my-app
team: my-team
framework: nextjs
integrations:
- client_id: oac_abc123
client_secret: secret_abc123
name: My Vercel App
redirect_uris:
- http://localhost:3000/api/auth/callback/vercel
github:
users:
- login: octocat
name: The Octocat
email: octocat@github.com
orgs:
- login: my-org
name: My Organization
members:
- login: octocat
role: admin
repos:
- owner: octocat
name: hello-world
language: JavaScript
auto_init: true
oauth_apps:
- client_id: Iv1.abc123
client_secret: secret_abc123
name: My Web App
redirect_uris:
- http://localhost:3000/api/auth/callback/github
google:
users:
- email: testuser@example.com
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
slack:
team:
name: My Workspace
domain: my-workspace
users:
- name: developer
real_name: Developer
email: dev@example.com
channels:
- name: general
topic: General discussion
bots:
- name: my-bot
oauth_apps:
- client_id: "12345.67890"
client_secret: example_client_secret
name: My Slack App
redirect_uris:
- http://localhost:3000/api/auth/callback/slack
signing_secret: my_signing_secret
linear:
organization:
name: Acme
url_key: acme
users:
- email: admin@example.com
name: Admin User
admin: true
- email: dev@example.com
name: Developer
teams:
- key: ENG
name: Engineering
states:
- name: Backlog
type: backlog
- name: Todo
type: unstarted
- name: In Progress
type: started
- name: Done
type: completed
labels:
- name: Bug
color: "#d92d20"
team: ENG
- name: Feature
color: "#2563eb"
team: ENG
issues:
- team: ENG
title: Fix local checkout test
description: Reproduce and fix the checkout failure.
state: Todo
assignee: dev@example.com
labels: [Bug]
oauth_apps:
- client_id: lin_example_client_id
client_secret: example_client_secret
name: My Linear App
redirect_uris:
- http://localhost:3000/api/auth/callback/linear
scopes: [read, write, issues:create, comments:create]
actor: user
tokens:
- token: lin_test_admin
user: admin@example.com
scopes: [read, write, issues:create, comments:create, admin]
strict_scopes: false
apple:
users:
- email: testuser@icloud.com
name: Test User
oauth_clients:
- client_id: com.example.app
team_id: TEAM001
name: My Apple App
redirect_uris:
- http://localhost:3000/api/auth/callback/apple
microsoft:
users:
- email: testuser@outlook.com
name: Test User
oauth_clients:
- client_id: example-client-id
client_secret: example-client-secret
name: My Microsoft App
redirect_uris:
- http://localhost:3000/api/auth/callback/microsoft-entra-id
aws:
region: us-east-1
s3:
buckets:
- name: my-app-bucket
sqs:
queues:
- name: my-app-events
iam:
users:
- user_name: developer
create_access_key: true
roles:
- role_name: lambda-execution-role
description: Role for Lambda function execution
okta:
users:
- login: testuser@okta.local
email: testuser@okta.local
first_name: Test
last_name: User
groups:
- name: Everyone
description: All users
type: BUILT_IN
okta_id: 00g_everyone
authorization_servers:
- id: default
name: default
audiences: [api://default]
oauth_clients:
- client_id: okta-test-client
client_secret: okta-test-secret
name: Sample OIDC Client
redirect_uris:
- http://localhost:3000/callback
auth_server_id: default
resend:
domains:
- name: example.com
region: us-east-1
contacts:
- email: test@example.com
first_name: Test
last_name: User
stripe:
customers:
- email: test@example.com
name: Test Customer
products:
- name: Pro Plan
description: Monthly pro subscription
prices:
- product_name: Pro Plan
currency: usd
unit_amount: 2000
mongoatlas:
projects:
- name: Project0
clusters:
- name: Cluster0
project: Project0
database_users:
- username: admin
project: Project0
databases:
- cluster: Cluster0
name: test
collections: [items]
clerk:
users:
- first_name: Test
last_name: User
email_addresses: [test@example.com]
password: clerk_test_password
organizations:
- name: My Company
slug: my-company
members:
- email: test@example.com
role: admin
oauth_applications:
- client_id: clerk_emulate_client
client_secret: clerk_emulate_secret
name: Emulate App
redirect_uris:
- http://localhost:3000/api/auth/callback/clerk
twilio:
account:
sid: AC00000000000000000000000000000000
auth_token: twilio_test_auth_token
friendly_name: Local Twilio Account
api_keys:
- sid: SK00000000000000000000000000000000
secret: twilio_test_api_secret
friendly_name: Local API Key
phone_numbers:
- phone_number: "+15551234567"
friendly_name: Local SMS and Voice Number
sms_url: http://localhost:3000/api/twilio/sms
voice_url: http://localhost:3000/api/twilio/voice
messaging_services:
- friendly_name: Local Messaging Service
phone_numbers: ["+15551234567"]
verify_services:
- friendly_name: Local Verify Service
code: "123456"
default_channel: sms
conversations:
services:
- friendly_name: Local Conversations
```
`slack.signing_secret` signs every outbound event subscription callback. Signed callbacks include `X-Slack-Request-Timestamp` and `X-Slack-Signature`, calculated as `v0=<HMAC-SHA256(secret, "v0:<timestamp>:<raw-body>")>` over the exact serialized callback body. Configure the receiver with the same secret and verify the unparsed request body. Callbacks are unsigned when the secret is absent or empty.
GitHub App `private_key` values are intentionally omitted from starter configuration. Programmatic `createEmulator` calls generate an RSA key and expose it through `generatedSecrets`. CLI startup generates omitted keys only when `--generated-secrets-file <path>` is provided; otherwise the seed must contain an explicit, valid private key. Never use a placeholder PEM value.
GitHub organization `members` are optional. Entries reference seeded users by `login`; `role` defaults to `member`, while `admin` creates an organization administrator. Unknown users are ignored.
### Auth
Tokens map to users. Pass them as `Authorization: Bearer <token>` or `Authorization: token <token>`. When no tokens are configured, a default `test_token_admin` is created for the `admin` user.
Each service also has a fallback user. If no token is provided, requests authenticate as the first seeded user.
## HTTPS with portless
[portless](https://github.com/vercel-labs/portless) gives emulators trusted HTTPS URLs with auto-generated certs. Use the `--portless` flag to auto-register each service as a portless alias:
Voir sur GitHub