vercel-labs/emulate Skill by ara.so — Daily 2026 Skills collection. emulate provides fully stateful, production-fidelity local HTTP servers that replace Vercel, GitHub, and Google APIs. Designed for CI pipelines and no-network sandboxes — not mocks, real in-memory state with proper pagination, OAuth, webhooks, and cascading deletes. Installation
CLI (no install needed)
npx emulate
Or install as a dev dependency
npm install --save-dev emulate CLI Usage
Start all services with defaults
npx emulate
Start specific services
npx emulate --service vercel,github
Custom base port (auto-increments per service)
npx emulate --port 3000
Start with seed data
npx emulate --seed emulate.config.yaml
Generate a starter config
npx emulate init
Generate config for a specific service
npx emulate init --service github
List available services
npx emulate list Default ports: Vercel → http://localhost:4000 GitHub → http://localhost:4001 Google → http://localhost:4002 Port can also be set via EMULATE_PORT or PORT environment variables. Programmatic API import { createEmulator , type Emulator } from 'emulate' // Start a single service const github = await createEmulator ( { service : 'github' , port : 4001 } ) const vercel = await createEmulator ( { service : 'vercel' , port : 4002 } ) console . log ( github . url ) // 'http://localhost:4001' console . log ( vercel . url ) // 'http://localhost:4002' // Reset state (replays seed data) github . reset ( ) // Shutdown await github . close ( ) await vercel . close ( ) Options Option Default Description service (required) 'github' , 'vercel' , or 'google' port 4000 Port for the HTTP server seed none Inline seed data object (same shape as YAML config) Instance Methods Method Description url Base URL of the running server reset() Wipe in-memory store and replay seed data close() Shut down the server (returns Promise) 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 ( ) ] ) ) // vitest.config.ts import { defineConfig } from 'vitest/config' export default defineConfig ( { test : { setupFiles : [ './vitest.setup.ts' ] , environment : 'node' , } , } ) Seed Configuration Create emulate.config.yaml in your project root (auto-detected):
Auth tokens
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
github
:
users
:
-
login
:
octocat
name
:
The Octocat
email
:
octocat@github.com
orgs
:
-
login
:
my
-
org
name
:
My Organization
repos
:
-
owner
:
octocat
name
:
hello
-
world
language
:
JavaScript
auto_init
:
true
google
:
users
:
-
email
:
testuser@example.com
name
:
Test User
oauth_clients
:
-
client_id
:
my
-
client
-
id.apps.googleusercontent.com
client_secret
:
$GOOGLE_CLIENT_SECRET
redirect_uris
:
-
http
:
//localhost
:
3000/api/auth/callback/google
Inline Seed (Programmatic)
const
github
=
await
createEmulator
(
{
service
:
'github'
,
port
:
4001
,
seed
:
{
users
:
[
{
login
:
'testuser'
,
name
:
'Test User'
,
email
:
'test@example.com'
}
]
,
repos
:
[
{
owner
:
'testuser'
,
name
:
'my-repo'
,
language
:
'TypeScript'
,
auto_init
:
true
}
]
,
}
,
}
)
OAuth Configuration
GitHub OAuth Apps
github
:
oauth_apps
:
-
client_id
:
$GITHUB_CLIENT_ID
client_secret
:
$GITHUB_CLIENT_SECRET
name
:
My Web App
redirect_uris
:
-
http
:
//localhost
:
3000/api/auth/callback/github
Without
oauth_apps
configured, the emulator accepts any
client_id
(backward-compatible). With apps configured, strict validation is enforced.
GitHub Apps (JWT Auth)
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
]
installations
:
-
installation_id
:
100
account
:
my
-
org
repository_selection
:
all
Sign JWTs with
{ iss: "
let octokit : Octokit beforeAll ( async ( ) => { emulator = await createEmulator ( { service : 'github' , port : 4001 , seed : { users : [ { login : 'testuser' , name : 'Test User' } ] , repos : [ { owner : 'testuser' , name : 'my-repo' , auto_init : true } ] , } , } ) octokit = new Octokit ( { baseUrl : emulator . url , auth : 'any-token' , } ) } ) afterEach ( ( ) => emulator . reset ( ) ) afterAll ( ( ) => emulator . close ( ) ) it ( 'creates and fetches an issue' , async ( ) => { const { data : issue } = await octokit . issues . create ( { owner : 'testuser' , repo : 'my-repo' , title : 'Test issue' , body : 'This is a test' , } ) expect ( issue . number ) . toBe ( 1 ) expect ( issue . state ) . toBe ( 'open' ) const { data : fetched } = await octokit . issues . get ( { owner : 'testuser' , repo : 'my-repo' , issue_number : issue . number , } ) expect ( fetched . title ) . toBe ( 'Test issue' ) } ) } ) Testing a Vercel Deployment Workflow import { createEmulator } from 'emulate' describe ( 'Vercel deployment' , ( ) => { let emulator : Awaited < ReturnType < typeof createEmulator
beforeAll ( async ( ) => { emulator = await createEmulator ( { service : 'vercel' , port : 4002 , seed : { users : [ { username : 'dev' , email : 'dev@example.com' } ] , projects : [ { name : 'my-app' , framework : 'nextjs' } ] , } , } ) process . env . VERCEL_API_URL = emulator . url } ) afterEach ( ( ) => emulator . reset ( ) ) afterAll ( ( ) => emulator . close ( ) ) it ( 'creates a deployment and transitions to READY' , async ( ) => { const res = await fetch (
${ emulator . url } /v13/deployments, { method : 'POST' , headers : { Authorization : 'Bearer any-token' , 'Content-Type' : 'application/json' , } , body : JSON . stringify ( { name : 'my-app' , target : 'production' } ) , } ) const deployment = await res . json ( ) expect ( deployment . readyState ) . toBe ( 'READY' ) } ) } ) Testing Multiple Services Together import { createEmulator , type Emulator } from 'emulate' let github : Emulator let vercel : Emulator let google : Emulator beforeAll ( async ( ) => { ; [ github , vercel , google ] = await Promise . all ( [ createEmulator ( { service : 'github' , port : 4001 } ) , createEmulator ( { service : 'vercel' , port : 4002 } ) , createEmulator ( { service : 'google' , port : 4003 } ) , ] ) // Point your app's env vars at local emulators process . env . GITHUB_API_URL = github . url process . env . VERCEL_API_URL = vercel . url process . env . GOOGLE_API_URL = google . url } ) afterEach ( ( ) => { github . reset ( ) vercel . reset ( ) google . reset ( ) } ) afterAll ( ( ) => Promise . all ( [ github . close ( ) , vercel . close ( ) , google . close ( ) ] ) ) CI Configuration GitHub Actions
.github/workflows/test.yml
jobs : test : runs-on : ubuntu - latest steps : - uses : actions/checkout@v4 - uses : actions/setup - node@v4 with : node-version : 20 - run : npm ci - name : Run tests with emulated APIs run : npm test env :
Emulators start in vitest.setup.ts — no extra service needed
NODE_ENV : test Docker / No-Network Sandbox FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci COPY . .
Tests start emulators programmatically — no outbound network needed
RUN npm test Key API Endpoints Reference GitHub Emulator GET /user # authenticated user GET /repos/:owner/:repo # get repo POST /user/repos # create repo POST /repos/:owner/:repo/issues # create issue PATCH /repos/:owner/:repo/issues/:number # update issue POST /repos/:owner/:repo/pulls # create PR PUT /repos/:owner/:repo/pulls/:number/merge # merge PR GET /search/repositories # search repos GET /search/issues # search issues Vercel Emulator GET /v2/user # authenticated user GET /v2/teams # list teams POST /v11/projects # create project GET /v10/projects # list projects POST /v13/deployments # create deployment (auto → READY) GET /v13/deployments/:idOrUrl # get deployment POST /v10/projects/:id/env # create env vars GET /v10/projects/:id/env # list env vars Troubleshooting Port already in use
Use a different base port
npx emulate --port 5000
Or set via env
EMULATE_PORT
5000
npx emulate
Tests interfering with each other
// Always call reset() in afterEach, not afterAll
afterEach
(
(
)
=>
emulator
.
reset
(
)
)
OAuth strict validation rejecting requests
If you configure
oauth_apps
or
integrations
, only matching
client_id
values are accepted
Remove the
oauth_apps
block to fall back to accept-any mode
Emulator not receiving requests from app code
// Make sure your app reads the URL from env at request time, not module load time
// ✅ Good
async
function
fetchUser
(
)
{
return
fetch
(
${
process
.
env
.
GITHUB_API_URL
}
/user
)
}
// ❌ Bad — captured before emulator starts
const
API_URL
=
process
.
env
.
GITHUB_API_URL
GitHub App JWT auth failing
JWT must have
{ iss: "