emulate

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Service Emulation with emulate

使用emulate进行服务模拟

Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation, not mocks.
适用于CI环境和无网络沙箱的本地可直接替代服务。具备完整状态、与生产环境一致的API模拟,而非简单Mock。

Quick Start

快速开始

bash
npx emulate
All services start with sensible defaults:
ServiceDefault Port
Vercel4000
GitHub4001
Google4002
Slack4003
Apple4004
Microsoft4005
AWS4006
bash
npx emulate
所有服务均以合理默认配置启动:
Service默认端口
Vercel4000
GitHub4001
Google4002
Slack4003
Apple4004
Microsoft4005
AWS4006

CLI

CLI 命令

bash
undefined
bash
undefined

Start all services (zero-config)

启动所有服务(零配置)

emulate
emulate

Start specific services

启动指定服务

emulate --service vercel,github
emulate --service vercel,github

Custom base port (auto-increments per service)

自定义基础端口(每个服务自动递增)

emulate --port 3000
emulate --port 3000

Use a seed config file

使用种子配置文件

emulate --seed config.yaml
emulate --seed config.yaml

Generate a starter config

生成初始配置

emulate init
emulate init

Generate config for a specific service

生成指定服务的配置

emulate init --service vercel
emulate init --service vercel

List available services

列出可用服务

emulate list
undefined
emulate list
undefined

Options

选项说明

FlagDefaultDescription
-p, --port
4000
Base port (auto-increments per service)
-s, --service
allComma-separated services to enable
--seed
auto-detectPath to seed config (YAML or JSON)
The port can also be set via
EMULATE_PORT
or
PORT
environment variables.
标识默认值描述
-p, --port
4000
基础端口(每个服务自动递增)
-s, --service
全部启用的服务列表,以逗号分隔
--seed
自动检测种子配置文件路径(YAML或JSON格式)
端口也可通过环境变量
EMULATE_PORT
PORT
设置。

Programmatic API

编程式API

bash
npm install emulate
Each call to
createEmulator
starts a single service:
typescript
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()
bash
npm install emulate
每次调用
createEmulator
会启动单个服务:
typescript
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()

Options

选项说明

OptionDefaultDescription
service
(required)
'vercel'
,
'github'
,
'google'
,
'slack'
,
'apple'
,
'microsoft'
, or
'aws'
port
4000
Port for the HTTP server
seed
noneInline seed data (same shape as YAML config)
选项默认值描述
service
必填
'vercel'
,
'github'
,
'google'
,
'slack'
,
'apple'
,
'microsoft'
, 或
'aws'
port
4000
HTTP服务器端口
seed
内联种子数据(与YAML配置结构一致)

Instance Methods

实例方法

MethodDescription
url
Base URL of the running server
reset()
Wipe the store and replay seed data
close()
Shut down the HTTP server, returns a Promise
方法描述
url
运行中服务器的基础URL
reset()
清空存储并重新加载种子数据
close()
关闭HTTP服务器,返回Promise

Vitest / Jest Setup

Vitest / Jest 配置

typescript
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()]))
typescript
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()]))

Configuration

配置说明

Configuration is optional. The CLI auto-detects config files in this order:
  1. emulate.config.yaml
    /
    .yml
  2. emulate.config.json
  3. service-emulator.config.yaml
    /
    .yml
  4. service-emulator.config.json
Or pass
--seed <file>
explicitly. Run
emulate init
to generate a starter file.
配置为可选操作。CLI会按以下顺序自动检测配置文件:
  1. emulate.config.yaml
    /
    .yml
  2. emulate.config.json
  3. service-emulator.config.yaml
    /
    .yml
  4. service-emulator.config.json
也可通过
--seed <file>
显式指定配置文件。运行
emulate init
可生成初始配置文件。

Config Structure

配置结构

yaml
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
  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: octocat@github.com
  orgs:
    - login: my-org
      name: My Organization
  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: testuser@example.com
      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: dev@example.com
  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

apple:
  users:
    - email: testuser@icloud.com
      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: testuser@outlook.com
      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
yaml
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
  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: octocat@github.com
  orgs:
    - login: my-org
      name: My Organization
  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: testuser@example.com
      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: dev@example.com
  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

apple:
  users:
    - email: testuser@icloud.com
      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: testuser@outlook.com
      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

Auth

认证机制

Tokens map to users. Pass them as
Authorization: Bearer <token>
or
Authorization: token <token>
. When no tokens are configured, a default
test_token_admin
is created for the
admin
user.
Each service also has a fallback user. If no token is provided, requests authenticate as the first seeded user.
令牌与用户绑定。可通过
Authorization: Bearer <token>
Authorization: token <token>
传递。未配置令牌时,系统会为
admin
用户创建默认令牌
test_token_admin
每个服务还包含一个备用用户。若未提供令牌,请求会自动认证为第一个种子用户。

Pointing Your App at the Emulator

将应用指向模拟器

Set environment variables to override real service URLs:
bash
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:4006
Then use these in your app to construct API and OAuth URLs. See each service's skill for SDK-specific override instructions.
设置环境变量以覆盖真实服务URL:
bash
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:4006
随后在应用中使用这些变量构建API和OAuth URL。具体SDK的覆盖方式可参考各服务对应的技能文档。

Next.js Integration (Embedded Mode)

Next.js 集成(嵌入模式)

The
@emulators/adapter-next
package embeds emulators directly into a Next.js app on the same origin. See the next skill (
skills/next/SKILL.md
) for full setup, Auth.js configuration, persistence, and font tracing details.
@emulators/adapter-next
包可将模拟器直接嵌入Next.js应用,实现同源访问。完整配置、Auth.js设置、持久化及字体追踪细节请查看 next 技能文档(
skills/next/SKILL.md
)。

Persistence

持久化

By default, all emulator state is in-memory. For persistence across process restarts and serverless cold starts, use a
PersistenceAdapter
.
默认情况下,所有模拟器状态均存储在内存中。若需跨进程重启或无服务器冷启动保留状态,可使用
PersistenceAdapter

Built-in file persistence

内置文件持久化

typescript
import { filePersistence } from '@emulators/core'

// CLI or local dev: persists to a JSON file
const adapter = filePersistence('.emulate/state.json')
typescript
import { filePersistence } from '@emulators/core'

// CLI或本地开发:持久化至JSON文件
const adapter = filePersistence('.emulate/state.json')

Custom adapters

自定义适配器

typescript
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) },
}
State is loaded on cold start and saved after every mutating request (POST, PUT, PATCH, DELETE). Saves are serialized to prevent race conditions.
typescript
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)后保存。保存操作会序列化执行,以避免竞争条件。

Architecture

架构

packages/
  emulate/           # CLI entry point + programmatic API
  @emulators/
    core/            # HTTP server (Hono), Store, plugin interface, middleware
    adapter-next/    # Next.js App Router 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
    apple/           # Sign in with Apple / OIDC plugin
    microsoft/       # Microsoft Entra ID OAuth 2.0 / OIDC plugin
    aws/             # AWS S3, SQS, IAM, STS plugin
The core provides a generic
Store
with typed
Collection<T>
instances supporting CRUD, indexing, filtering, and pagination. Each service plugin registers routes on the shared Hono app and uses the store for state.
packages/
  emulate/           # CLI入口 + 编程式API
  @emulators/
    core/            # HTTP服务器(Hono)、存储、插件接口、中间件
    adapter-next/    # Next.js App Router集成
    vercel/          # Vercel API服务插件
    github/          # GitHub API服务插件
    google/          # Google OAuth 2.0 / OIDC插件
    slack/           # Slack Web API、OAuth、入站Webhook插件
    apple/           # Apple登录 / OIDC插件
    microsoft/       # Microsoft Entra ID OAuth 2.0 / OIDC插件
    aws/             # AWS S3、SQS、IAM、STS插件
核心模块提供通用
Store
,包含支持CRUD、索引、筛选和分页的类型化
Collection<T>
实例。每个服务插件会在共享Hono应用上注册路由,并使用存储模块管理状态。