mcpField
V
vercel

vercel

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

关于 vercel

vercel 是面向应用测试的有状态 Vercel REST API 模拟器,提供 Vercel 风格的 JSON 响应和游标分页。它涵盖项目、部署、域名、环境变量、Blob、API 密钥及 OAuth 等接口。

主要功能

  1. 01模拟 Vercel REST API
  2. 02提供游标分页
  3. 03支持项目、部署和域名管理
  4. 04模拟环境变量与 Blob API
  5. 05支持团队作用域和 OAuth

适用场景

  1. 01测试 Vercel API 客户端
  2. 02验证项目创建与部署流程
  3. 03测试 Blob 文件操作
  4. 04检查 OAuth 与团队作用域请求

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vercel-labs/emulate --skill vercel

安装后在 Claude Code 中输入/vercel确认已加载。

详细介绍

Vercel API 模拟器

全面支持状态管理的 Vercel REST API 模拟,采用 Vercel 风格的 JSON 响应和基于游标的分页。

启动

本地监听器默认绑定到 127.0.0.1。若要从容器或其他机器访问,请向 CLI 传入 --host 0.0.0.0,或向 createEmulator 传入 hostname: '0.0.0.0'。配置 --base-url 或 baseUrl,以设置这些客户端可访问的对外公布 URL。

# Vercel only
npx emulate --service vercel

# Default port
# http://localhost:4000

也可以通过编程方式启动:

import { createEmulator } from 'emulate'

const vercel = await createEmulator({ service: 'vercel', port: 4000 })
// vercel.url === 'http://localhost:4000'

身份验证

通过 Authorization: Bearer <token> 传入令牌。所有端点都接受 teamId 或 slug 查询参数,用于指定团队范围。

curl http://localhost:4000/v2/user \
  -H "Authorization: Bearer test_token_admin"

团队范围的请求会根据 teamId 或 slug 查询参数解析账户。用户范围的请求会根据经过身份验证的用户解析账户。

将应用指向模拟器

环境变量
VERCEL_EMULATOR_URL=http://localhost:4000
Vercel SDK / 自定义 Fetch
const VERCEL_API = process.env.VERCEL_EMULATOR_URL ?? 'https://api.vercel.com'

const res = await fetch(`${VERCEL_API}/v10/projects`, {
  headers: { Authorization: `Bearer ${token}` },
})
OAuth URL 映射
真实 Vercel URL模拟器 URL
https://vercel.com/integrations/oauth/authorize$VERCEL_EMULATOR_URL/oauth/authorize
https://api.vercel.com/login/oauth/token$VERCEL_EMULATOR_URL/login/oauth/token
https://api.vercel.com/login/oauth/userinfo$VERCEL_EMULATOR_URL/login/oauth/userinfo
Auth.js / NextAuth.js
{
  id: 'vercel',
  name: 'Vercel',
  type: 'oauth',
  authorization: {
    url: `${process.env.VERCEL_EMULATOR_URL}/oauth/authorize`,
  },
  token: {
    url: `${process.env.VERCEL_EMULATOR_URL}/login/oauth/token`,
  },
  userinfo: {
    url: `${process.env.VERCEL_EMULATOR_URL}/login/oauth/userinfo`,
  },
  clientId: process.env.VERCEL_CLIENT_ID,
  clientSecret: process.env.VERCEL_CLIENT_SECRET,
  profile(profile) {
    return {
      id: profile.sub,
      name: profile.name,
      email: profile.email,
      image: profile.picture,
    }
  },
}

Seed 配置

tokens:
  test_token_admin:
    login: admin
    scopes: []

vercel:
  users:
    - username: developer
      name: Developer
      email: [email protected]
  teams:
    - slug: my-team
      name: My Team
      description: Engineering team
  projects:
    - name: my-app
      team: my-team
      framework: nextjs
      buildCommand: next build
      outputDirectory: .next
      rootDirectory: null
      nodeVersion: "20.x"
      envVars:
        - key: DATABASE_URL
          value: postgres://localhost/mydb
          type: encrypted
          target: [production, preview]
  integrations:
    - client_id: oac_abc123
      client_secret: secret_abc123
      name: My Vercel App
      redirect_uris:
        - http://localhost:3000/api/auth/callback/vercel

分页

使用 limit、since 和 until 查询参数进行基于游标的分页。响应包含一个 pagination 对象:

curl "http://localhost:4000/v10/projects?limit=10" \
  -H "Authorization: Bearer $TOKEN"

API 端点

用户与团队
# Registration check
curl http://localhost:4000/registration

# Authenticated user
curl http://localhost:4000/v2/user -H "Authorization: Bearer $TOKEN"

# Update user
curl -X PATCH http://localhost:4000/v2/user \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "New Name", "email": "[email protected]"}'

# List teams (cursor paginated)
curl http://localhost:4000/v2/teams -H "Authorization: Bearer $TOKEN"

# Get team (by ID or slug)
curl http://localhost:4000/v2/teams/my-team -H "Authorization: Bearer $TOKEN"

# Create team
curl -X POST http://localhost:4000/v2/teams \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"slug": "new-team", "name": "New Team"}'

# Update team (name, slug, description)
curl -X PATCH http://localhost:4000/v2/teams/my-team \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Updated Team", "description": "New description"}'

# List members
curl http://localhost:4000/v2/teams/my-team/members -H "Authorization: Bearer $TOKEN"

# Add member (by uid or email, with role)
curl -X POST "http://localhost:4000/v2/teams/team_id/members" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "role": "MEMBER"}'

角色:OWNER、MEMBER、DEVELOPER、VIEWER。

项目
# Create project (with optional env vars, git, and build config)
curl -X POST http://localhost:4000/v11/projects \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app", "framework": "nextjs", "buildCommand": "next build", "outputDirectory": ".next", "nodeVersion": "20.x", "environmentVariables": [{"key": "API_KEY", "value": "secret", "type": "encrypted", "target": ["production"]}]}'

# List projects (search, cursor pagination)
curl "http://localhost:4000/v10/projects?search=my-app" \
  -H "Authorization: Bearer $TOKEN"

# Get project (includes env vars)
curl http://localhost:4000/v9/projects/my-app \
  -H "Authorization: Bearer $TOKEN"

# Update project (framework, buildCommand, devCommand, installCommand,
#   outputDirectory, rootDirectory, nodeVersion, serverlessFunctionRegion,
#   publicSource, autoAssignCustomDomains, gitForkProtection,
#   commandForIgnoringBuildStep)
curl -X PATCH http://localhost:4000/v9/projects/my-app \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"framework": "remix"}'

# Delete project (cascades deployments, domains, env vars, protection bypasses)
curl -X DELETE http://localhost:4000/v9/projects/my-app \
  -H "Authorization: Bearer $TOKEN"

# Promote aliases status
curl http://localhost:4000/v1/projects/my-app/promote/aliases \
  -H "Authorization: Bearer $TOKEN"

# Protection bypass: generate, revoke, regenerate
curl -X PATCH http://localhost:4000/v1/projects/my-app/protection-bypass \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"generate": {"note": "CI preview", "scope": "deployment"}}'

# Revoke protection bypass secrets
curl -X PATCH http://localhost:4000/v1/projects/my-app/protection-bypass \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"revoke": ["secret_to_revoke"]}'

# Regenerate protection bypass secrets
curl -X PATCH http://localhost:4000/v1/projects/my-app/protection-bypass \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"regenerate": ["old_secret"]}'
部署
# Create deployment (auto-transitions to READY)
curl -X POST http://localhost:4000/v13/deployments \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app", "target": "production", "meta": {"commit": "abc123"}, "regions": ["iad1"], "gitSource": {"type": "github", "ref": "main", "sha": "abc123", "repoId": "123", "org": "my-org", "repo": "my-app", "message": "Deploy", "authorName": "dev", "commitAuthorName": "dev"}}'

# Targets: "production", "preview", "staging"

# Get deployment (by ID or URL)
curl http://localhost:4000/v13/deployments/dpl_abc123 \
  -H "Authorization: Bearer $TOKEN"

# List deployments (filter by projectId, app, target, state; cursor paginated)
curl "http://localhost:4000/v6/deployments?projectId=my-app&target=production&limit=10" \
  -H "Authorization: Bearer $TOKEN"

# List deployments (filter by commit SHA)
curl "http://localhost:4000/v7/deployments?sha=abc123" \
  -H "Authorization: Bearer $TOKEN"

# Delete deployment
curl -X DELETE http://localhost:4000/v13/deployments/dpl_abc123 \
  -H "Authorization: Bearer $TOKEN"

# Cancel building deployment
curl -X PATCH http://localhost:4000/v12/deployments/dpl_abc123/cancel \
  -H "Authorization: Bearer $TOKEN"

# List deployment aliases
curl http://localhost:4000/v2/deployments/dpl_abc123/aliases \
  -H "Authorization: Bearer $TOKEN"

# Get build events/logs (supports direction, limit)
curl "http://localhost:4000/v3/deployments/dpl_abc123/events?direction=forward&limit=50" \
  -H "Authorization: Bearer $TOKEN"

# List deployment files
curl http://localhost:4000/v6/deployments/dpl_abc123/files \
  -H "Authorization: Bearer $TOKEN"

# Upload file (by SHA digest)
curl -X POST http://localhost:4000/v2/files \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/octet-stream" \
  -H "x-vercel-digest: sha256hash" \
  --data-binary @file.txt
域名
# Add domain (with optional redirect, gitBranch, customEnvironmentId)
curl -X POST http://localhost:4000/v10/projects/my-app/domains \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "example.com", "redirect": null, "redirectStatusCode": null, "gitBranch": null}'

# *.vercel.app domains are auto-verified

# List domains (cursor paginated)
curl http://localhost:4000/v9/projects/my-app/domains \
  -H "Authorization: Bearer $TOKEN"

# Get, update, remove domain
curl http://localhost:4000/v9/projects/my-app/domains/example.com \
  -H "Authorization: Bearer $TOKEN"

# Verify domain
curl -X POST http://localhost:4000/v9/projects/my-app/domains/example.com/verify \
  -H "Authorization: Bearer $TOKEN"

重定向状态码:301、302、307、308。

环境变量
# List env vars (with decrypt option; filter by gitBranch, customEnvironmentId)
curl "http://localhost:4000/v10/projects/my-app/env?decrypt=true" \
  -H "Authorization: Bearer $TOKEN"

# Create env vars (single, batch, or upsert)
curl -X POST "http://localhost:4000/v10/projects/my-app/env?upsert=true" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key": "API_KEY", "value": "secret123", "type": "encrypted", "target": ["production", "preview"], "comment": "API key for service"}'

# Get env var
curl http://localhost:4000/v10/projects/my-app/env/env_abc123 \
  -H "Authorization: Bearer $TOKEN"

# Update env var
curl -X PATCH http://localhost:4000/v9/projects/my-app/env/env_abc123 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": "newsecret"}'

# Delete env var
curl -X DELETE http://localhost:4000/v9/projects/my-app/env/env_abc123 \
  -H "Authorization: Bearer $TOKEN"

环境变量类型:system、encrypted、plain、secret、sensitive。

Blob

实现 @vercel/blob SDK 使用的 Vercel Blob API(put、head、list、del)。设置两个环境变量,即可将 SDK 指向模拟器:

VERCEL_BLOB_API_URL=http://localhost:4000/api/blob
BLOB_READ_WRITE_TOKEN=vercel_blob_rw_mystore_secret

接受任何形式为 vercel_blob_rw_<storeId>_<secret> 的令牌;存储 ID 会从令牌中解析。

import { put, head, list, del } from '@vercel/blob'

const blob = await put('avatars/user.png', data, { access: 'public' })
// blob.url serves the bytes from the emulator
await head(blob.url)
await list({ prefix: 'avatars/' })
await del(blob.url)

直接使用 HTTP:

BLOB_TOKEN="vercel_blob_rw_mystore_secret"

# Upload (honors x-add-random-suffix, x-allow-overwrite, x-content-type,
#   x-cache-control-max-age, x-if-match headers)
curl -X PUT "http://localhost:4000/api/blob?pathname=docs/readme.txt" \
  -H "Authorization: Bearer $BLOB_TOKEN" \
  --data-binary @readme.txt

# Copy (x-if-match is compared with the source blob's ETag;
#   an existing destination is always replaced)
curl -X PUT "http://localhost:4000/api/blob?pathname=docs/copy.txt&fromUrl=docs/readme.txt" \
  -H "Authorization: Bearer $BLOB_TOKEN"

# Metadata (head)
curl "http://localhost:4000/api/blob?url=docs/readme.txt" \
  -H "Authorization: Bearer $BLOB_TOKEN"

# List (prefix, limit, cursor, mode=folded; folded pages share one
#   sorted cursor across folders and root blobs)
curl "http://localhost:4000/api/blob?prefix=docs/" \
  -H "Authorization: Bearer $BLOB_TOKEN"

# Delete
curl -X POST http://localhost:4000/api/blob/delete \
  -H "Authorization: Bearer $BLOB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["docs/readme.txt"]}'

# Start a multipart upload (returns { key, uploadId })
curl -X POST "http://localhost:4000/api/blob/mpu?pathname=docs/large.bin" \
  -H "Authorization: Bearer $BLOB_TOKEN" \
  -H "x-mpu-action: create"

# Serve content (public, no auth; ?download=1 forces attachment)
curl http://localhost:4000/blob/mystore/docs/readme.txt

上传和删除规则:

  • 路径名前导的单个斜杠会被移除,因此 /docs/readme.txt 会存储为 docs/readme.txt。与真实服务一样,HTTP 请求中的 // 仍可使用,因此 //a.txt 会存储为 /a.txt(@vercel/blob SDK 会在发送请求前拒绝 //)。此规则同样适用于复制目标和分段上传。
  • 内容类型依次取自 x-content-type、已知类型的路径名扩展名、请求的 Content-Type 标头,最后是 application/octet-stream。
  • x-cache-control-max-age 最小值为 60 秒(0 会存储为 public, max-age=60)。未提供或值无效时,默认值为 30 天。
  • 使用缺失或为空的 urls 数组进行删除时,会返回 400 Missing urls in request body。JSON 格式错误时会返回 400 Cannot delete more than 1,000 urls at once or body is malformed;urls 超过 1,000 个时会返回 400 Cannot delete more than 1,000 urls at once;包含任何非字符串条目时会返回 400 Some urls are malformed。以上情况均不会删除任何内容。

可以创建分段上传,但目前不支持上传分段和完成上传,这些操作会返回 400。目前不支持客户端(浏览器)上传。

API 密钥
# Create API key (optional teamId scope)
curl -X POST "http://localhost:4000/v1/api-keys?teamId=team_abc123" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "CI Deploy Key"}'

# List API keys (optional teamId filter)
curl "http://localhost:4000/v1/api-keys?teamId=team_abc123" \
  -H "Authorization: Bearer $TOKEN"

# Delete API key
curl -X DELETE http://localhost:4000/v1/api-keys/ak_abc123 \
  -H "Authorization: Bearer $TOKEN"

创建的 API 密钥会自动注册到令牌映射中,可作为 Bearer 令牌用于所有端点。

OAuth / 集成
# Authorize (browser flow, shows user picker)
# GET /oauth/authorize?client_id=...&redirect_uri=...&scope=...&state=...

# Token exchange (supports PKCE; accepts JSON or form-urlencoded)
curl -X POST http://localhost:4000/login/oauth/token \
  -H "Content-Type: application/json" \
  -d '{"client_id": "oac_abc123", "client_secret": "secret_abc123", "code": "<code>", "redirect_uri": "http://localhost:3000/api/auth/callback/vercel"}'

# User info (returns sub, email, email_verified, name, preferred_username, picture)
curl http://localhost:4000/login/oauth/userinfo \
  -H "Authorization: Bearer $TOKEN"

常见模式

创建项目并部署
TOKEN="test_token_admin"
BASE="http://localhost:4000"

# Create project
curl -X POST $BASE/v11/projects \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app", "framework": "nextjs"}'

# Add env var
curl -X POST $BASE/v10/projects/my-app/env \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key": "DATABASE_URL", "value": "postgres://...", "type": "encrypted", "target": ["production"]}'

# Create deployment
curl -X POST $BASE/v13/deployments \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app", "target": "production"}'
OAuth 集成流程
  1. 将用户重定向到 $VERCEL_EMULATOR_URL/oauth/authorize?client_id=...&redirect_uri=...&state=...
  2. 用户在模拟器的界面中选择一个预置用户
  3. 模拟器重定向回来,并附带 ?code=...&state=...
  4. 通过 POST /login/oauth/token 交换代码以获取令牌
  5. 通过 GET /login/oauth/userinfo 获取用户信息

支持 PKCE。在授权请求中传递 code_challenge 和 code_challenge_method,然后在令牌交换时传递 code_verifier。

团队范围的请求

所有端点都接受 teamId 或 slug 查询参数:

curl "http://localhost:4000/v10/projects?teamId=team_abc123" \
  -H "Authorization: Bearer $TOKEN"

curl "http://localhost:4000/v10/projects?slug=my-team" \
  -H "Authorization: Bearer $TOKEN"

与内置模拟器一起使用自定义模拟器

使用 npx emulate init --custom inventory 搭建第三方 API 模拟器并进行测试。使用 emulate 中的 defineConfig 在 emulate.config.ts 中注册它,也可以同时注册 { emulator: "vercel" } 等内置条目。运行 npx emulate start --watch 可重新加载导入项,并在打印出的 /_emulate URL 中检查自定义状态。重新加载成功后,会将运行重置为种子数据。现有的扁平种子配置仍然有效;--config 用于选择指定文件。有关编写和测试第三方 API 模拟器的信息,请参阅 https://emulate.dev/docs/custom-emulators。

评论

支持 Markdown