|
| 1 | +--- |
| 2 | +name: emulate |
| 3 | +description: Local drop-in API emulator for Vercel, GitHub, Google, Slack, Apple, Microsoft, and AWS. Use when the user needs to start emulated services, configure seed data, write tests against local APIs, set up CI without network access, or work with the emulate CLI or programmatic API. Triggers include "start the emulator", "emulate services", "mock API locally", "create emulator config", "test against local API", "npx emulate", or any task requiring local service emulation. |
| 4 | +allowed-tools: Bash(npx emulate:*), Bash(emulate:*) |
| 5 | +--- |
| 6 | + |
| 7 | +# Service Emulation with emulate |
| 8 | + |
| 9 | +Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation, not mocks. |
| 10 | + |
| 11 | +## Quick Start |
| 12 | + |
| 13 | +```bash |
| 14 | +npx emulate |
| 15 | +``` |
| 16 | + |
| 17 | +All services start with sensible defaults: |
| 18 | + |
| 19 | +| Service | Default Port | |
| 20 | +|-----------|-------------| |
| 21 | +| Vercel | 4000 | |
| 22 | +| GitHub | 4001 | |
| 23 | +| Google | 4002 | |
| 24 | +| Slack | 4003 | |
| 25 | +| Apple | 4004 | |
| 26 | +| Microsoft | 4005 | |
| 27 | +| AWS | 4006 | |
| 28 | + |
| 29 | +## CLI |
| 30 | + |
| 31 | +```bash |
| 32 | +# Start all services (zero-config) |
| 33 | +emulate |
| 34 | + |
| 35 | +# Start specific services |
| 36 | +emulate --service vercel,github |
| 37 | + |
| 38 | +# Custom base port (auto-increments per service) |
| 39 | +emulate --port 3000 |
| 40 | + |
| 41 | +# Use a seed config file |
| 42 | +emulate --seed config.yaml |
| 43 | + |
| 44 | +# Generate a starter config |
| 45 | +emulate init |
| 46 | + |
| 47 | +# Generate config for a specific service |
| 48 | +emulate init --service vercel |
| 49 | + |
| 50 | +# List available services |
| 51 | +emulate list |
| 52 | +``` |
| 53 | + |
| 54 | +### Options |
| 55 | + |
| 56 | +| Flag | Default | Description | |
| 57 | +|------|---------|-------------| |
| 58 | +| `-p, --port` | `4000` | Base port (auto-increments per service) | |
| 59 | +| `-s, --service` | all | Comma-separated services to enable | |
| 60 | +| `--seed` | auto-detect | Path to seed config (YAML or JSON) | |
| 61 | + |
| 62 | +The port can also be set via `EMULATE_PORT` or `PORT` environment variables. |
| 63 | + |
| 64 | +## Programmatic API |
| 65 | + |
| 66 | +```bash |
| 67 | +npm install emulate |
| 68 | +``` |
| 69 | + |
| 70 | +Each call to `createEmulator` starts a single service: |
| 71 | + |
| 72 | +```typescript |
| 73 | +import { createEmulator } from 'emulate' |
| 74 | + |
| 75 | +const github = await createEmulator({ service: 'github', port: 4001 }) |
| 76 | +const vercel = await createEmulator({ service: 'vercel', port: 4002 }) |
| 77 | + |
| 78 | +github.url // 'http://localhost:4001' |
| 79 | +vercel.url // 'http://localhost:4002' |
| 80 | + |
| 81 | +await github.close() |
| 82 | +await vercel.close() |
| 83 | +``` |
| 84 | + |
| 85 | +### Options |
| 86 | + |
| 87 | +| Option | Default | Description | |
| 88 | +|--------|---------|-------------| |
| 89 | +| `service` | *(required)* | `'vercel'`, `'github'`, `'google'`, `'slack'`, `'apple'`, `'microsoft'`, or `'aws'` | |
| 90 | +| `port` | `4000` | Port for the HTTP server | |
| 91 | +| `seed` | none | Inline seed data (same shape as YAML config) | |
| 92 | + |
| 93 | +### Instance Methods |
| 94 | + |
| 95 | +| Method | Description | |
| 96 | +|--------|-------------| |
| 97 | +| `url` | Base URL of the running server | |
| 98 | +| `reset()` | Wipe the store and replay seed data | |
| 99 | +| `close()` | Shut down the HTTP server, returns a Promise | |
| 100 | + |
| 101 | +## Vitest / Jest Setup |
| 102 | + |
| 103 | +```typescript |
| 104 | +import { createEmulator, type Emulator } from 'emulate' |
| 105 | + |
| 106 | +let github: Emulator |
| 107 | +let vercel: Emulator |
| 108 | + |
| 109 | +beforeAll(async () => { |
| 110 | + ;[github, vercel] = await Promise.all([ |
| 111 | + createEmulator({ service: 'github', port: 4001 }), |
| 112 | + createEmulator({ service: 'vercel', port: 4002 }), |
| 113 | + ]) |
| 114 | + process.env.GITHUB_URL = github.url |
| 115 | + process.env.VERCEL_URL = vercel.url |
| 116 | +}) |
| 117 | + |
| 118 | +afterEach(() => { github.reset(); vercel.reset() }) |
| 119 | +afterAll(() => Promise.all([github.close(), vercel.close()])) |
| 120 | +``` |
| 121 | + |
| 122 | +## Configuration |
| 123 | + |
| 124 | +Configuration is optional. The CLI auto-detects config files in this order: |
| 125 | + |
| 126 | +1. `emulate.config.yaml` / `.yml` |
| 127 | +2. `emulate.config.json` |
| 128 | +3. `service-emulator.config.yaml` / `.yml` |
| 129 | +4. `service-emulator.config.json` |
| 130 | + |
| 131 | +Or pass `--seed <file>` explicitly. Run `emulate init` to generate a starter file. |
| 132 | + |
| 133 | +### Config Structure |
| 134 | + |
| 135 | +```yaml |
| 136 | +tokens: |
| 137 | + my_token: |
| 138 | + login: admin |
| 139 | + scopes: [repo, user] |
| 140 | + |
| 141 | +vercel: |
| 142 | + users: |
| 143 | + - username: developer |
| 144 | + name: Developer |
| 145 | + email: dev@example.com |
| 146 | + teams: |
| 147 | + - slug: my-team |
| 148 | + name: My Team |
| 149 | + projects: |
| 150 | + - name: my-app |
| 151 | + team: my-team |
| 152 | + framework: nextjs |
| 153 | + integrations: |
| 154 | + - client_id: oac_abc123 |
| 155 | + client_secret: secret_abc123 |
| 156 | + name: My Vercel App |
| 157 | + redirect_uris: |
| 158 | + - http://localhost:3000/api/auth/callback/vercel |
| 159 | + |
| 160 | +github: |
| 161 | + users: |
| 162 | + - login: octocat |
| 163 | + name: The Octocat |
| 164 | + email: octocat@github.com |
| 165 | + orgs: |
| 166 | + - login: my-org |
| 167 | + name: My Organization |
| 168 | + repos: |
| 169 | + - owner: octocat |
| 170 | + name: hello-world |
| 171 | + language: JavaScript |
| 172 | + auto_init: true |
| 173 | + oauth_apps: |
| 174 | + - client_id: Iv1.abc123 |
| 175 | + client_secret: secret_abc123 |
| 176 | + name: My Web App |
| 177 | + redirect_uris: |
| 178 | + - http://localhost:3000/api/auth/callback/github |
| 179 | + |
| 180 | +google: |
| 181 | + users: |
| 182 | + - email: testuser@example.com |
| 183 | + name: Test User |
| 184 | + oauth_clients: |
| 185 | + - client_id: my-client-id.apps.googleusercontent.com |
| 186 | + client_secret: GOCSPX-secret |
| 187 | + redirect_uris: |
| 188 | + - http://localhost:3000/api/auth/callback/google |
| 189 | + |
| 190 | +slack: |
| 191 | + team: |
| 192 | + name: My Workspace |
| 193 | + domain: my-workspace |
| 194 | + users: |
| 195 | + - name: developer |
| 196 | + real_name: Developer |
| 197 | + email: dev@example.com |
| 198 | + channels: |
| 199 | + - name: general |
| 200 | + topic: General discussion |
| 201 | + bots: |
| 202 | + - name: my-bot |
| 203 | + oauth_apps: |
| 204 | + - client_id: "12345.67890" |
| 205 | + client_secret: example_client_secret |
| 206 | + name: My Slack App |
| 207 | + redirect_uris: |
| 208 | + - http://localhost:3000/api/auth/callback/slack |
| 209 | + |
| 210 | +apple: |
| 211 | + users: |
| 212 | + - email: testuser@icloud.com |
| 213 | + name: Test User |
| 214 | + oauth_clients: |
| 215 | + - client_id: com.example.app |
| 216 | + team_id: TEAM001 |
| 217 | + name: My Apple App |
| 218 | + redirect_uris: |
| 219 | + - http://localhost:3000/api/auth/callback/apple |
| 220 | + |
| 221 | +microsoft: |
| 222 | + users: |
| 223 | + - email: testuser@outlook.com |
| 224 | + name: Test User |
| 225 | + oauth_clients: |
| 226 | + - client_id: example-client-id |
| 227 | + client_secret: example-client-secret |
| 228 | + name: My Microsoft App |
| 229 | + redirect_uris: |
| 230 | + - http://localhost:3000/api/auth/callback/microsoft-entra-id |
| 231 | + |
| 232 | +aws: |
| 233 | + region: us-east-1 |
| 234 | + s3: |
| 235 | + buckets: |
| 236 | + - name: my-app-bucket |
| 237 | + sqs: |
| 238 | + queues: |
| 239 | + - name: my-app-events |
| 240 | + iam: |
| 241 | + users: |
| 242 | + - user_name: developer |
| 243 | + create_access_key: true |
| 244 | + roles: |
| 245 | + - role_name: lambda-execution-role |
| 246 | +``` |
| 247 | +
|
| 248 | +### Auth |
| 249 | +
|
| 250 | +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. |
| 251 | + |
| 252 | +Each service also has a fallback user. If no token is provided, requests authenticate as the first seeded user. |
| 253 | + |
| 254 | +## Pointing Your App at the Emulator |
| 255 | + |
| 256 | +Set environment variables to override real service URLs: |
| 257 | + |
| 258 | +```bash |
| 259 | +VERCEL_EMULATOR_URL=http://localhost:4000 |
| 260 | +GITHUB_EMULATOR_URL=http://localhost:4001 |
| 261 | +GOOGLE_EMULATOR_URL=http://localhost:4002 |
| 262 | +SLACK_EMULATOR_URL=http://localhost:4003 |
| 263 | +APPLE_EMULATOR_URL=http://localhost:4004 |
| 264 | +MICROSOFT_EMULATOR_URL=http://localhost:4005 |
| 265 | +AWS_EMULATOR_URL=http://localhost:4006 |
| 266 | +``` |
| 267 | + |
| 268 | +Then use these in your app to construct API and OAuth URLs. See each service's skill for SDK-specific override instructions. |
| 269 | + |
| 270 | +## Architecture |
| 271 | + |
| 272 | +``` |
| 273 | +packages/ |
| 274 | + emulate/ # CLI entry point + programmatic API |
| 275 | + @emulators/ |
| 276 | + core/ # HTTP server (Hono), Store, plugin interface, middleware |
| 277 | + vercel/ # Vercel API service plugin |
| 278 | + github/ # GitHub API service plugin |
| 279 | + google/ # Google OAuth 2.0 / OIDC plugin |
| 280 | + slack/ # Slack Web API, OAuth, incoming webhooks plugin |
| 281 | + apple/ # Sign in with Apple / OIDC plugin |
| 282 | + microsoft/ # Microsoft Entra ID OAuth 2.0 / OIDC plugin |
| 283 | + aws/ # AWS S3, SQS, IAM, STS plugin |
| 284 | +``` |
| 285 | +
|
| 286 | +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. |
0 commit comments