第十八章:使用 Google Release Please 实现 github 云端自动化版本管理与发布

第十八章:使用 Google Release Please 实现 github 云端自动化版本管理与发布

本章目标:掌握语义化版本规范,利用 Release Please 实现版本号与 CHANGELOG 的自动化生成


开始之前:你需要知道的版本号基础

前置知识快速补课

在学习自动化版本管理之前,你需要理解几个基础概念:

版本号格式:MAJOR.MINOR.PATCH(例如:2.3.5

位置名称何时+1示例场景
MAJOR主版本号做了 不兼容 的 API 修改Vue 2 → Vue 3(Options API → Composition API)
MINOR次版本号添加了 向后兼容 的新功能React 16.8 添加 Hooks
PATCH修订号修复了 向后兼容 的 Bug修复内存泄漏、修正文档错误

记忆口诀

  • 破坏兼容 → MAJOR 跳
  • 新增功能 → MINOR 涨
  • 修 Bug → PATCH 补

特殊规则

  • 0.x.x = 初始开发版本,随时可能破坏兼容性
  • 1.0.0 = 第一个稳定版本,API 开始遵守语义化版本规则
  • 预发布版本1.0.0-alpha.12.0.0-beta.23.0.0-rc.1

18.1. 手动版本管理的三大噩梦

18.1.1. 噩梦一:版本号冲突的死锁

场景复现

星期一上午,开发者小李和小王都在开发新功能。

小李的时间线

1
2
3
4
5
6
7
8
9
# 9:00 - 拉取最新代码
git pull origin main # package.json version: "1.2.0"

# 9:30 - 开发完成,修改版本号
# package.json: "version": "1.3.0"
git commit -m "feat: add dark mode"

# 10:00 - 推送代码
git push origin main # ✅ 成功

小王的时间线(同时进行):

1
2
3
4
5
6
7
8
9
10
# 9:00 - 拉取最新代码
git pull origin main # package.json version: "1.2.0"

# 10:30 - 开发完成,修改版本号
# package.json: "version": "1.3.0" # 🚨 和小李改成一样了!
git commit -m "feat: add i18n support"

# 11:00 - 推送代码
git push origin main
# ❌ 失败!Git检测到冲突

合并时的灾难

1
2
3
4
5
<<<<<<< HEAD
"version": "1.3.0"
=======
"version": "1.3.0"
>>>>>>> feature/i18n

Git 无法自动解决(因为两边内容相同),但逻辑上错了——应该是 1.4.0 才对


18.1.2. 噩梦二:CHANGELOG 维护的黑洞

传统手工流程

每次发版前,团队需要:

  1. 召开会议,让每个开发者口述自己做了什么
  2. Tech Lead 手动整理成 Markdown
  3. 按类型分组(Features / Bug Fixes / Breaking Changes)
  4. 查找对应的 Issue 编号和 PR 链接
  5. 人工校对是否有遗漏

一个真实的 CHANGELOG 编写时间统计

版本包含的提交数编写时间主要耗时
10 个提交30 分钟回忆每个提交的背景
50 个提交2 小时分类和查找 Issue 编号
100+个提交半天去重、校对、格式化

手工 CHANGELOG 的常见问题

1
2
3
4
5
6
7
8
9
## v1.5.0 (2024-03-15)

### Features
- 添加了新功能
- 修复了一些问题
- 优化了性能

### Bug Fixes
- 修复了登录bug

问题分析

  1. ❌ “添加了新功能” — 太模糊,用户看不懂
  2. ❌ “修复了一些问题” — 哪些问题?
  3. ❌ 没有 PR 链接 — 无法追溯详细信息
  4. ❌ “修复了登录 bug” — 在 Features 还是 Bug Fixes?分类混乱

18.1.3. 噩梦三:发布流程的不透明

传统发布流程

1
2
3
4
5
6
1. 开发完成
2. ??? 谁负责改版本号?
3. ??? 谁负责打Tag?
4. ??? 谁负责写Release Notes?
5. ??? 什么时候发布到NPM?
6. 🤷 其他团队成员完全不知情

真实案例

某天下午 5 点,运维发现生产环境出现了新 Bug。紧急回溯后发现:

  • 小张在中午 12 点手动发布了 v2.1.0
  • 但他忘记通知任何人
  • Git 上没有对应的 Tag(他只改了 package.json)
  • NPM 上有这个版本,但 GitHub Release 里没有
  • 根本无法确定这个版本包含了哪些代码

18.2. 解决方案:Conventional Commits + Release Please

18.2.1. Conventional Commits 规范详解

核心思想:通过标准化的提交信息格式,让机器能够 “理解” 每次提交的意图。

完整语法结构

1
2
3
4
5
<type>(<scope>): <subject>

<body>

<footer>

各部分说明

部分是否必需说明示例
type✅ 必需提交类型(见下表)featfixdocs
scope❌ 可选影响范围(auth)(ui)(api)
subject✅ 必需简短描述(50 字符内)add dark mode toggle
body❌ 可选详细描述多行文本,解释动机和实现
footer❌ 可选关联 Issue 或破坏性变更Closes #123
BREAKING CHANGE: ...

Type 类型完整列表

Type作用影响版本号出现在 CHANGELOG示例
feat新功能+MINOR✅ 是feat(auth): add OAuth login
fix修复 Bug+PATCH✅ 是fix(api): handle null response
docs文档变更不变⚠️ 可选docs: update README
style代码格式(不影响逻辑)不变❌ 否style: format with prettier
refactor重构(不改功能)不变⚠️ 可选refactor: extract helper function
perf性能优化+PATCH✅ 是perf: lazy load images
test测试相关不变❌ 否test: add unit tests for utils
build构建系统/依赖不变⚠️ 可选build: upgrade webpack to v5
ciCI 配置不变❌ 否ci: add caching to GitHub Actions
chore其他杂项不变❌ 否chore: update .gitignore
revert回滚提交+PATCH✅ 是revert: feat(auth): add OAuth

破坏性变更的两种写法

方法 1:在 type 后加 !

1
git commit -m "feat!: migrate to new API endpoint"

方法 2:在 footer 中声明

1
2
3
4
git commit -m "feat: migrate to new API endpoint

BREAKING CHANGE: The old /api/v1 endpoints are removed.
Please update to /api/v2."

效果:两种方法都会触发 MAJOR 版本号 +1

例如如下的参考

场景正确答案
修复了用户无法登出的 bugfix(auth): prevent logout button from being disabled
添加了深色模式feat(ui): add dark mode toggle
更新了 README 的安装说明docs(readme): clarify installation steps
将 Vue 2 升级到 Vue 3feat!: upgrade to Vue 3

feat: upgrade to Vue 3
BREAKING CHANGE: ...
优化了图片加载速度perf(images): implement lazy loading
修复了测试用例的 typotest: fix typo in user.test.js

18.2.2. Release Please 的工作原理

一句话概括:Release Please 是一个 “提交信息解析器 + 版本号计算器 + CHANGELOG 生成器 + PR 自动化机器人”。

1. 监听(Listen)

  • 每次代码推送到 main 分支时触发
  • 读取上次发布的版本号(从 .release-please-manifest.json 或 Git Tags)
  • 扫描并解析所有新提交的 Type 和 BREAKING CHANGE

2. 提案(Propose)

  • 检测到有效提交(feat/fix)后,创建或更新一个特殊的 PR
  • PR 标题:chore(main): release <version>
  • PR 分支:release-please--branches--main
  • 包含的文件变更:
    • package.json:版本号已更新
    • CHANGELOG.md:新增本次发布的变更记录
    • .release-please-manifest.json:更新为新版本号

3. 堆叠(Stack)

  • 如果 Release PR 未合并,新提交会自动追加到现有 PR 中
  • 版本号和 CHANGELOG 会重新计算,始终保持一个 PR 反映最新待发布状态

4. 交付(Deliver)

  • 合并 Release PR 后,自动创建 Git Tag 和 GitHub Release
  • CHANGELOG 内容填充到 Release Notes
  • 设置 Action Outputs 供后续 Job 使用

为什么使用 PR 模式而不是直接打 Tag

  • 可预览:合并前可查看 CHANGELOG 内容
  • 可干预:可手动调整版本号或补充文档
  • 可讨论:团队可在 PR 中讨论是否应该发布

18.2.3. 配置文件完全解析:从 5 行到 60 行

Release Please 使用 “配置与状态分离” 的设计,这种模式非常适合长期维护的项目:

  • release-please-config.json:定义发布规则(由开发者维护)
  • .release-please-manifest.json:记录当前版本号(由工具自动维护)

完整配置文件示例(release-please-config.json)

我们需要在项目根目录中创建如下 JSON 文件。注意:为了让 IDE 能够读取 Schema 进行自动补全,这里的注释仅用于讲解,实际复制到 JSON 文件中请务必删除注释!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
{
// 引入 JSON Schema,让 IDE 提供自动补全和字段校验
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",

// 全局默认的发布类型
// - node: 读取 package.json 的 version 字段
// - python: 读取 setup.py 或 pyproject.toml
// - rust: 读取 Cargo.toml
// - go: 基于 Git Tags(Go 没有版本文件)
// - simple: 不修改任何文件,仅基于 Git Tags
"release-type": "node",

// Monorepo 场景下 Tag 是否包含组件名
// false: v1.0.0 | true: frontend-v1.0.0
"include-component-in-tag": false,

// Tag 是否包含 v 前缀
// true: v1.0.0 | false: 1.0.0
"include-v-in-tag": true,

// 当版本号为 0.x.x 时,feat 提交是否增加 MINOR
// false: 0.1.0 -> 0.1.1 | true: 0.1.0 -> 0.2.0
"bump-minor-pre-major": false,

// 当 bump-minor-pre-major=true 时,fix 提交是否增加 PATCH
// false: 不变更 | true: 0.2.0 -> 0.2.1
"bump-patch-for-minor-pre-major": false,

// CHANGELOG 分组规则
// type: 对应 Conventional Commits 的类型
// section: 在 CHANGELOG 中显示的标题
// hidden: 是否隐藏该类型的提交
"changelog-sections": [
{"type": "feat", "section": "Features"},
{"type": "fix", "section": "Bug Fixes"},
{"type": "perf", "section": "Performance Improvements"},
{"type": "revert", "section": "Reverts"},
{"type": "docs", "section": "Documentation", "hidden": false},
{"type": "style", "section": "Styles", "hidden": true},
{"type": "chore", "section": "Miscellaneous Chores", "hidden": true},
{"type": "refactor", "section": "Code Refactoring", "hidden": true},
{"type": "test", "section": "Tests", "hidden": true},
{"type": "build", "section": "Build System", "hidden": true},
{"type": "ci", "section": "Continuous Integration", "hidden": true}
],

// 包配置(支持 Monorepo)
"packages": {
// "." 表示根目录项目
".": {
"release-type": "node",
"package-name": "vite-cicd-demo",

// CHANGELOG 文件路径
"changelog-path": "CHANGELOG.md",

// 除了默认文件(如 package.json)外,还需要更新版本号的额外文件
// 例如:src/version.ts 中导出了版本号常量供运行时使用
"extra-files": [
"src/version.ts"
]
}
}
}

状态文件示例(.release-please-manifest.json)

1
2
3
{
".": "1.0.0"
}

extra-files 的实际应用场景

在我们的前端项目中,通常希望在页面底部显示当前应用版本。我们创建一个专门存放版本号的文件:

src/version.ts

1
2
// 这个文件的版本号会被 Release Please 自动更新,请勿手动修改
export const VERSION = '1.0.0';

然后在 src/App.vue (或 React 的 App.tsx) 中引入使用:

1
2
3
4
5
6
7
import { VERSION } from "./version"

export default function App() {
return <div>
<p>Version: {VERSION}</p>
</div>
}

当 Release Please 创建 Release PR 时,它会同时修改 package.json 和这个 src/version.ts,确保页面显示的版本永远是最新的。


18.2.4. GitHub Actions 工作流配置

创建 .github/workflows/release-please.yml。这个文件负责“看门”,它只在代码推送到 main 分支时工作,在此之前我们需要授权 workflow 创建 pr 的权限,否则会报错退出

image-20251201111457249

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
name: Release Please

on:
push:
branches:
- main

permissions:
contents: write # 需要创建 Release 和 Tag
pull-requests: write # 需要创建和更新 Release PR

jobs:
release-please:
runs-on: ubuntu-latest

# 将输出暴露出来,供后续的构建/部署 Job 使用
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}

steps:
- uses: google-github-actions/release-please-action@v4
id: release
with:
# 指定使用 Manifest 模式的配置文件
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

18.2.5. 完整实战演练:从开发到发布

让我们在现有的项目中实际演练一遍。

初始化配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. 创建配置文件 (内容如上节所示,记得去除注释)
touch release-please-config.json

# 2. 创建初始状态文件(假设当前项目是 0.0.0)
echo '{"." : "0.0.0"}' > .release-please-manifest.json

# 3. 创建版本常量文件
echo "export const VERSION = '0.0.0';" > src/version.ts

# 4. 提交配置到 main 分支
git add .
git commit -m "chore: setup release-please config"
git push origin main

场景一:开发新功能(Feature)

假设我们要开发一个新组件,并希望版本号从 0.0.0 升级到 0.1.0

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 修改代码
# 假设我们修改了 src/App.vue,引入了刚才的 version.ts
# 或者添加了一个新的 UI 组件

# 2. 使用 Conventional Commits 提交
git add src/App.vue
git commit -m "feat: display app version in footer"
git push origin main

# 3. 触发自动化
# GitHub Actions 运行后,会自动创建一个 PR
# 标题为: "chore(main): release 0.1.0"

检查 Release PR 内容

此时去 GitHub 查看 Pull Requests,你会发现机器人创建的 PR 包含以下变更:

  1. package.json: "version": "0.1.0"
  2. src/version.ts: export const VERSION = '0.1.0'; (自动同步了!)
  3. CHANGELOG.md: 生成了新条目。
  4. .release-please-manifest.json: 更新为 0.1.0

场景二:发现 Bug 并修复(Fix)

在 PR 合并之前,测试同学发现页面的样式有点问题。我们继续提交:

1
2
3
4
5
6
7
# 1. 修复样式问题
git commit -am "fix: adjust footer text alignment"
git push origin main

# 2. 机器人自动更新 PR
# 版本号依然维持在 0.1.0 (因为 feat 已经是 MINOR 更新了,fix 包含在内)
# CHANGELOG 会追加一条 Bug Fixes 记录

场景三:合并发布

  1. Code Review:团队成员 Review 代码确认无误。
  2. Merge:点击 “Squash and merge” 或 “Rebase and merge”。
  3. 触发发布
    • release-please Action 再次运行。
    • 检测到 Release PR 已合并。
    • 创建 Git Tag v0.1.0
    • 创建 GitHub Release,内容自动填充 CHANGELOG。

场景四:破坏性变更(Breaking Change)

如果我们重构了核心逻辑,导致旧版本不兼容:

1
2
3
4
5
6
7
git commit -m "feat!: migrate to new auth system

BREAKING CHANGE: The login API endpoint has changed."
git push origin main

# Release Please 会识别 "!" 和 "BREAKING CHANGE" 关键字
# 下一次生成的 PR 版本号将直接跳到 1.0.0

18.3. 本章小结与实战检查清单

核心知识回顾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
自动化版本管理体系
├── Conventional Commits
│ ├── 格式:<type>(<scope>): <subject>
│ ├── 关键type:feat / fix / BREAKING CHANGE
│ └── 自动化基础:让机器理解提交意图

├── Semantic Versioning
│ ├── MAJOR:破坏性变更
│ ├── MINOR:新功能
│ └── PATCH:Bug修复

└── Release Please
├── 监听:扫描提交信息
├── 计算:推导版本号
├── 提案:创建Release PR
├── 堆叠:自动合并新提交
└── 交付:创建Tag + Release

下一章预告

在第 19 章《制品自动化交付实战》中,我们将把 Release Please 与实际的发布流程结合:

  • NPM 包的自动发布(处理 2FA 认证)
  • Docker 镜像的多架构构建
  • 如何在发布失败时回滚
  • 完整的 CD Pipeline 集成

继续前进! 🚀