[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-74999":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":15,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":19,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":24,"hasPages":22,"topics":25,"createdAt":10,"pushedAt":10,"updatedAt":26,"readmeContent":27,"aiSummary":28,"trendingCount":16,"starSnapshotCount":16,"syncStatus":29,"lastSyncTime":30,"discoverSource":31},74999,"emulate","vercel-labs\u002Femulate","vercel-labs","Local API emulation for CI and no-network sandboxes","https:\u002F\u002Femulate.dev",null,"TypeScript",1330,81,1,9,0,6,106,18,80.24,"Apache License 2.0",false,"main",true,[],"2026-06-12 04:01:16","# emulate\n\nLocal drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation. Not mocks.\n\n## Quick Start\n\n```bash\nnpx emulate\n```\n\nAll services start with sensible defaults. No config file needed:\n\n- **Vercel** on `http:\u002F\u002Flocalhost:4000`\n- **GitHub** on `http:\u002F\u002Flocalhost:4001`\n- **Google** on `http:\u002F\u002Flocalhost:4002`\n- **Slack** on `http:\u002F\u002Flocalhost:4003`\n- **Apple** on `http:\u002F\u002Flocalhost:4004`\n- **Microsoft** on `http:\u002F\u002Flocalhost:4005`\n- **AWS** on `http:\u002F\u002Flocalhost:4006`\n\n## CLI\n\n```bash\n# Start all services (zero-config)\nnpx emulate\n\n# Start specific services\nnpx emulate --service vercel,github\n\n# Custom port\nnpx emulate --port 3000\n\n# Use a seed config file\nnpx emulate --seed config.yaml\n\n# Generate a starter config\nnpx emulate init\n\n# Generate config for a specific service\nnpx emulate init --service vercel\n\n# List available services\nnpx emulate list\n```\n\n### Options\n\n| Flag | Default | Description |\n|------|---------|-------------|\n| `-p, --port` | `4000` | Base port (auto-increments per service) |\n| `-s, --service` | all | Comma-separated services to enable |\n| `--seed` | auto-detect | Path to seed config (YAML or JSON) |\n| `--base-url` | none | Override advertised base URL (supports `{service}` template) |\n| `--portless` | off | Serve over HTTPS via portless (auto-registers aliases) |\n\nThe port can also be set via `EMULATE_PORT` or `PORT` environment variables.\n\n## HTTPS with portless\n\n[portless](https:\u002F\u002Fgithub.com\u002Fvercel-labs\u002Fportless) gives emulators trusted HTTPS URLs with auto-generated certs and no browser warnings.\n\n```bash\n# Start the portless proxy (first time only)\nportless proxy start\n\n# Start emulate with portless integration\nnpx emulate start --portless\n```\n\nEach service registers as a portless alias and gets a named HTTPS URL:\n\n```\ngithub  https:\u002F\u002Fgithub.emulate.localhost\ngoogle  https:\u002F\u002Fgoogle.emulate.localhost\nslack   https:\u002F\u002Fslack.emulate.localhost\n```\n\nIf portless is not installed, emulate will prompt to install it (`npm i -g portless`).\n\nThe `--portless` flag overwrites any existing portless aliases matching `*.emulate`. Aliases are removed automatically when emulate shuts down.\n\nFor a custom base URL without portless (any reverse proxy), use `--base-url` or the `EMULATE_BASE_URL` env var:\n\n```bash\nnpx emulate start --base-url \"https:\u002F\u002F{service}.myproxy.test\"\n```\n\nThe `PORTLESS_URL` env var is automatically set by the `portless` CLI wrapper when running a command through it (e.g. `portless github.emulate emulate start`), typically to a value like `https:\u002F\u002F{service}.emulate.localhost`. It supports `{service}` interpolation, just like `--base-url` and `EMULATE_BASE_URL`. When no explicit `baseUrl` is provided, it is used as a fallback.\n\nPer-service overrides are also supported in the seed config (these take highest priority over all other base URL sources):\n\n```yaml\ngithub:\n  baseUrl: https:\u002F\u002Fgithub.emulate.localhost\n```\n\n## Programmatic API\n\n```bash\nnpm install emulate\n```\n\nEach call to `createEmulator` starts a single service:\n\n```typescript\nimport { createEmulator } from 'emulate'\n\nconst github = await createEmulator({ service: 'github', port: 4001 })\nconst vercel = await createEmulator({ service: 'vercel', port: 4002 })\n\ngithub.url   \u002F\u002F 'http:\u002F\u002Flocalhost:4001'\nvercel.url   \u002F\u002F 'http:\u002F\u002Flocalhost:4002'\n\nawait github.close()\nawait vercel.close()\n```\n\n### Vitest \u002F Jest setup\n\n```typescript\n\u002F\u002F vitest.setup.ts\nimport { createEmulator, type Emulator } from 'emulate'\n\nlet github: Emulator\nlet vercel: Emulator\n\nbeforeAll(async () => {\n  ;[github, vercel] = await Promise.all([\n    createEmulator({ service: 'github', port: 4001 }),\n    createEmulator({ service: 'vercel', port: 4002 }),\n  ])\n  process.env.GITHUB_EMULATOR_URL = github.url\n  process.env.VERCEL_EMULATOR_URL = vercel.url\n})\n\nafterEach(() => { github.reset(); vercel.reset() })\nafterAll(() => Promise.all([github.close(), vercel.close()]))\n```\n\n### Options\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `service` | *(required)* | Service name: `'vercel'`, `'github'`, `'google'`, `'slack'`, `'apple'`, `'microsoft'`, or `'aws'` |\n| `port` | `4000` | Port for the HTTP server |\n| `seed` | none | Inline seed data (same shape as YAML config) |\n| `baseUrl` | none | Override advertised base URL. Per-service `baseUrl` in seed config takes highest priority, then this option, then `EMULATE_BASE_URL` env var (supports `{service}`), then `PORTLESS_URL` (supports `{service}`, automatically set by the `portless` CLI wrapper), then `http:\u002F\u002Flocalhost:\u003Cport>`. |\n\n### Instance methods\n\n| Method | Description |\n|--------|-------------|\n| `url` | Base URL of the running server |\n| `reset()` | Wipe the store and replay seed data |\n| `close()` | Shut down the HTTP server, returns a Promise |\n\n## Configuration\n\nConfiguration is optional. The CLI auto-detects config files in this order: `emulate.config.yaml` \u002F `.yml`, `emulate.config.json`, `service-emulator.config.yaml` \u002F `.yml`, `service-emulator.config.json`. Or pass `--seed \u003Cfile>` explicitly. Run `npx emulate init` to generate a starter file.\n\n```yaml\ntokens:\n  my_token:\n    login: admin\n    scopes: [repo, user]\n\nvercel:\n  users:\n    - username: developer\n      name: Developer\n      email: dev@example.com\n  teams:\n    - slug: my-team\n      name: My Team\n  projects:\n    - name: my-app\n      team: my-team\n      framework: nextjs\n\ngithub:\n  users:\n    - login: octocat\n      name: The Octocat\n      email: octocat@github.com\n  orgs:\n    - login: my-org\n      name: My Organization\n  repos:\n    - owner: octocat\n      name: hello-world\n      language: JavaScript\n      auto_init: true\n\ngoogle:\n  users:\n    - email: testuser@example.com\n      name: Test User\n    - email: admin@acme.com\n      name: Admin\n      hd: acme.com\n  oauth_clients:\n    - client_id: my-client-id.apps.googleusercontent.com\n      client_secret: GOCSPX-secret\n      redirect_uris:\n        - http:\u002F\u002Flocalhost:3000\u002Fapi\u002Fauth\u002Fcallback\u002Fgoogle\n  labels:\n    - id: Label_ops\n      user_email: testuser@example.com\n      name: Ops\u002FReview\n      color_background: \"#DDEEFF\"\n      color_text: \"#111111\"\n  messages:\n    - id: msg_welcome\n      user_email: testuser@example.com\n      from: welcome@example.com\n      to: testuser@example.com\n      subject: Welcome to the Gmail emulator\n      body_text: You can now test Gmail, Calendar, and Drive flows locally.\n      label_ids: [INBOX, UNREAD, CATEGORY_UPDATES]\n  calendars:\n    - id: primary\n      user_email: testuser@example.com\n      summary: testuser@example.com\n      primary: true\n      selected: true\n      time_zone: UTC\n  calendar_events:\n    - id: evt_kickoff\n      user_email: testuser@example.com\n      calendar_id: primary\n      summary: Project Kickoff\n      start_date_time: 2025-01-10T09:00:00.000Z\n      end_date_time: 2025-01-10T09:30:00.000Z\n  drive_items:\n    - id: drv_docs\n      user_email: testuser@example.com\n      name: Docs\n      mime_type: application\u002Fvnd.google-apps.folder\n      parent_ids: [root]\n\nslack:\n  team:\n    name: My Workspace\n    domain: my-workspace\n  users:\n    - name: developer\n      real_name: Developer\n      email: dev@example.com\n  channels:\n    - name: general\n      topic: General discussion\n    - name: random\n      topic: Random stuff\n  bots:\n    - name: my-bot\n  oauth_apps:\n    - client_id: \"12345.67890\"\n      client_secret: example_client_secret\n      name: My Slack App\n      redirect_uris:\n        - http:\u002F\u002Flocalhost:3000\u002Fapi\u002Fauth\u002Fcallback\u002Fslack\n\napple:\n  users:\n    - email: testuser@icloud.com\n      name: Test User\n  oauth_clients:\n    - client_id: com.example.app\n      team_id: TEAM001\n      name: My Apple App\n      redirect_uris:\n        - http:\u002F\u002Flocalhost:3000\u002Fapi\u002Fauth\u002Fcallback\u002Fapple\n\nmicrosoft:\n  users:\n    - email: testuser@outlook.com\n      name: Test User\n  oauth_clients:\n    - client_id: example-client-id\n      client_secret: example-client-secret\n      name: My Microsoft App\n      redirect_uris:\n        - http:\u002F\u002Flocalhost:3000\u002Fapi\u002Fauth\u002Fcallback\u002Fmicrosoft-entra-id\n\naws:\n  region: us-east-1\n  s3:\n    buckets:\n      - name: my-app-bucket\n      - name: my-app-uploads\n  sqs:\n    queues:\n      - name: my-app-events\n      - name: my-app-dlq\n  iam:\n    users:\n      - user_name: developer\n        create_access_key: true\n    roles:\n      - role_name: lambda-execution-role\n        description: Role for Lambda function execution\n```\n\n## OAuth & Integrations\n\nThe emulator supports configurable OAuth apps and integrations with strict client validation.\n\n### Vercel Integrations\n\n```yaml\nvercel:\n  integrations:\n    - client_id: \"oac_abc123\"\n      client_secret: \"secret_abc123\"\n      name: \"My Vercel App\"\n      redirect_uris:\n        - \"http:\u002F\u002Flocalhost:3000\u002Fapi\u002Fauth\u002Fcallback\u002Fvercel\"\n```\n\n### GitHub OAuth Apps\n\n```yaml\ngithub:\n  oauth_apps:\n    - client_id: \"Iv1.abc123\"\n      client_secret: \"secret_abc123\"\n      name: \"My Web App\"\n      redirect_uris:\n        - \"http:\u002F\u002Flocalhost:3000\u002Fapi\u002Fauth\u002Fcallback\u002Fgithub\"\n```\n\nIf no `oauth_apps` are configured, the emulator accepts any `client_id` (backward-compatible). With apps configured, strict validation is enforced.\n\n### GitHub Apps\n\nFull GitHub App support with JWT authentication and installation access tokens:\n\n```yaml\ngithub:\n  apps:\n    - app_id: 12345\n      slug: \"my-github-app\"\n      name: \"My GitHub App\"\n      private_key: |\n        -----BEGIN RSA PRIVATE KEY-----\n        ...your PEM key...\n        -----END RSA PRIVATE KEY-----\n      permissions:\n        contents: read\n        issues: write\n      events: [push, pull_request]\n      webhook_url: \"http:\u002F\u002Flocalhost:3000\u002Fwebhooks\u002Fgithub\"\n      webhook_secret: \"my-secret\"\n      installations:\n        - installation_id: 100\n          account: my-org\n          repository_selection: all\n```\n\nJWT authentication: sign a JWT with `{ iss: \"\u003Capp_id>\" }` using the app's private key (RS256). The emulator verifies the signature and resolves the app.\n\n**App webhook delivery**: When events occur on repos where a GitHub App is installed, the emulator mirrors real GitHub behavior:\n- All webhook payloads (including repo and org hooks) include an `installation` field with `{ id, node_id }`.\n- If the app has a `webhook_url`, the emulator delivers the event there with the `installation` field and (if configured) an `X-Hub-Signature-256` header signed with `webhook_secret`.\n\n### Slack OAuth Apps\n\n```yaml\nslack:\n  oauth_apps:\n    - client_id: \"12345.67890\"\n      client_secret: \"example_client_secret\"\n      name: \"My Slack App\"\n      redirect_uris:\n        - \"http:\u002F\u002Flocalhost:3000\u002Fapi\u002Fauth\u002Fcallback\u002Fslack\"\n```\n\n### Apple OAuth Clients\n\n```yaml\napple:\n  oauth_clients:\n    - client_id: \"com.example.app\"\n      team_id: \"TEAM001\"\n      name: \"My Apple App\"\n      redirect_uris:\n        - \"http:\u002F\u002Flocalhost:3000\u002Fapi\u002Fauth\u002Fcallback\u002Fapple\"\n```\n\n### Microsoft OAuth Clients\n\n```yaml\nmicrosoft:\n  oauth_clients:\n    - client_id: \"example-client-id\"\n      client_secret: \"example-client-secret\"\n      name: \"My Microsoft App\"\n      redirect_uris:\n        - \"http:\u002F\u002Flocalhost:3000\u002Fapi\u002Fauth\u002Fcallback\u002Fmicrosoft-entra-id\"\n```\n\n## Vercel API\n\nEvery endpoint below is fully stateful with Vercel-style JSON responses and cursor-based pagination.\n\n### User & Teams\n- `GET \u002Fv2\u002Fuser` - authenticated user\n- `PATCH \u002Fv2\u002Fuser` - update user\n- `GET \u002Fv2\u002Fteams` - list teams (cursor paginated)\n- `GET \u002Fv2\u002Fteams\u002F:teamId` - get team (by ID or slug)\n- `POST \u002Fv2\u002Fteams` - create team\n- `PATCH \u002Fv2\u002Fteams\u002F:teamId` - update team\n- `GET \u002Fv2\u002Fteams\u002F:teamId\u002Fmembers` - list members\n- `POST \u002Fv2\u002Fteams\u002F:teamId\u002Fmembers` - add member\n\n### Projects\n- `POST \u002Fv11\u002Fprojects` - create project (with optional env vars and git integration)\n- `GET \u002Fv10\u002Fprojects` - list projects (search, cursor pagination)\n- `GET \u002Fv9\u002Fprojects\u002F:idOrName` - get project (includes env vars)\n- `PATCH \u002Fv9\u002Fprojects\u002F:idOrName` - update project\n- `DELETE \u002Fv9\u002Fprojects\u002F:idOrName` - delete project (cascades)\n- `GET \u002Fv1\u002Fprojects\u002F:projectId\u002Fpromote\u002Faliases` - promote aliases status\n- `PATCH \u002Fv1\u002Fprojects\u002F:idOrName\u002Fprotection-bypass` - manage bypass secrets\n\n### Deployments\n- `POST \u002Fv13\u002Fdeployments` - create deployment (auto-transitions to READY)\n- `GET \u002Fv13\u002Fdeployments\u002F:idOrUrl` - get deployment (by ID or URL)\n- `GET \u002Fv6\u002Fdeployments` - list deployments (filter by project, target, state)\n- `DELETE \u002Fv13\u002Fdeployments\u002F:id` - delete deployment (cascades)\n- `PATCH \u002Fv12\u002Fdeployments\u002F:id\u002Fcancel` - cancel building deployment\n- `GET \u002Fv2\u002Fdeployments\u002F:id\u002Faliases` - list deployment aliases\n- `GET \u002Fv3\u002Fdeployments\u002F:idOrUrl\u002Fevents` - get build events\u002Flogs\n- `GET \u002Fv6\u002Fdeployments\u002F:id\u002Ffiles` - list deployment files\n- `POST \u002Fv2\u002Ffiles` - upload file (by SHA digest)\n\n### Domains\n- `POST \u002Fv10\u002Fprojects\u002F:idOrName\u002Fdomains` - add domain (with verification challenge)\n- `GET \u002Fv9\u002Fprojects\u002F:idOrName\u002Fdomains` - list domains\n- `GET \u002Fv9\u002Fprojects\u002F:idOrName\u002Fdomains\u002F:domain` - get domain\n- `PATCH \u002Fv9\u002Fprojects\u002F:idOrName\u002Fdomains\u002F:domain` - update domain\n- `DELETE \u002Fv9\u002Fprojects\u002F:idOrName\u002Fdomains\u002F:domain` - remove domain\n- `POST \u002Fv9\u002Fprojects\u002F:idOrName\u002Fdomains\u002F:domain\u002Fverify` - verify domain\n\n### Environment Variables\n- `GET \u002Fv10\u002Fprojects\u002F:idOrName\u002Fenv` - list env vars (with decrypt option)\n- `POST \u002Fv10\u002Fprojects\u002F:idOrName\u002Fenv` - create env vars (single, batch, upsert)\n- `GET \u002Fv10\u002Fprojects\u002F:idOrName\u002Fenv\u002F:id` - get env var\n- `PATCH \u002Fv9\u002Fprojects\u002F:idOrName\u002Fenv\u002F:id` - update env var\n- `DELETE \u002Fv9\u002Fprojects\u002F:idOrName\u002Fenv\u002F:id` - delete env var\n\n## GitHub API\n\nEvery endpoint below is fully stateful. Creates, updates, and deletes persist in memory and affect related entities.\n\n### Users\n- `GET \u002Fuser` - authenticated user\n- `PATCH \u002Fuser` - update profile\n- `GET \u002Fusers\u002F:username` - get user\n- `GET \u002Fusers` - list users\n- `GET \u002Fusers\u002F:username\u002Frepos` - list user repos\n- `GET \u002Fusers\u002F:username\u002Forgs` - list user orgs\n- `GET \u002Fusers\u002F:username\u002Ffollowers` - list followers\n- `GET \u002Fusers\u002F:username\u002Ffollowing` - list following\n\n### Repositories\n- `GET \u002Frepos\u002F:owner\u002F:repo` - get repo\n- `POST \u002Fuser\u002Frepos` - create user repo\n- `POST \u002Forgs\u002F:org\u002Frepos` - create org repo\n- `PATCH \u002Frepos\u002F:owner\u002F:repo` - update repo\n- `DELETE \u002Frepos\u002F:owner\u002F:repo` - delete repo (cascades)\n- `GET\u002FPUT \u002Frepos\u002F:owner\u002F:repo\u002Ftopics` - get\u002Freplace topics\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Flanguages` - languages\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fcontributors` - contributors\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fforks` - list forks\n- `POST \u002Frepos\u002F:owner\u002F:repo\u002Fforks` - create fork\n- `GET\u002FPUT\u002FDELETE \u002Frepos\u002F:owner\u002F:repo\u002Fcollaborators\u002F:username` - collaborators\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fcollaborators\u002F:username\u002Fpermission`\n- `POST \u002Frepos\u002F:owner\u002F:repo\u002Ftransfer` - transfer repo\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Ftags` - list tags\n\n### Issues\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fissues` - list (filter by state, labels, assignee, milestone, creator, since)\n- `POST \u002Frepos\u002F:owner\u002F:repo\u002Fissues` - create\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fissues\u002F:number` - get\n- `PATCH \u002Frepos\u002F:owner\u002F:repo\u002Fissues\u002F:number` - update (state transitions, events)\n- `PUT\u002FDELETE \u002Frepos\u002F:owner\u002F:repo\u002Fissues\u002F:number\u002Flock` - lock\u002Funlock\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fissues\u002F:number\u002Ftimeline` - timeline events\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fissues\u002F:number\u002Fevents` - events\n- `POST\u002FDELETE \u002Frepos\u002F:owner\u002F:repo\u002Fissues\u002F:number\u002Fassignees` - manage assignees\n\n### Pull Requests\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fpulls` - list (filter by state, head, base)\n- `POST \u002Frepos\u002F:owner\u002F:repo\u002Fpulls` - create\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number` - get\n- `PATCH \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number` - update\n- `PUT \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number\u002Fmerge` - merge (with branch protection enforcement)\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number\u002Fcommits` - list commits\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number\u002Ffiles` - list files\n- `POST\u002FDELETE \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number\u002Frequested_reviewers` - manage reviewers\n- `PUT \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number\u002Fupdate-branch` - update branch\n\n### Comments\n- Issue comments: full CRUD on `\u002Frepos\u002F:owner\u002F:repo\u002Fissues\u002F:number\u002Fcomments`\n- Review comments: full CRUD on `\u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number\u002Fcomments`\n- Commit comments: full CRUD on `\u002Frepos\u002F:owner\u002F:repo\u002Fcommits\u002F:sha\u002Fcomments`\n- Repo-wide listings for each type\n\n### Reviews\n- `GET \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number\u002Freviews` - list\n- `POST \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number\u002Freviews` - create (with inline comments)\n- `GET\u002FPUT \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number\u002Freviews\u002F:id` - get\u002Fupdate\n- `POST \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number\u002Freviews\u002F:id\u002Fevents` - submit\n- `PUT \u002Frepos\u002F:owner\u002F:repo\u002Fpulls\u002F:number\u002Freviews\u002F:id\u002Fdismissals` - dismiss\n\n### Labels & Milestones\n- Labels: full CRUD, add\u002Fremove from issues, replace all\n- Milestones: full CRUD, state transitions, issue counts\n\n### Branches & Git Data\n- Branches: list, get, protection CRUD (status checks, PR reviews, enforce admins)\n- Refs: get, match, create, update, delete\n- Commits: get, create\n- Trees: get (with recursive), create (with inline content)\n- Blobs: get, create\n- Tags: get, create\n\n### Organizations & Teams\n- Orgs: get, update, list\n- Org members: list, check, remove, get\u002Fset membership\n- Teams: full CRUD, members, repos\n\n### Releases\n- Releases: full CRUD, latest, by tag\n- Release assets: full CRUD, upload\n- Generate release notes\n\n### Webhooks\n- Repo webhooks: full CRUD, ping, test, deliveries\n- Org webhooks: full CRUD, ping\n- Real HTTP delivery to registered URLs on all state changes\n\n### Search\n- `GET \u002Fsearch\u002Frepositories` - full query syntax (user, org, language, topic, stars, forks, etc.)\n- `GET \u002Fsearch\u002Fissues` - issues + PRs (repo, is, author, label, milestone, state, etc.)\n- `GET \u002Fsearch\u002Fusers` - users + orgs\n- `GET \u002Fsearch\u002Fcode` - blob content search\n- `GET \u002Fsearch\u002Fcommits` - commit message search\n- `GET \u002Fsearch\u002Ftopics` - topic search\n- `GET \u002Fsearch\u002Flabels` - label search\n\n### Actions\n- Workflows: list, get, enable\u002Fdisable, dispatch\n- Workflow runs: list, get, cancel, rerun, delete, logs\n- Jobs: list, get, logs\n- Artifacts: list, get, delete\n- Secrets: repo + org CRUD\n\n### Checks\n- Check runs: create, update, get, annotations, rerequest, list by ref\u002Fsuite\n- Check suites: create, get, preferences, rerequest, list by ref\n- Automatic suite status rollup from check run results\n\n### Misc\n- `GET \u002Frate_limit` - rate limit status\n- `GET \u002Fmeta` - server metadata\n- `GET \u002Foctocat` - ASCII art\n- `GET \u002Femojis` - emoji URLs\n- `GET \u002Fzen` - random zen phrase\n- `GET \u002Fversions` - API versions\n\n## Google OAuth + Gmail, Calendar, and Drive APIs\n\nOAuth 2.0, OpenID Connect, and mutable Google Workspace-style surfaces for local inbox, calendar, and drive flows.\n\n- `GET \u002Fo\u002Foauth2\u002Fv2\u002Fauth` - authorization endpoint\n- `POST \u002Foauth2\u002Ftoken` - token exchange\n- `GET \u002Foauth2\u002Fv2\u002Fuserinfo` - get user info\n- `GET \u002F.well-known\u002Fopenid-configuration` - OIDC discovery document\n- `GET \u002Foauth2\u002Fv3\u002Fcerts` - JSON Web Key Set (JWKS)\n- `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fmessages` - list messages with `q`, `labelIds`, `maxResults`, and `pageToken`\n- `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fmessages\u002F:id` - fetch a Gmail-style message payload in `full`, `metadata`, `minimal`, or `raw` formats\n- `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fmessages\u002F:messageId\u002Fattachments\u002F:id` - fetch attachment bodies\n- `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fmessages\u002Fsend` - create sent mail from `raw` MIME or structured fields\n- `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fmessages\u002Fimport` - import inbox mail\n- `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fmessages` - insert a message directly\n- `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fmessages\u002F:id\u002Fmodify` - add\u002Fremove labels on one message\n- `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fmessages\u002FbatchModify` - add\u002Fremove labels across many messages\n- `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fmessages\u002F:id\u002Ftrash` and `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fmessages\u002F:id\u002Funtrash`\n- `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fdrafts`, `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fdrafts`, `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fdrafts\u002F:id`, `PUT \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fdrafts\u002F:id`, `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fdrafts\u002F:id\u002Fsend`, `DELETE \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fdrafts\u002F:id`\n- `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fthreads\u002F:id\u002Fmodify` - add\u002Fremove labels across a thread\n- `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fthreads` and `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fthreads\u002F:id`\n- `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Flabels`, `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Flabels`, `PATCH \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Flabels\u002F:id`, `DELETE \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Flabels\u002F:id`\n- `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fhistory`, `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fwatch`, `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fstop`\n- `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fsettings\u002Ffilters`, `POST \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fsettings\u002Ffilters`, `DELETE \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fsettings\u002Ffilters\u002F:id`\n- `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fsettings\u002FforwardingAddresses`, `GET \u002Fgmail\u002Fv1\u002Fusers\u002F:userId\u002Fsettings\u002FsendAs`\n- `GET \u002Fcalendar\u002Fv3\u002Fusers\u002F:userId\u002FcalendarList`, `GET \u002Fcalendar\u002Fv3\u002Fcalendars\u002F:calendarId\u002Fevents`, `POST \u002Fcalendar\u002Fv3\u002Fcalendars\u002F:calendarId\u002Fevents`, `DELETE \u002Fcalendar\u002Fv3\u002Fcalendars\u002F:calendarId\u002Fevents\u002F:eventId`, `POST \u002Fcalendar\u002Fv3\u002FfreeBusy`\n- `GET \u002Fdrive\u002Fv3\u002Ffiles`, `GET \u002Fdrive\u002Fv3\u002Ffiles\u002F:fileId`, `POST \u002Fdrive\u002Fv3\u002Ffiles`, `PATCH \u002Fdrive\u002Fv3\u002Ffiles\u002F:fileId`, `PUT \u002Fdrive\u002Fv3\u002Ffiles\u002F:fileId`, `POST \u002Fupload\u002Fdrive\u002Fv3\u002Ffiles`\n\n## Slack API\n\nFully stateful Slack Web API emulation with channels, messages, threads, reactions, OAuth v2, and incoming webhooks.\n\n### Auth & Chat\n- `POST \u002Fapi\u002Fauth.test` - test authentication\n- `POST \u002Fapi\u002Fchat.postMessage` - post message (supports threads via `thread_ts`)\n- `POST \u002Fapi\u002Fchat.update` - update message\n- `POST \u002Fapi\u002Fchat.delete` - delete message\n- `POST \u002Fapi\u002Fchat.meMessage` - \u002Fme message\n\n### Conversations\n- `POST \u002Fapi\u002Fconversations.list` - list channels (cursor pagination)\n- `POST \u002Fapi\u002Fconversations.info` - get channel info\n- `POST \u002Fapi\u002Fconversations.create` - create channel\n- `POST \u002Fapi\u002Fconversations.history` - channel history\n- `POST \u002Fapi\u002Fconversations.replies` - thread replies\n- `POST \u002Fapi\u002Fconversations.join` \u002F `conversations.leave` - join\u002Fleave\n- `POST \u002Fapi\u002Fconversations.members` - list members\n\n### Users & Reactions\n- `POST \u002Fapi\u002Fusers.list` - list users (cursor pagination)\n- `POST \u002Fapi\u002Fusers.info` - get user info\n- `POST \u002Fapi\u002Fusers.lookupByEmail` - lookup by email\n- `POST \u002Fapi\u002Freactions.add` \u002F `reactions.remove` \u002F `reactions.get` - manage reactions\n\n### Team, Bots & Webhooks\n- `POST \u002Fapi\u002Fteam.info` - workspace info\n- `POST \u002Fapi\u002Fbots.info` - bot info\n- `POST \u002Fservices\u002F:teamId\u002F:botId\u002F:webhookId` - incoming webhook\n\n### OAuth\n- `GET \u002Foauth\u002Fv2\u002Fauthorize` - authorization (shows user picker)\n- `POST \u002Fapi\u002Foauth.v2.access` - token exchange\n\n## Apple Sign In\n\nSign in with Apple emulation with authorization code flow, PKCE support, RS256 ID tokens, and OIDC discovery.\n\n- `GET \u002F.well-known\u002Fopenid-configuration` - OIDC discovery document\n- `GET \u002Fauth\u002Fkeys` - JSON Web Key Set (JWKS)\n- `GET \u002Fauth\u002Fauthorize` - authorization endpoint (shows user picker)\n- `POST \u002Fauth\u002Ftoken` - token exchange (authorization code and refresh token grants)\n- `POST \u002Fauth\u002Frevoke` - token revocation\n\n## Microsoft Entra ID\n\nMicrosoft Entra ID (Azure AD) v2.0 OAuth 2.0 and OpenID Connect emulation with authorization code flow, PKCE, client credentials, RS256 ID tokens, and OIDC discovery.\n\n- `GET \u002F.well-known\u002Fopenid-configuration` - OIDC discovery document\n- `GET \u002F:tenant\u002Fv2.0\u002F.well-known\u002Fopenid-configuration` - tenant-scoped OIDC discovery\n- `GET \u002Fdiscovery\u002Fv2.0\u002Fkeys` - JSON Web Key Set (JWKS)\n- `GET \u002Foauth2\u002Fv2.0\u002Fauthorize` - authorization endpoint (shows user picker)\n- `POST \u002Foauth2\u002Fv2.0\u002Ftoken` - token exchange (authorization code, refresh token, client credentials)\n- `GET \u002Foidc\u002Fuserinfo` - OpenID Connect user info\n- `GET \u002Fv1.0\u002Fme` - Microsoft Graph user profile\n- `GET \u002Foauth2\u002Fv2.0\u002Flogout` - end session \u002F logout\n- `POST \u002Foauth2\u002Fv2.0\u002Frevoke` - token revocation\n\n## AWS\n\nS3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths and query-style SQS\u002FIAM\u002FSTS endpoints. All responses use AWS-compatible XML.\n\n### S3\n\nS3 routes use root paths matching the real AWS S3 wire format, so the official AWS SDK works out of the box with `forcePathStyle: true`. Legacy `\u002Fs3\u002F` prefixed paths are also supported for backward compatibility.\n\n- `GET \u002F` - list all buckets\n- `PUT \u002F:bucket` - create bucket\n- `DELETE \u002F:bucket` - delete bucket\n- `HEAD \u002F:bucket` - check existence\n- `GET \u002F:bucket` - list objects (prefix, delimiter, max-keys, continuation-token, start-after)\n- `POST \u002F:bucket` - presigned POST upload (browser-style multipart form with policy validation)\n- `PUT \u002F:bucket\u002F:key` - put object (supports copy via `x-amz-copy-source`)\n- `GET \u002F:bucket\u002F:key` - get object\n- `HEAD \u002F:bucket\u002F:key` - head object\n- `DELETE \u002F:bucket\u002F:key` - delete object\n\n### SQS\nAll operations via `POST \u002Fsqs\u002F` with `Action` parameter:\n- `CreateQueue`, `ListQueues`, `GetQueueUrl`, `GetQueueAttributes`\n- `SendMessage`, `ReceiveMessage`, `DeleteMessage`\n- `PurgeQueue`, `DeleteQueue`\n\n### IAM\nAll operations via `POST \u002Fiam\u002F` with `Action` parameter:\n- `CreateUser`, `GetUser`, `ListUsers`, `DeleteUser`\n- `CreateAccessKey`, `ListAccessKeys`, `DeleteAccessKey`\n- `CreateRole`, `GetRole`, `ListRoles`, `DeleteRole`\n\n### STS\nAll operations via `POST \u002Fsts\u002F` with `Action` parameter:\n- `GetCallerIdentity`, `AssumeRole`\n\n## Next.js Integration\n\nEmbed emulators directly in your Next.js app so they run on the same origin. This solves the Vercel preview deployment problem where OAuth callback URLs change with every deployment.\n\n### Install\n\n```bash\nnpm install @emulators\u002Fadapter-next @emulators\u002Fgithub @emulators\u002Fgoogle\n```\n\nOnly install the emulators you need. Each `@emulators\u002F*` package is published independently.\n\n### Route handler\n\nCreate a catch-all route that serves emulator traffic:\n\n```typescript\n\u002F\u002F app\u002Femulate\u002F[...path]\u002Froute.ts\nimport { createEmulateHandler } from '@emulators\u002Fadapter-next'\nimport * as github from '@emulators\u002Fgithub'\nimport * as google from '@emulators\u002Fgoogle'\n\nexport const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({\n  services: {\n    github: {\n      emulator: github,\n      seed: {\n        users: [{ login: 'octocat', name: 'The Octocat' }],\n        repos: [{ owner: 'octocat', name: 'hello-world', auto_init: true }],\n      },\n    },\n    google: {\n      emulator: google,\n      seed: {\n        users: [{ email: 'test@example.com', name: 'Test User' }],\n      },\n    },\n  },\n})\n```\n\n### Auth.js \u002F NextAuth configuration\n\nPoint your provider at the emulator paths on the same origin:\n\n```typescript\nimport GitHub from 'next-auth\u002Fproviders\u002Fgithub'\n\nconst baseUrl = process.env.VERCEL_URL\n  ? `https:\u002F\u002F${process.env.VERCEL_URL}`\n  : 'http:\u002F\u002Flocalhost:3000'\n\nGitHub({\n  clientId: 'any-value',\n  clientSecret: 'any-value',\n  authorization: { url: `${baseUrl}\u002Femulate\u002Fgithub\u002Flogin\u002Foauth\u002Fauthorize` },\n  token: { url: `${baseUrl}\u002Femulate\u002Fgithub\u002Flogin\u002Foauth\u002Faccess_token` },\n  userinfo: { url: `${baseUrl}\u002Femulate\u002Fgithub\u002Fuser` },\n})\n```\n\nNo `oauth_apps` need to be seeded. When none are configured, the emulator skips `client_id`, `client_secret`, and `redirect_uri` validation.\n\n### Font files in serverless\n\nEmulator UI pages use bundled fonts. Wrap your Next.js config to include them in the serverless trace:\n\n```typescript\n\u002F\u002F next.config.mjs\nimport { withEmulate } from '@emulators\u002Fadapter-next'\n\nexport default withEmulate({\n  \u002F\u002F your normal Next.js config\n})\n```\n\nIf you mount the catch-all at a custom path, pass the matching prefix:\n\n```typescript\nexport default withEmulate(nextConfig, { routePrefix: '\u002Fapi\u002Femulate' })\n```\n\n### Persistence\n\nBy default, emulator state is in-memory and resets on every cold start. To persist state across restarts, pass a `persistence` adapter:\n\n```typescript\nimport { createEmulateHandler } from '@emulators\u002Fadapter-next'\nimport * as github from '@emulators\u002Fgithub'\n\nconst kvAdapter = {\n  async load() { return await kv.get('emulate-state') },\n  async save(data: string) { await kv.set('emulate-state', data) },\n}\n\nexport const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({\n  services: { github: { emulator: github } },\n  persistence: kvAdapter,\n})\n```\n\nFor local development, `@emulators\u002Fcore` ships `filePersistence`:\n\n```typescript\nimport { filePersistence } from '@emulators\u002Fcore'\n\n\u002F\u002F ...\npersistence: filePersistence('.emulate\u002Fstate.json'),\n```\n\nThe persistence adapter is called on cold start (load) and after every mutating request (save). Saves are serialized via an internal queue to prevent race conditions.\n\n## Architecture\n\n```\npackages\u002F\n  emulate\u002F          # CLI entry point (commander)\n  @emulators\u002F\n    core\u002F           # HTTP server, in-memory store, plugin interface, middleware\n    adapter-next\u002F   # Next.js App Router integration\n    vercel\u002F         # Vercel API service\n    github\u002F         # GitHub API service\n    google\u002F         # Google OAuth 2.0 \u002F OIDC + Gmail, Calendar, Drive\n    slack\u002F          # Slack Web API, OAuth v2, incoming webhooks\n    apple\u002F          # Apple Sign In \u002F OIDC\n    microsoft\u002F      # Microsoft Entra ID OAuth 2.0 \u002F OIDC + Graph \u002Fme\n    aws\u002F            # AWS S3, SQS, IAM, STS\napps\u002F\n  web\u002F              # Documentation site (Next.js)\n```\n\nThe core provides a generic `Store` with typed `Collection\u003CT>` instances supporting CRUD, indexing, filtering, and pagination. Each service plugin registers its routes on the shared Hono app and uses the store for state.\n\n## Auth\n\nTokens are configured in the seed config and map to users. Pass them as `Authorization: Bearer \u003Ctoken>` or `Authorization: token \u003Ctoken>`.\n\n**Vercel**: All endpoints accept `teamId` or `slug` query params for team scoping. Pagination uses cursor-based `limit`\u002F`since`\u002F`until` with `pagination` response objects.\n\n**GitHub**: Public repo endpoints work without auth. Private repos and write operations require a valid token. Pagination uses `page`\u002F`per_page` with `Link` headers.\n\n**Google**: Standard OAuth 2.0 authorization code flow. Configure clients in the seed config.\n\n**Slack**: All Web API endpoints require `Authorization: Bearer \u003Ctoken>`. OAuth v2 flow with user picker UI.\n\n**Apple**: OIDC authorization code flow with RS256 ID tokens. On first auth per user\u002Fclient pair, a `user` JSON blob is included.\n\n**Microsoft**: OIDC authorization code flow with PKCE support. Also supports client credentials grants. Microsoft Graph `\u002Fv1.0\u002Fme` available.\n\n**AWS**: Bearer tokens or IAM access key credentials. Default key pair always seeded: `AKIAIOSFODNN7EXAMPLE` \u002F `wJalrXUtnFEMI\u002FK7MDENG\u002FbPxRfiCYEXAMPLEKEY`.\n","emulate 是一个用于本地模拟 API 的工具，特别适用于持续集成环境和无网络沙箱。它使用 TypeScript 编写，能够完全状态化地仿真生产级别的 API 而不仅仅是简单的 Mock。通过简单的命令行接口，用户可以轻松启动包括 Vercel、GitHub、Google 等多个常用服务的本地替代版本，默认情况下无需额外配置文件即可运行于指定端口上。此外，还支持自定义端口、基于种子配置文件初始化以及与 portless 集成实现安全的 HTTPS 访问等功能，使得 emulate 成为开发者在隔离环境中测试应用程序的理想选择。",2,"2026-06-11 03:51:54","high_star"]