vercel-labs-emulate
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesevercel-labs/emulate
vercel-labs/emulate
Skill by ara.so — Daily 2026 Skills collection.
emulate由ara.so开发的Skill — 属于Daily 2026 Skills系列。
emulateInstallation
安装
bash
undefinedbash
undefinedCLI (no install needed)
CLI(无需安装)
npx emulate
npx emulate
Or install as a dev dependency
或作为开发依赖安装
npm install --save-dev emulate
undefinednpm install --save-dev emulate
undefinedCLI Usage
CLI 使用方式
bash
undefinedbash
undefinedStart all services with defaults
使用默认配置启动所有服务
npx emulate
npx emulate
Start specific services
启动指定服务
npx emulate --service vercel,github
npx emulate --service vercel,github
Custom base port (auto-increments per service)
自定义基础端口(每个服务自动递增端口号)
npx emulate --port 3000
npx emulate --port 3000
Start with seed data
携带初始数据启动
npx emulate --seed emulate.config.yaml
npx emulate --seed emulate.config.yaml
Generate a starter config
生成初始配置文件
npx emulate init
npx emulate init
Generate config for a specific service
生成指定服务的配置文件
npx emulate init --service github
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.npx emulate list
默认端口:
- **Vercel** → `http://localhost:4000`
- **GitHub** → `http://localhost:4001`
- **Google** → `http://localhost:4002`
端口也可通过`EMULATE_PORT`或`PORT`环境变量设置。Programmatic API
程序化API
typescript
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()typescript
import { createEmulator, type Emulator } from 'emulate'
// 启动单个服务
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'
// 重置状态(重新加载初始数据)
github.reset()
// 关闭服务
await github.close()
await vercel.close()Options
配置选项
| Option | Default | Description |
|---|---|---|
| (required) | |
| | Port for the HTTP server |
| none | Inline seed data object (same shape as YAML config) |
| 选项 | 默认值 | 描述 |
|---|---|---|
| (必填) | |
| | HTTP服务器端口 |
| 无 | 内联初始数据对象(与YAML配置格式一致) |
Instance Methods
实例方法
| Method | Description |
|---|---|
| Base URL of the running server |
| Wipe in-memory store and replay seed data |
| Shut down the server (returns Promise) |
| 方法 | 描述 |
|---|---|
| 运行中服务器的基础URL |
| 清空内存存储并重新加载初始数据 |
| 关闭服务器(返回Promise) |
Vitest / Jest Setup
Vitest / Jest 配置
typescript
// 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()]))typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
setupFiles: ['./vitest.setup.ts'],
environment: 'node',
},
})typescript
// 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()]))typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
setupFiles: ['./vitest.setup.ts'],
environment: 'node',
},
})Seed Configuration
初始数据配置
Create in your project root (auto-detected):
emulate.config.yamlyaml
undefined在项目根目录创建(会被自动识别):
emulate.config.yamlyaml
undefinedAuth 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
undefinedtokens:
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
undefinedInline Seed (Programmatic)
内联初始数据(程序化方式)
typescript
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 }
],
},
})typescript
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
OAuth 配置
GitHub OAuth Apps
GitHub OAuth 应用
yaml
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
yaml
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若未配置,模拟器会接受任意oauth_apps(向后兼容)。配置后则会启用严格校验。client_id
GitHub Apps (JWT Auth)
GitHub Apps(JWT 认证)
yaml
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: allSign JWTs with using RS256 — the emulator verifies the signature.
{ iss: "<app_id>" }yaml
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使用RS256算法签署包含的JWT——模拟器会验证签名。
{ iss: "<app_id>" }Vercel Integrations
Vercel 集成
yaml
vercel:
integrations:
- client_id: $VERCEL_CLIENT_ID
client_secret: $VERCEL_CLIENT_SECRET
name: My Vercel App
redirect_uris:
- http://localhost:3000/api/auth/callback/vercelyaml
vercel:
integrations:
- client_id: $VERCEL_CLIENT_ID
client_secret: $VERCEL_CLIENT_SECRET
name: My Vercel App
redirect_uris:
- http://localhost:3000/api/auth/callback/vercelReal-World Test Patterns
实际测试场景示例
Testing a GitHub API Client
测试GitHub API客户端
typescript
import { 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')
})
})typescript
import { 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')
})
})Testing a Vercel Deployment Workflow
测试Vercel部署流程
typescript
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')
})
})typescript
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
同时测试多个服务
typescript
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()]))typescript
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 }),
])
// 将应用的环境变量指向本地模拟器
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
CI 配置
GitHub Actions
GitHub Actions
yaml
undefinedyaml
undefined.github/workflows/test.yml
.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
undefinedjobs:
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:
# 模拟器在vitest.setup.ts中启动——无需额外服务
NODE_ENV: test
undefinedDocker / No-Network Sandbox
Docker / 无网络沙箱
dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .Tests start emulators programmatically — no outbound network needed
测试通过程序化方式启动模拟器——无需外部网络
RUN npm test
undefinedRUN npm test
undefinedKey API Endpoints Reference
核心API端点参考
GitHub Emulator
GitHub 模拟器
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 issuesGET /user # 已认证用户信息
GET /repos/:owner/:repo # 获取仓库信息
POST /user/repos # 创建仓库
POST /repos/:owner/:repo/issues # 创建Issue
PATCH /repos/:owner/:repo/issues/:number # 更新Issue
POST /repos/:owner/:repo/pulls # 创建PR
PUT /repos/:owner/:repo/pulls/:number/merge # 合并PR
GET /search/repositories # 搜索仓库
GET /search/issues # 搜索IssueVercel Emulator
Vercel 模拟器
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 varsGET /v2/user # 已认证用户信息
GET /v2/teams # 列出团队
POST /v11/projects # 创建项目
GET /v10/projects # 列出项目
POST /v13/deployments # 创建部署(自动进入READY状态)
GET /v13/deployments/:idOrUrl # 获取部署信息
POST /v10/projects/:id/env # 创建环境变量
GET /v10/projects/:id/env # 列出环境变量Troubleshooting
故障排查
Port already in use
bash
undefined端口已被占用
bash
undefinedUse a different base port
使用其他基础端口
npx emulate --port 5000
npx emulate --port 5000
Or set via env
或通过环境变量设置
EMULATE_PORT=5000 npx emulate
**Tests interfering with each other**
```typescript
// Always call reset() in afterEach, not afterAll
afterEach(() => emulator.reset())OAuth strict validation rejecting requests
- If you configure or
oauth_apps, only matchingintegrationsvalues are acceptedclient_id - Remove the block to fall back to accept-any mode
oauth_apps
Emulator not receiving requests from app code
typescript
// 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_URLGitHub App JWT auth failing
- JWT must have as a string or number matching the configured
{ iss: "<app_id>" }app_id - Must be signed RS256 with the exact private key from config
- The emulator verifies the signature — use a real RSA key pair in tests
EMULATE_PORT=5000 npx emulate
**测试相互干扰**
```typescript—
务必在afterEach中调用reset(),而非afterAll
—
afterEach(() => emulator.reset())
**OAuth严格校验拒绝请求**
- 若配置了`oauth_apps`或`integrations`,仅会接受匹配的`client_id`
- 删除`oauth_apps`配置块即可回到接受任意client_id的模式
**模拟器未接收到应用代码的请求**
```typescript—
确保应用在发起请求时从环境变量读取URL,而非在模块加载时捕获
—
// ✅ 正确方式
async function fetchUser() {
return fetch()
}
${process.env.GITHUB_API_URL}/user// ❌ 错误方式——在模拟器启动前已捕获URL
const API_URL = process.env.GITHUB_API_URL
**GitHub App JWT认证失败**
- JWT必须包含`{ iss: "<app_id>" }`,其值需与配置的`app_id`(字符串或数字类型)匹配
- 必须使用配置中的私钥通过RS256算法签署
- 模拟器会验证签名——测试中请使用真实的RSA密钥对