jt-release
自动将 main 分支合并到 release 分支并发布.
声明: "正在使用 jt-release skill 执行发布流程."
流程
1. 前置检查
依次检查, 任一失败则报告并退出:
# 1.1 当前分支
current_branch=$(git branch --show-current)
if [ "$current_branch" != "main" ]; then
echo "ERROR: 当前不在 main 分支, 请先切换到 main"
exit 1
fi
# 1.2 工作区干净
if [ -n "$(git status --porcelain --ignore-submodules)" ]; then
echo "ERROR: 工作区有未提交变更, 请先处理"
exit 1
fi
# 1.3 release 分支存在
git fetch origin
if ! git rev-parse --verify release >/dev/null 2>&1 && ! git rev-parse --verify origin/release >/dev/null 2>&1; then
echo "ERROR: release 分支不存在, 请先创建"
exit 1
fi
# 1.4 远端 main 不领先于本地
local_main=$(git rev-parse main)
remote_main=$(git rev-parse origin/main)
if [ "$(git merge-base "$local_main" "$remote_main")" != "$remote_main" ]; then
echo "ERROR: 远端 main 有新提交, 请先 pull/rebase"
exit 1
fi
如果本地没有 release 分支但远端有, 先 checkout:
if ! git rev-parse --verify release >/dev/null 2>&1; then
git checkout -b release origin/release
git checkout main
fi
1.5 本地全仓 lint 门禁 (push 前提前抓 CI Lint gate 的错):
CI 的 Lint gate 跑全仓, 但 main push 不触发它 —— 多个 commit 累积的 lint 错会一直憋到 release 发布时 CI 才爆. 发布前先在本地跑一次全仓 lint, 几秒就能提前抓到, 避免推上去白等一轮 CI.
# 仅 Node/pnpm 项目 (package.json 含 check 脚本) 适用; 其它技术栈按需替换 lint 命令
if [ -f package.json ] && grep -q '"check"' package.json; then
# 坑: 仓库内嵌套 worktree (.worktrees/ 或 worktrees/ 下) 各自的 biome.jsonc
# 会让全仓 lint 因 nested root config 整体 bail —— 这是配置冲突, 不是真 lint 错,
# 不能误判成 lint 失败而阻断发布. 只精确识别"嵌套在仓库内的 worktree".
repo_root=$(git rev-parse --show-toplevel)
nested_wt=$(git worktree list --porcelain | awk '/^worktree /{print $2}' \
| grep -F "$repo_root/" || true)
if [ -n "$nested_wt" ]; then
echo "WARN: 检测到仓库内嵌套 worktree, 本地全仓 lint 可能因 nested root config 整体 bail (非真 lint 错):"
echo "$nested_wt"
echo "已降级为 warn, 不硬阻断. 建议清理 worktree 后重跑, 或人工确认 lint 状态后再发布."
else
echo "运行本地全仓 lint (pnpm check)..."
if ! pnpm check; then
echo "ERROR: 本地全仓 lint 失败, 请修复上述错误后再发布 (避免推上去等 CI 才发现)"
exit 1
fi
fi
fi
2. 版本号检测与 bump
按优先级在项目根目录检测版本文件, 找到第一个即停止:
| 优先级 | 文件 | 读取方式 |
|---|---|---|
| 1 | package.json | JSON "version" 字段 |
| 2 | Cargo.toml | version = "X.Y.Z" |
| 3 | pyproject.toml | version = "X.Y.Z" |
未找到版本文件: 报告 "未找到版本文件, 跳过版本号 bump", 继续合并流程.
对比版本号:
用 git show release:<version-file> 读取 release 分支上的版本号. 如果 release 分支没有该文件 (首次发布), 跳过 bump.
- 版本号相同 → patch +1 (如
1.2.3→1.2.4), 修改版本文件, commit:git add <version-file> git commit -m "chore: bump version to <new-version>" - 版本号不同 → 跳过 bump, 报告当前版本号
版本号修改方式:
package.json: 用 Read 工具读取, 用 Edit 工具修改"version"字段Cargo.toml: 用 Edit 工具修改version = "X.Y.Z"行pyproject.toml: 用 Edit 工具修改version = "X.Y.Z"行
3. 合并 main → release
git checkout release
git merge --ff-only main
如果 ff-only 失败:
echo "ERROR: 无法 fast-forward 合并, release 分支有独立提交, 请手动处理"
git checkout main
exit 1
4. Push 并切回 main
git push origin release
git checkout main
如果步骤 2 执行了版本号 bump, 也 push main:
git push origin main
5. CI 监控
记录 push 时间, 然后轮询 CI 状态:
gh run list --branch release --limit 10 --json databaseId,name,status,conclusion,createdAt,url
监控逻辑:
- 过滤出 push 之后创建的 workflow run
- 每 30 秒轮询一次
- 所有 run 都
completed+success→ 发布成功 - 任一 run
completed+failure→ 立即报告失败 - 基础超时 5 分钟, 如果上次轮询到本次轮询之间有状态变化 (如 step 完成), 重置超时计时器
- 超时无进展 → 报告超时
6. 最终报告
成功:
## 发布成功
- 版本: <version>
- 分支: main → release
- CI:
- <workflow-name>: success (<url>)
- <workflow-name>: success (<url>)
失败:
## 发布失败
- 失败步骤: <step>
- 错误: <message>
- 建议: <action>
红线
永远不要:
- 在工作区不干净时继续
- 在远端 main 领先时继续
- 在无嵌套 worktree 干扰且本地全仓 lint 失败时继续推送
- ff-only 失败时强制合并
- 跳过 CI 监控直接报告成功