mcpField
E
emulate

emulate

github.com/vercel-labs测试与质量2.0k542 次安装6 天前更新
代码仓库

关于 emulate

emulate 是面向 CI 和无网络沙箱的本地服务模拟器,为多种 API 提供有状态的替代服务。开发者可通过命令行或程序化 API 启动服务,并用种子数据配置测试环境。

主要功能

  1. 01启动多种本地 API 模拟服务
  2. 02通过命令行选择和配置服务
  3. 03程序化创建单个服务
  4. 04用种子数据初始化状态
  5. 05支持文件或自定义持久化适配器
  6. 06脚手架创建自定义模拟器

适用场景

  1. 01在 CI 中测试依赖外部 API 的应用
  2. 02在无网络环境运行集成测试
  3. 03为 OAuth 和 API 测试配置本地数据
  4. 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):

服务默认端口
Vercel4000
GitHub4001
Google4002
Slack4003
Apple4004
Microsoft4005
Okta4006
AWS4007
Resend4008
Stripe4009
MongoDB Atlas4010
Clerk4011
Linear4012
Twilio4013

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, --port4000基础端口(每个服务自动递增)
--host127.0.0.1监听地址;使用 0.0.0.0 允许网络访问
-s, --serviceall以逗号分隔的要启用的服务
--seedauto-detect种子配置文件路径(YAML 或 JSON)
--base-urlnone覆盖对外公布的基础 URL(支持 {service} 模板)
--portlessoff通过 portless 使用 HTTPS 提供服务(自动注册别名)
--generated-secrets-filenone生成未提供的服务密钥,并将其写入新的仅所有者可访问的 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'
port4000HTTP 服务的端口
hostname127.0.0.1内置及自定义模拟器的监听地址
seednone内联种子数据(与 YAML 配置格式相同)
baseUrlnone覆盖对外公布的基础 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 会按以下顺序自动检测配置文件:

  1. emulate.config.yaml / .yml
  2. emulate.config.json
  3. service-emulator.config.yaml / .yml
  4. service-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。

评论

支持 Markdown