mcpField
V
vercel-deploy

vercel-deploy

github.com/fellowship-dev云与 DevOps3170 次安装2 周前更新
代码仓库

关于 vercel-deploy

这项分阶段技能从本地目录将现有 Vercel 项目部署到生产环境。它要求明确提供项目参数并逐步验证,遇到失败即停止,不会创建项目或依赖隐式状态。

主要功能

  1. 01要求明确提供目录和项目参数
  2. 02通过 Vercel API 检查项目关联
  3. 03使用 CLI 以非交互方式部署
  4. 04轮询部署直至就绪或报错
  5. 05验证生产域名的响应

适用场景

  1. 01从本地检出目录部署现有项目
  2. 02根据配置的域名验证生产部署

安装

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

$npx skills add https://github.com/fellowship-dev/dogfooded-skills --skill vercel-deploy

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

详细介绍

用途

从本地目录将现有的 Vercel 项目部署到生产环境。设计目标是绝不挂起、绝不创建新项目,也绝不假设存在隐式状态。

必需的密钥

必须以环境变量的形式提供(来自 crew 密钥存储):

  • VERCEL_TOKEN — 具有部署权限的访问令牌(这是唯一真正的密钥)

必需的输入

所有输入都以 KEY=value 键值对的形式传入技能调用或任务说明中。请从参数中解析这些值——绝不要从环境变量中读取。

  • DEPLOY_DIR — 可部署目录的路径(例如,./ui、仓库根目录)
  • PROD_DOMAIN — 预期的生产域名(例如,pylot.fellowship.dev)
  • VERCEL_ORG_ID — 组织/团队 ID(以 team_ 开头)
  • VERCEL_PROJECT_ID — 项目 ID(以 prj_ 开头)

这些值来自仓库操作手册(GET /admin/playbooks/<repo>),而非密钥。操作人员读取操作手册,并在调用此技能时传入这些值。只能通过此技能的 CLI 流程进行部署——绝不要依赖 Vercel 的 Git 自动部署集成;它会绕过上述来自操作手册的输入,以及“禁止的操作”中的检查。

可选的输入

  • DEPLOY_BRANCH — 要部署的 Git 分支。未指定时默认为 main。部署前始终会检出并拉取该分支。
  • DEPLOY_AUTHOR — 已验证的 Vercel 团队成员的 Git 名称(例如,maxfindel)。如果与 DEPLOY_EMAIL 一起提供,阶段 00 会在部署前修正提交作者。仅当 Vercel 强制要求提交作者属于团队时才需要。
  • DEPLOY_EMAIL — 与作者匹配的 Git 邮箱。设置 DEPLOY_AUTHOR 时必须提供。

禁止的操作

  1. 绝不要创建新的 Vercel 项目。 如果项目不存在,请停止并报告失败。
  2. 绝不要假设项目会自动关联。 始终验证 .vercel/project.json 是否存在,或直接写入该文件。
  3. 绝不要将 Git 仓库连接到 Vercel 项目。 所有部署都通过 CLI 推送,而非由 Git 触发。
  4. 绝不要在未带 --token 和 --yes 的情况下运行 vercel。 缺少这些参数时,CLI 会永久挂起。
  5. 绝不要运行 vercel link。 它会进行交互式提示。请直接写入 .vercel/project.json。
  6. 绝不要运行 vercel project create 或等效命令。 创建项目有单独的流程。

阶段概览

阶段名称用途
00author-fix如果设置了 DEPLOY_AUTHOR,则修正提交作者(可选,否则跳过)
01preflight验证密钥和工具,并计算部署上下文
02link确保 .vercel/project.json 存在;通过 API 验证项目
03deploy以非交互方式运行 vercel deploy --prod
04poll轮询 Vercel API,直到部署状态变为 READY 或 ERROR
05verify确认生产域名可访问,并输出结果

阶段 00 — 修正作者(有条件执行)

如果未提供 DEPLOY_AUTHOR,请跳过此阶段。 并非所有 Vercel 项目都会强制要求提交作者属于团队。

当 Vercel 强制执行此要求时(TEAM_ACCESS_REQUIRED / seatBlock),机器人账号(例如,fry-lobster)会阻止 CLI 部署。解决方法是创建一个由团队成员作为作者的空提交。

# Only run if DEPLOY_AUTHOR is set in the skill params
if [ -n "$DEPLOY_AUTHOR" ] && [ -n "$DEPLOY_EMAIL" ]; then
  HEAD_AUTHOR=$(git log -1 --format='%an')
  if [ "$HEAD_AUTHOR" != "$DEPLOY_AUTHOR" ]; then
    echo "[vercel-deploy] HEAD author '$HEAD_AUTHOR' is not a Vercel team member."
    echo "[vercel-deploy] Creating empty commit with author '$DEPLOY_AUTHOR' to unblock deploy."
    git commit --allow-empty \
      --author="$DEPLOY_AUTHOR <$DEPLOY_EMAIL>" \
      -m "chore: vercel deploy author fix (empty commit)"
    git push origin HEAD
  fi
else
  echo "[vercel-deploy] Stage 00 skipped — no DEPLOY_AUTHOR configured"
fi

阶段 01 — 预检

验证所有密钥和输入均已提供、工具均已安装,并检出部署分支。

# Verify required secret (from crew secret store — the only real secret)
[ -z "$VERCEL_TOKEN" ] && { echo "[vercel-deploy] MISSING secret: VERCEL_TOKEN"; exit 1; }

# Verify required inputs (parsed from skill args / task description — from the repo playbook)
[ -z "$DEPLOY_DIR" ]        && { echo "[vercel-deploy] MISSING input: DEPLOY_DIR — check the repo playbook"; exit 1; }
[ -z "$PROD_DOMAIN" ]       && { echo "[vercel-deploy] MISSING input: PROD_DOMAIN — check the repo playbook"; exit 1; }
[ -z "$VERCEL_ORG_ID" ]     && { echo "[vercel-deploy] MISSING input: VERCEL_ORG_ID — check the repo playbook"; exit 1; }
[ -z "$VERCEL_PROJECT_ID" ] && { echo "[vercel-deploy] MISSING input: VERCEL_PROJECT_ID — check the repo playbook"; exit 1; }

# Optional: warn if author-fix params are incomplete (both or neither)
if [ -n "$DEPLOY_AUTHOR" ] && [ -z "$DEPLOY_EMAIL" ]; then
  echo "[vercel-deploy] WARNING: DEPLOY_AUTHOR set but DEPLOY_EMAIL missing — Stage 00 will be skipped"
fi

# Verify vercel CLI is available
command -v vercel >/dev/null 2>&1 || command -v npx >/dev/null 2>&1 || {
  echo "[vercel-deploy] vercel CLI not available — install with: npm i -g vercel"
  exit 1
}

# Verify deploy directory exists
[ -d "$DEPLOY_DIR" ] || { echo "[vercel-deploy] DEPLOY_DIR not found: $DEPLOY_DIR"; exit 1; }

# Checkout and pull deploy branch
DEPLOY_BRANCH="${DEPLOY_BRANCH:-main}"
git checkout "$DEPLOY_BRANCH" && git pull origin "$DEPLOY_BRANCH" || {
  echo "[vercel-deploy] Failed to checkout/pull $DEPLOY_BRANCH"
  exit 1
}

# Save state for downstream stages
cat > /tmp/vercel-deploy-ctx.env <<EOF
DEPLOY_BRANCH=$DEPLOY_BRANCH
DEPLOY_DIR=$DEPLOY_DIR
PROD_DOMAIN=$PROD_DOMAIN
EOF

echo "[vercel-deploy] Stage 01 complete — preflight passed, branch: $DEPLOY_BRANCH"

阶段 02 — 关联

直接写入 .vercel/project.json(绝不要使用 vercel link),然后通过 API 验证项目是否存在。

source /tmp/vercel-deploy-ctx.env

# Write project link file directly
mkdir -p "$DEPLOY_DIR/.vercel"
cat > "$DEPLOY_DIR/.vercel/project.json" <<EOF
{
  "orgId": "$VERCEL_ORG_ID",
  "projectId": "$VERCEL_PROJECT_ID"
}
EOF

# Verify project exists via Vercel API
PROJECT_JSON=$(curl -sf \
  -H "Authorization: Bearer $VERCEL_TOKEN" \
  "https://api.vercel.com/v9/projects/$VERCEL_PROJECT_ID?teamId=$VERCEL_ORG_ID")

[ -z "$PROJECT_JSON" ] && {
  echo "[vercel-deploy] Stage 02 failed: project $VERCEL_PROJECT_ID not found — STOP, do not create"
  exit 1
}

PROJECT_NAME=$(echo "$PROJECT_JSON" | python3 -c \
  "import sys,json; d=json.load(sys.stdin); print(d.get('name','unknown'))" 2>/dev/null || echo "unknown")

echo "PROJECT_NAME=$PROJECT_NAME" >> /tmp/vercel-deploy-ctx.env
echo "[vercel-deploy] Stage 02 complete — linked to project '$PROJECT_NAME' ($VERCEL_PROJECT_ID)"

阶段 03 — 部署

以非交互方式运行 vercel deploy --prod。必须使用 --token 和 --yes——缺少这些参数时,CLI 会挂起。

source /tmp/vercel-deploy-ctx.env

cd "$DEPLOY_DIR"
DEPLOY_OUTPUT=$(npx vercel deploy --prod --token="$VERCEL_TOKEN" --yes 2>&1)
DEPLOY_EXIT=$?

echo "$DEPLOY_OUTPUT"

if [ $DEPLOY_EXIT -ne 0 ]; then
  echo "[vercel-deploy] Stage 03 failed: vercel deploy exited $DEPLOY_EXIT"
  exit 1
fi

# Extract deployment URL (last https:// line in output)
DEPLOY_URL=$(echo "$DEPLOY_OUTPUT" | grep -E '^https://' | tail -1)
[ -z "$DEPLOY_URL" ] && {
  echo "[vercel-deploy] Stage 03 failed: no deployment URL found in vercel output"
  exit 1
}

echo "DEPLOY_URL=$DEPLOY_URL" >> /tmp/vercel-deploy-ctx.env
echo "[vercel-deploy] Stage 03 complete — deployment URL: $DEPLOY_URL"

阶段 04 — 轮询

轮询 Vercel API,直到部署状态变为 READY 或 ERROR。120 秒后超时。

source /tmp/vercel-deploy-ctx.env

MAX_WAIT=120
ELAPSED=0
DEPLOY_HOST=$(echo "$DEPLOY_URL" | sed 's|https://||')

while [ $ELAPSED -lt $MAX_WAIT ]; do
  DEPLOY_STATE=$(curl -sf \
    -H "Authorization: Bearer $VERCEL_TOKEN" \
    "https://api.vercel.com/v13/deployments?url=${DEPLOY_HOST}&teamId=$VERCEL_ORG_ID&limit=1" \
    | python3 -c \
      "import sys,json; d=json.load(sys.stdin); deps=d.get('deployments',[]); print(deps[0].get('state','UNKNOWN') if deps else 'NOT_FOUND')" \
      2>/dev/null || echo "API_ERROR")

  echo "[vercel-deploy] Deploy state: $DEPLOY_STATE (${ELAPSED}s elapsed)"

  case "$DEPLOY_STATE" in
    READY)
      echo "[vercel-deploy] Stage 04 complete — deployment READY"
      break
      ;;
    ERROR|CANCELED)
      echo "[vercel-deploy] Stage 04 failed: deployment reached terminal state $DEPLOY_STATE"
      exit 1
      ;;
    *)
      sleep 10
      ELAPSED=$((ELAPSED + 10))
      ;;
  esac
done

[ $ELAPSED -ge $MAX_WAIT ] && {
  echo "[vercel-deploy] Stage 04 failed: timed out after ${MAX_WAIT}s — last state: $DEPLOY_STATE"
  exit 1
}

阶段 05 — 验证

确认生产域名可访问(HTTP 200、301 或 302),然后输出结果。

source /tmp/vercel-deploy-ctx.env

VERIFY_URL="https://$PROD_DOMAIN"
HTTP_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" --max-time 15 "$VERIFY_URL" 2>/dev/null || echo "000")

echo "[vercel-deploy] $VERIFY_URL → HTTP $HTTP_STATUS"

case "$HTTP_STATUS" in
  200|301|302)
    echo "[vercel-deploy] Stage 05 complete — production domain verified"
    ;;
  *)
    echo "[vercel-deploy] Stage 05 failed: $VERIFY_URL returned HTTP $HTTP_STATUS"
    exit 1
    ;;
esac

验证命令执行后,将匹配的已解析标记作为助手最后一整行输出(不能通过 Bash 输出,也不能放在代码围栏中):

  • 已验证:[pylot:$PYLOT_OUTCOME_NONCE] outcome="vercel-deploy succeeded" project=$PROJECT_NAME domain=$PROD_DOMAIN deploy_url=$DEPLOY_URL status=success
  • 失败:[pylot:$PYLOT_OUTCOME_NONCE] outcome="vercel-deploy failed at stage 05: $PROD_DOMAIN returned HTTP $HTTP_STATUS" status=failed

执行模型

  • 按顺序执行各阶段。只有当前阶段的检查点通过后,才能继续。
  • 不得跳过阶段。 每个检查点都必须经过验证。
  • 快速失败。 如果任何检查点失败,立即停止并报告 status=failed。
  • 各阶段之间通过 /tmp/vercel-deploy-ctx.env 传递状态。

关键规则

  1. 仅按顺序执行。第 N 阶段的检查点通过后,才能开始第 N+1 阶段。
  2. 不得跳过阶段。必须运行每个阶段,并验证每个检查点。
  3. 快速失败。如果任何检查点失败,立即停止并报告。
  4. 每次调用 vercel 或 npx vercel 时,都必须包含 --token="$VERCEL_TOKEN" --yes。

错误处理

如果任何阶段失败:

  1. 输出失败的阶段及确切错误
  2. 如果有 Vercel API 响应正文,则将其包含在内
  3. 输出:[pylot:$PYLOT_OUTCOME_NONCE] outcome="vercel-deploy failed at stage <NN>: <reason>" status=failed
  4. 不得自动重试

结果

成功时,输出:

[pylot:$PYLOT_OUTCOME_NONCE] outcome="vercel-deploy succeeded" project=$PROJECT_NAME domain=$PROD_DOMAIN deploy_url=$DEPLOY_URL status=success

评论

支持 Markdown