编程 一个 fix: commit 推上 main 就发版:semantic-release 配置与三个必踩的坑

2026-10-17 00:03:26

一个 fix: commit 推上 main 就发版:semantic-release 配置与三个必踩的坑

项目与文档地址

  • semantic-release:
  • release-please-action:
  • Changesets:
  • semantic-release-action:
  • npm trusted publishing:
  • Conventional Commits:

「自动化发版」指什么

代码合并进 release 分支后,由 CI 自动完成整条链路:算版本号 → 生成 changelog → 打 tag → 建 GitHub Release → 发布到 registry,全程不改人工版本号。

三种流派:

  • semantic-release:全自动、commit 驱动、零人工。默认使用 Angular Commit Message Conventions,也可以换成别的 preset(比如 conventionalcommits)。
  • release-please(googleapis/release-please-action,Google 出品):半自动。分析 Conventional Commits 后生成一个 Release PR,人工审批合并才真正发布。
  • Changesets(@changesets/cli):半自动。开发者手写 changeset 文件声明本次改动类型,原生支持 monorepo,按依赖拓扑排序发布,包 A 升级时会自动 bump 依赖 A 的包 B。

三者对照

维度Changesetssemantic-releaserelease-please
自动化程度半自动(手写 changeset)全自动半自动(需审批 PR)
Monorepo原生支持需插件原生支持
版本控制手动声明Commit 驱动Commit 驱动
Changelog从 changeset 生成从 commit 生成从 commit 生成
学习成本低中(需严格 commit 规范)低
适合场景多包项目、团队协作单包、持续交付单包、需审批流程

Commit 类型 → 版本号

semantic-release 默认的映射关系:

  • fix(pencil): ... → Patch(1.0.x)
  • feat(pencil): ... → Minor(1.x.0)
  • feat!: ... 或 footer 里的 BREAKING CHANGE: → Major(x.0.0)
  • BREAKING CHANGE: 必须写在 commit footer(body),写进第一行标题不生效
  • 不发布版本:chore: / docs: / style: / test: / build: / ci:

不支持 0.x。 master/main 上第一个 feat 会直接把版本推到 1.0.0。如果确实想从 0.x 起步,得先手动打个 tag,之后第一个 feat 才会挂到这个 tag 上:

git tag -a v0.1.0 -m "Version v0.1.0"
git push origin v0.1.0

预发布走分支:在 beta 分支上提交会发成 1.0.0-beta.1 这种形式。

semantic-release 的执行顺序是:Verify Conditions → Get last release(分析 Git tags)→ Analyze commits → Verify release → Generate notes → Create Git tag → Prepare → Publish → Notify。注意第二步依赖完整 tag 历史,这决定了后面 workflow 里 fetch-depth 怎么配。

.releaserc.json

{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
["@semantic-release/git", {
"assets": ["CHANGELOG.md", "package.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}],
"@semantic-release/github"
]
}

GitHub Actions 最小集成

name: Release
on:
push:
branches: [main]

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
id-token: write   # npm OIDC trusted publishing + provenance
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0     # semantic-release 要靠完整 tag 历史算版本
- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
- run: npm ci
- run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

坑 1:回写 commit 和 tag 过不了保护分支

semantic-release 需要把 CHANGELOG.md、package.json 的版本 bump 提交回仓库并打 tag。默认的 GITHUB_TOKEN 绕不过 main 分支上的 ruleset(要求所有改动走 PR),@semantic-release/git 直推会被 GitHub 拒掉,报 GH013。

两种解法:

  • 建一个 fine-grained PAT(contents: write),存成 RELEASE_PAT;
  • 建一个 GitHub App(Contents / Issues / Pull requests 均为 read & write),把这个 App 加为该 ruleset 的唯一 bypass actor,再用 actions/create-github-app-token 现场取一个短时 token。

很多仓库在 secret 缺失时会 no-op 保持绿灯(用 if gate 控制)。另外 fetch-depth: 0 必须给,否则算不出上一个 tag。

坑 2:[skip ci] 和 tag push 触发打架

release commit 上带了 [skip ci],而 tag push 事件会触发 GitHub 扫描整条 commit message 里的 [skip ci],于是「靠 tag push 触发 publish workflow」这套写法会被静默跳过——不报错,就是不发。

解法是别依赖 tag push 事件:

  • publish job 用 needs: release + if: needs.release.outputs.published == 'true';
  • 直接 uses: ./.github/workflows/release.yml(把发布流程写成 workflow_call)来调用;
  • 同时 release job 加 if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') }} 防止自触发。

坑 3:npm 发布凭据

用 npm 的 OIDC trusted publishing 可以完全不要 NPM_TOKEN:npm 拿 workflow 的 OIDC token 换一个短时、限定范围的发布凭据,所以 permissions 里的 id-token: write 就是授权本身,并且会自动生成 provenance。

前提是在 npmjs.com 上配置的 trusted publisher 必须写对 workflow 文件名(例如 release.yml),名字对不上就换不到凭据。旧方案是配 NPM_TOKEN secret。

release-please 的接法

- uses: googleapis/release-please-action@v4
id: release
with:
release-type: node

后续 publish job 用 needs + if: ${{ needs.release-please.outputs.release_created }} 决定要不要执行 npm publish --provenance。

浮动 major tag

发布流程通常会维护 v1、v2 这类浮动 major tag,指向该 major 下最新的 release。这样使用者用 v1 引用时,能自动拿到最新的 minor / patch,而不用改引用版本。

推荐文章

程序员茄子在线接单