Loading...
Loading...
Local drop-in API emulation for Vercel, GitHub, and Google services in CI and no-network sandboxes
npx skill4agent add aradotso/trending-skills vercel-labs-emulateSkill by ara.so — Daily 2026 Skills collection.
emulate# CLI (no install needed)
npx emulate
# Or install as a dev dependency
npm install --save-dev emulate# 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 listhttp://localhost:4000http://localhost:4001http://localhost:4002EMULATE_PORTPORTimport { 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()| Option | Default | Description |
|---|---|---|
| (required) | |
| | Port for the HTTP server |
| none | Inline seed data object (same shape as YAML config) |
| Method | Description |
|---|---|
| Base URL of the running server |
| Wipe in-memory store and replay seed data |
| Shut down the server (returns Promise) |
// 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',
},
})emulate.config.yaml# 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/googleconst 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 }
],
},
})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/githubWithoutconfigured, the emulator accepts anyoauth_apps(backward-compatible). With apps configured, strict validation is enforced.client_id
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{ iss: "<app_id>" }vercel:
integrations:
- client_id: $VERCEL_CLIENT_ID
client_secret: $VERCEL_CLIENT_SECRET
name: My Vercel App
redirect_uris:
- http://localhost:3000/api/auth/callback/vercelimport { createEmulator } from 'emulate'
import { Octokit } from '@octokit/rest'
describe('GitHub integration', () => {
let emulator: Awaited<ReturnType<typeof createEmulator>>
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')
})
})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')
})
})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()]))# .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: testFROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Tests start emulators programmatically — no outbound network needed
RUN npm testGET /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 issuesGET /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# Use a different base port
npx emulate --port 5000
# Or set via env
EMULATE_PORT=5000 npx emulate// Always call reset() in afterEach, not afterAll
afterEach(() => emulator.reset())oauth_appsintegrationsclient_idoauth_apps// 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{ iss: "<app_id>" }app_id