关于 emulate
emulate 是面向 CI 和无网络沙箱的本地服务模拟器,为多种 API 提供有状态的替代服务。开发者可通过命令行或程序化 API 启动服务,并用种子数据配置测试环境。
主要功能
- 01启动多种本地 API 模拟服务
- 02通过命令行选择和配置服务
- 03程序化创建单个服务
- 04用种子数据初始化状态
- 05支持文件或自定义持久化适配器
- 06脚手架创建自定义模拟器
适用场景
- 01在 CI 中测试依赖外部 API 的应用
- 02在无网络环境运行集成测试
- 03为 OAuth 和 API 测试配置本地数据
- 04开发并测试第三方 API 模拟器
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vercel-labs/ emulate --skill emulate安装后在 Claude Code 中输入/emulate确认已加载。
详细介绍
使用 emulate 模拟服务
用于 CI 和无网络沙盒的本地即插即用替代服务。提供完全有状态、与生产环境一致的 API 模拟,而非 mock。
快速开始
npx emulate
默认情况下,所有服务都监听 IPv4 环回地址(127.0.0.1):
| 服务 | 默认端口 |
|---|---|
| Vercel | 4000 |
| GitHub | 4001 |
| 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
# 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
选项
| 标志 | 默认值 | 描述 |
|---|---|---|
-p, --port | 4000 | 基础端口(每个服务自动递增) |
--host | 127.0.0.1 | 监听地址;使用 0.0.0.0 允许网络访问 |
-s, --service | all | 以逗号分隔的要启用的服务 |
--seed | auto-detect | 种子配置文件路径(YAML 或 JSON) |
--base-url | none | 覆盖对外公布的基础 URL(支持 {service} 模板) |
--portless | off | 通过 portless 使用 HTTPS 提供服务(自动注册别名) |
--generated-secrets-file | none | 生成未提供的服务密钥,并将其写入新的仅所有者可访问的 JSON 文件 |
也可以通过 EMULATE_PORT 或 PORT 环境变量设置端口。
若要从容器或其他机器访问,请使用 npx emulate start --host 0.0.0.0。使用 OAuth 重定向或其他对外公布的 URL 时,请将 --base-url 设置为这些客户端可访问的 URL。
生成的密钥文件目标路径必须不存在。emulate 会移除继承的 ACL,验证有效权限仅限所有者,并在打开监听器或配置 portless 之前写入完整的 JSON。已处理的启动失败会删除本次调用创建的文件。若进程被强制终止,可能会留下完整文件;确认没有调用正在使用该文件后,必须手动将其删除。文件中只包含服务生成的值。Linux 需要 acl 软件包提供的 setfacl 和 getfacl。如果无法验证访问控制,该标志会拒绝执行;Windows 不支持此标志。
可通过 --base-url、EMULATE_BASE_URL 环境变量(支持 {service} 模板)或种子配置中的各服务 baseUrl 覆盖对外公布的基础 URL(用于 OAuth 重定向、Webhook URL 等)。在 portless 下运行时,也会自动检测 PORTLESS_URL 环境变量。
编程式 API
npm install emulate
每次调用 createEmulator 都会启动单个服务:
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()
对于 GitHub App 测试,可通过 GET /_emulate/installation-tokens 查看不含密钥的已签发安装令牌元数据。
选项
| 选项 | 默认值 | 描述 |
|---|---|---|
service | (必填) | 'vercel'、'github'、'google'、'slack'、'apple'、'microsoft'、'okta'、'aws'、'resend'、'stripe'、'mongoatlas'、'clerk'、'linear' 或 'twilio' |
port | 4000 | HTTP 服务的端口 |
hostname | 127.0.0.1 | 内置及自定义模拟器的监听地址 |
seed | none | 内联种子数据(与 YAML 配置格式相同) |
baseUrl | none | 覆盖对外公布的基础 URL。优先级最高的是种子配置中各服务的 baseUrl,其次是此选项,然后是 EMULATE_BASE_URL 环境变量(支持 {service}),接着是 PORTLESS_URL(支持 {service},由 portless CLI 包装器自动设置),最后是 http://localhost:<port>。 |
使用 hostname: '0.0.0.0' 允许来自容器或其他机器的连接,并使用 baseUrl 公布这些客户端可访问的 URL。
实例方法
| 方法 | 描述 |
|---|---|
url | 运行中服务的基础 URL |
reset() | 清空存储并重新载入种子数据 |
close() | 关闭 HTTP 服务,返回一个 Promise |
Vitest / Jest 配置
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()]))
配置
配置为可选项。CLI 会按以下顺序自动检测配置文件:
emulate.config.yaml/.ymlemulate.config.jsonservice-emulator.config.yaml/.ymlservice-emulator.config.json
也可以显式传入 --seed <file>。运行 npx emulate init 可生成初始文件。
配置结构
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
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: [email protected]
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: [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
slack:
team:
name: My Workspace
domain: my-workspace
users:
- name: developer
real_name: Developer
email: [email protected]
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: [email protected]
name: Admin User
admin: true
- email: [email protected]
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: [email protected]
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: [email protected]
scopes: [read, write, issues:create, comments:create, admin]
strict_scopes: false
apple:
users:
- email: [email protected]
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: [email protected]
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: [email protected]
email: [email protected]
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: [email protected]
first_name: Test
last_name: User
stripe:
customers:
- email: [email protected]
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: [[email protected]]
password: clerk_test_password
organizations:
- name: My Company
slug: my-company
members:
- email: [email protected]
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 用于对每个发出的事件订阅回调进行签名。签名后的回调包含 X-Slack-Request-Timestamp 和 X-Slack-Signature,计算方式为:对完全序列化的回调正文执行 v0=<HMAC-SHA256(secret, "v0:<timestamp>:<raw-body>")>。请使用相同的密钥配置接收端,并验证未经解析的请求正文。未设置密钥或密钥为空时,回调不会签名。
初始配置会有意省略 GitHub App 的 private_key 值。以编程方式调用 createEmulator 时,会生成 RSA 密钥,并通过 generatedSecrets 提供该密钥。CLI 启动时,只有在提供 --generated-secrets-file <path> 的情况下才会生成省略的密钥;否则,种子配置中必须包含有效的显式私钥。切勿使用占位 PEM 值。
GitHub 组织的 members 为可选项。条目通过 login 引用已植入的用户;role 默认值为 member,而 admin 会创建组织管理员。未知用户会被忽略。
认证
令牌会映射到用户。将令牌作为 Authorization: Bearer <token> 或 Authorization: token <token> 传入。未配置令牌时,会为 admin 用户创建默认令牌 test_token_admin。
每项服务也有一个备用用户。如果未提供令牌,请求会以第一个植入的用户进行身份验证。
使用 portless 配置 HTTPS
portless 可为模拟器提供自动生成证书的可信 HTTPS URL。使用 --portless 标志,可将每项服务自动注册为 portless 别名:
npx emulate start --portless
# github https://github.emulate.localhost
# google https://google.emulate.localhost
# ...
这需要 portless 代理正在运行(portless proxy start)。如果未安装 portless,emulate 会提示安装。
--portless 标志会覆盖与 *.emulate 匹配的所有现有 portless 别名。emulate 关闭时会自动移除这些别名。
使用 portless 为单项服务配置代理:
portless github.emulate emulate start --service github
不使用 portless 时,为自定义基础 URL 配置代理(适用于任意反向代理):
npx emulate start --base-url "https://{service}.myproxy.test"
# or
EMULATE_BASE_URL="https://{service}.myproxy.test" npx emulate start
通过 portless CLI 包装器运行命令时,PORTLESS_URL 环境变量会由包装器自动设置(例如 portless github.emulate emulate start),通常为 https://{service}.emulate.localhost 这样的值。它支持 {service} 插值,与 --base-url 和 EMULATE_BASE_URL 一样。未提供显式 baseUrl 时,会将其用作备用值。
种子配置中的逐服务覆盖项(其优先级高于所有其他基础 URL 来源):
github:
baseUrl: https://github.emulate.localhost
google:
baseUrl: https://google.emulate.localhost
将应用指向模拟器
设置环境变量以覆盖真实服务 URL:
VERCEL_EMULATOR_URL=http://localhost:4000
GITHUB_EMULATOR_URL=http://localhost:4001
GOOGLE_EMULATOR_URL=http://localhost:4002
SLACK_EMULATOR_URL=http://localhost:4003
APPLE_EMULATOR_URL=http://localhost:4004
MICROSOFT_EMULATOR_URL=http://localhost:4005
AWS_EMULATOR_URL=http://localhost:4007
LINEAR_EMULATOR_URL=http://localhost:4012
然后在应用中使用这些变量来构建 API 和 OAuth URL。SDK 特定的覆盖说明请参阅各项服务的 skill。
框架集成(嵌入模式)
@emulators/adapter-next 包可将模拟器直接嵌入同源的 Next.js 应用中。完整设置说明、Auth.js 配置、持久化和字体追踪详情,请参阅 next skill(skills/next/SKILL.md)。
@emulators/adapter-nuxt 包可将模拟器直接嵌入同源的 Nuxt 应用中。服务器路由、Nuxt 配置、OAuth 配置和持久化设置说明,请参阅 nuxt skill(skills/nuxt/SKILL.md)。
持久化
默认情况下,所有模拟器状态都存储在内存中。如需在进程重启和无服务器冷启动后保留状态,请使用 PersistenceAdapter。
内置文件持久化
import { filePersistence } from '@emulators/core'
// CLI or local dev: persists to a JSON file
const adapter = filePersistence('.emulate/state.json')
自定义适配器
import type { PersistenceAdapter } from '@emulators/core'
const kvAdapter: PersistenceAdapter = {
async load() { return await kv.get('emulate-state') },
async save(data) { await kv.set('emulate-state', data) },
}
冷启动时会加载状态,并在每次变更请求(POST、PUT、PATCH、DELETE)后保存状态。保存操作会串行执行,以避免竞争条件。生成的 GitHub App 身份需要通过 initialize 原子地创建初始值,或返回另一实例先创建的值。
架构
packages/
emulate/ # CLI entry point + programmatic API
@emulators/
core/ # HTTP server, Store, plugin interface, middleware
adapter-next/ # Next.js App Router integration
adapter-nuxt/ # Nuxt server route integration
vercel/ # Vercel API service plugin
github/ # GitHub API service plugin
google/ # Google OAuth 2.0 / OIDC plugin
slack/ # Slack Web API, OAuth, incoming webhooks plugin
linear/ # Linear GraphQL API, OAuth, webhooks plugin
twilio/ # Twilio Messaging, Verify, Voice, webhooks plugin
apple/ # Sign in with Apple / OIDC plugin
microsoft/ # Microsoft Entra ID OAuth 2.0 / OIDC plugin
aws/ # AWS S3, SQS, IAM, STS plugin
核心提供通用的 Store,其中包含支持 CRUD、索引、筛选和分页的类型化 Collection<T> 实例。每个服务插件都会向共享的内部应用注册路由,并使用 Store 管理状态。
与内置模拟器一同使用自定义模拟器
使用 npx emulate init --custom inventory 搭建第三方 API 模拟器和测试。该命令会在可发现的 YAML、JSON、TypeScript 或 JavaScript 配置中注册模拟器,也包括已有服务的配置;对于不常见的可执行配置,命令会打印手动注册步骤。运行 npx emulate start --watch 以重新加载导入项。请使用启动横幅中的服务 URL 和 Inspector 链接发送请求,因为端口取决于配置。在配置目录之外创建缺失的本地导入项,也会重试失败的重新加载。成功重新加载后,运行会重置为种子数据。现有的扁平种子配置仍可使用;--config 用于指定显式文件。Node 26 可加载可擦除语法的 TypeScript;枚举和参数属性应先编译为 JavaScript。Node 24 也支持原生 TypeScript 转换。有关第三方 API 模拟器的编写和测试,请参阅 https://emulate.dev/docs/custom-emulators。