前阵子在开发一个小工具,希望自己可以学习了解软件从开发到发布的过程。
其中就有关 Github Release 信息和 Changelog 的编写。这一过程有一些繁琐,并且保持格式统一也有些令人疲惫,于是我开始寻找自动化生成 Changelog 的方案。
编写 Changelog 的原因可以查看: keepachangelog.
Changelogen 存在的问题
很快,我在网上发现了 changelogen, 这个工具可以快速生成精美的 Changelog 并输出到 CHANGELOG.md
,以及创建或者更新 Release 信息,几乎完美符合我的需求。
但是有几个问题的存在,使其与 Github Action 结合的体验非常糟糕:
1. 适用项目受限
从他可以自动更新 package.json
中的版本号这一功能可以看出,changelogen 主要适用于 Node.js 项目。关键是这个功能在用 --release
发布到 GitHub 无法取消,如果不是 Node.js 项目或者 package.json
版本号更新失败,就会直接退出,根本不发布。
2. 获取当前 Tag 的机制
changelogen 的使用场景发生在打 tag 之前,如果不用 --from
和 --to
来指定 commit,他默认生成离 HEAD 最近的 tag 到 HEAD 之间的 Changelog。
但是我们在使用 Github Action 时,一般用 tag 来作为触发条件,也就是说我们在期望触发 Action 时,已经打好了 tag, 如果这时候使用 changelogen,默认行为就是生成 from current_tag
to current_tag
的 changelog,这当然是毫无意义的。
当然我们也可以选择在 GitHub Action 运行时手动提取出前后 Tag,然后用 --from
和 --to
指定 tag。但是这显然不够优雅,幸好该项目不算复杂并且结构清晰,修改源码非常容易:
修改
// ./src/git.ts
export async function getLastGitTag(cwd?: string) {
try {
return execCommand("git describe --tags --abbrev=0", cwd)
?.split("\n")
.at(-1);
} catch {
// Ignore
}
}
// ...
export function getCurrentGitTag(cwd?: string) {
return execCommand("git tag --points-at HEAD", cwd);
}
从源码可以看出,changelogen 通过执行外部 git 命令来获取上一条 tag 与当前 tag。只需在获取上一条 tag 时,忽略当前 HEAD, 即可正确处理 HEAD 打完 tag 的情况:
export async function getLastGitTag(cwd?: string) {
try {
return execCommand("git describe --tags --abbrev=0 HEAD^", cwd)
?.split("\n")
.at(-1);
} catch {
// Ignore
}
}
然后我移除了关于自动打 tag,以及 package.json
版本号更新的功能,以获取对更多项目的兼容性。
详细修改可以查看这个 Commit.
使用
现在打完 tag 后,使用 --release
参数就可以正确生成 Changelog 并在 GitHub release 啦。
我将修改后的版本传到了 npm,包名为 changelogen-gh
。这样就可以很方便的集成到 GitHub Action 中了。
示例:
name: Release Changelog
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: main
- name: Setup git config
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Release changelog
run: npx changelogen-gh --release --push
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
以上 Action 做了这些事情:
- 设置好仓库。
- 根据当前 tag 以及上一条 tag 生成或更新 (追加内容)
CHANGELOG.md
。 - 将修改后的文件提交 commit 并推送到仓库。
- 创建或更新 GitHub Release。
不足
既然删除了自动打 tag 功能,也就意味着我们需要自己手动打 tag。这有利有弊吧,不过需要严格遵守语义化版本。并且包管理器 (Cargo.toml
, package.json
, etc) 中的版本也不能忘记。
并且,Changelog 的生成是根据 conventional commit 生成的, 如果撰写 commit 不规范,生成的 changelog 也质量不高。
keepachangelog 中也指出,
Changelogs are for humans, not machines.
用工具生成的 changelog 难免有违「人」,所以在大的开源项目中,也需谨慎使用。