name: changelog-automation description: 自动化从提交、PR和发布中生成变更日志,遵循Keep a Changelog格式。用于设置发布工作流、生成发布说明或标准化提交约定。
变更日志自动化
模式和工具用于自动化生成变更日志、发布说明和版本管理,遵循行业标准。
何时使用此技能
- 设置自动化变更日志生成
- 实施Conventional Commits
- 创建发布说明工作流
- 标准化提交消息格式
- 生成GitHub/GitLab发布说明
- 管理语义版本控制
核心概念
1. Keep a Changelog格式
# 变更日志
所有对此项目的显著更改将记录在此文件中。
格式基于[Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
并且此项目遵循[语义版本控制](https://semver.org/spec/v2.0.0.html)。
## [未发布]
### 新增
- 新功能X
## [1.2.0] - 2024-01-15
### 新增
- 用户个人资料头像
- 深色模式支持
### 更改
- 改进加载性能40%
### 弃用
- 旧认证API(使用v2)
### 移除
- 遗留支付网关
### 修复
- 登录超时问题(#123)
### 安全
- 更新依赖项以应对CVE-2024-1234
[未发布]: https://github.com/user/repo/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/user/repo/compare/v1.1.0...v1.2.0
2. Conventional Commits
<类型>[可选范围]: <描述>
[可选正文]
[可选页脚]
| 类型 | 描述 | 变更日志部分 |
|---|---|---|
feat |
新功能 | 新增 |
fix |
错误修复 | 修复 |
docs |
文档 | (通常排除) |
style |
格式化 | (通常排除) |
refactor |
代码重构 | 更改 |
perf |
性能 | 更改 |
test |
测试 | (通常排除) |
chore |
维护 | (通常排除) |
ci |
CI更改 | (通常排除) |
build |
构建系统 | (通常排除) |
revert |
还原提交 | 移除 |
3. 语义版本控制
主版本号.次版本号.修订号
主版本号:破坏性更改 (feat! 或 BREAKING CHANGE)
次版本号:新功能 (feat)
修订号:错误修复 (fix)
实施方法
方法1: Conventional Changelog (Node.js)
# 安装工具
npm install -D @commitlint/cli @commitlint/config-conventional
npm install -D husky
npm install -D standard-version
# 或
npm install -D semantic-release
# 设置commitlint
cat > commitlint.config.js << 'EOF'
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'style',
'refactor',
'perf',
'test',
'chore',
'ci',
'build',
'revert',
],
],
'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
'subject-max-length': [2, 'always', 72],
},
};
EOF
# 设置husky
npx husky init
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
方法2: standard-version配置
// .versionrc.js
module.exports = {
types: [
{ type: "feat", section: "功能" },
{ type: "fix", section: "错误修复" },
{ type: "perf", section: "性能改进" },
{ type: "revert", section: "还原" },
{ type: "docs", section: "文档", hidden: true },
{ type: "style", section: "样式", hidden: true },
{ type: "chore", section: "杂项", hidden: true },
{ type: "refactor", section: "代码重构", hidden: true },
{ type: "test", section: "测试", hidden: true },
{ type: "build", section: "构建系统", hidden: true },
{ type: "ci", section: "CI/CD", hidden: true },
],
commitUrlFormat: "{{host}}/{{owner}}/{{repository}}/commit/{{hash}}",
compareUrlFormat:
"{{host}}/{{owner}}/{{repository}}/compare/{{previousTag}}...{{currentTag}}",
issueUrlFormat: "{{host}}/{{owner}}/{{repository}}/issues/{{id}}",
userUrlFormat: "{{host}}/{{user}}",
releaseCommitMessageFormat: "chore(release): {{currentTag}}",
scripts: {
prebump: 'echo "运行prebump"',
postbump: 'echo "运行postbump"',
prechangelog: 'echo "运行prechangelog"',
postchangelog: 'echo "运行postchangelog"',
},
};
// package.json脚本
{
"scripts": {
"release": "standard-version",
"release:minor": "standard-version --release-as minor",
"release:major": "standard-version --release-as major",
"release:patch": "standard-version --release-as patch",
"release:dry": "standard-version --dry-run"
}
}
方法3: semantic-release (全自动化)
// release.config.js
module.exports = {
branches: [
"main",
{ name: "beta", prerelease: true },
{ name: "alpha", prerelease: true },
],
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@semantic-release/changelog",
{
changelogFile: "CHANGELOG.md",
},
],
[
"@semantic-release/npm",
{
npmPublish: true,
},
],
[
"@semantic-release/github",
{
assets: ["dist/**/*.js", "dist/**/*.css"],
},
],
[
"@semantic-release/git",
{
assets: ["CHANGELOG.md", "package.json"],
message:
"chore(release): ${nextRelease.version} [skip ci]
${nextRelease.notes}",
},
],
],
};
方法4: GitHub Actions工作流
# .github/workflows/release.yml
name: 发布
on:
push:
branches: [main]
workflow_dispatch:
inputs:
release_type:
description: "发布类型"
required: true
default: "patch"
type: choice
options:
- patch
- minor
- major
permissions:
contents: write
pull-requests: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm ci
- name: 配置Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: 运行semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
# 替代方法:手动发布使用standard-version
manual-release:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- name: 配置Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: 版本提升并生成变更日志
run: npx standard-version --release-as ${{ inputs.release_type }}
- name: 推送更改
run: git push --follow-tags origin main
- name: 创建GitHub发布
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.version.outputs.tag }}
body_path: RELEASE_NOTES.md
generate_release_notes: true
方法5: git-cliff (基于Rust, 快速)
# cliff.toml
[changelog]
header = """
# 变更日志
所有对此项目的显著更改将记录在此文件中。
"""
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [未发布]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}**{{ commit.scope }}:** {% endif %}\
{{ commit.message | upper_first }}\
{% if commit.github.pr_number %} ([#{{ commit.github.pr_number }}](https://github.com/owner/repo/pull/{{ commit.github.pr_number }})){% endif %}\
{% endfor %}
{% endfor %}
"""
footer = """
{% for release in releases -%}
{% if release.version -%}
{% if release.previous.version -%}
[{{ release.version | trim_start_matches(pat="v") }}]: \
https://github.com/owner/repo/compare/{{ release.previous.version }}...{{ release.version }}
{% endif -%}
{% else -%}
[未发布]: https://github.com/owner/repo/compare/{{ release.previous.version }}...HEAD
{% endif -%}
{% endfor %}
"""
trim = true
[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
commit_parsers = [
{ message = "^feat", group = "功能" },
{ message = "^fix", group = "错误修复" },
{ message = "^doc", group = "文档" },
{ message = "^perf", group = "性能" },
{ message = "^refactor", group = "重构" },
{ message = "^style", group = "样式" },
{ message = "^test", group = "测试" },
{ message = "^chore\\(release\\)", skip = true },
{ message = "^chore", group = "杂项" },
]
filter_commits = false
tag_pattern = "v[0-9]*"
skip_tags = ""
ignore_tags = ""
topo_order = false
sort_commits = "oldest"
[github]
owner = "owner"
repo = "repo"
# 生成变更日志
git cliff -o CHANGELOG.md
# 为特定范围生成
git cliff v1.0.0..v2.0.0 -o RELEASE_NOTES.md
# 预览不写入
git cliff --unreleased --dry-run
方法6: Python (commitizen)
# pyproject.toml
[tool.commitizen]
name = "cz_conventional_commits"
version = "1.0.0"
version_files = [
"pyproject.toml:version",
"src/__init__.py:__version__",
]
tag_format = "v$version"
update_changelog_on_bump = true
changelog_incremental = true
changelog_start_rev = "v0.1.0"
[tool.commitizen.customize]
message_template = "{{change_type}}{% if scope %}({{scope}}){% endif %}: {{message}}"
schema = "<类型>(<范围>): <主题>"
schema_pattern = "^(feat|fix|docs|style|refactor|perf|test|chore)(\\(\\w+\\))?:\\s.*"
bump_pattern = "^(feat|fix|perf|refactor)"
bump_map = {"feat" = "MINOR", "fix" = "PATCH", "perf" = "PATCH", "refactor" = "PATCH"}
# 安装
pip install commitizen
# 交互式创建提交
cz commit
# 提升版本并更新变更日志
cz bump --changelog
# 检查提交
cz check --rev-range HEAD~5..HEAD
发布说明模板
GitHub发布模板
## 有什么变化
### 🚀 功能
{{ range .Features }}
- {{ .Title }} 由 @{{ .Author }} 在 #{{ .PR }}
{{ end }}
### 🐛 错误修复
{{ range .Fixes }}
- {{ .Title }} 由 @{{ .Author }} 在 #{{ .PR }}
{{ end }}
### 📚 文档
{{ range .Docs }}
- {{ .Title }} 由 @{{ .Author }} 在 #{{ .PR }}
{{ end }}
### 🔧 维护
{{ range .Chores }}
- {{ .Title }} 由 @{{ .Author }} 在 #{{ .PR }}
{{ end }}
## 新贡献者
{{ range .NewContributors }}
- @{{ .Username }} 在 #{{ .PR }} 中首次贡献
{{ end }}
**完整变更日志**: https://github.com/owner/repo/compare/v{{ .Previous }}...v{{ .Current }}
内部发布说明
# 发布 v2.1.0 - 2024年1月15日
## 总结
此发布引入深色模式支持,并提高结账性能40%。还包括重要的安全更新。
## 亮点
### 🌙 深色模式
用户现在可以从设置切换到深色模式。偏好自动保存并跨设备同步。
### ⚡ 性能
- 结账流程快40%
- 减少捆绑包大小15%
## 破坏性更改
此发布中无破坏性更改。
## 升级指南
无需特殊步骤。标准部署流程适用。
## 已知问题
- 深色模式可能在初始加载时闪烁(计划在v2.1.1中修复)
## 更新依赖项
| 包 | 从 | 到 | 原因 |
| -------- | -------- | -------- | ---------------------- |
| react | 18.2.0 | 18.3.0 | 性能改进 |
| lodash | 4.17.20 | 4.17.21 | 安全补丁 |
提交消息示例
# 带范围的功能
feat(auth): 添加Google登录的OAuth2支持
# 带问题引用的错误修复
fix(checkout): 解决支付处理中的竞争条件
关闭 #123
# 破坏性更改
feat(api)!: 更改用户端点响应格式
破坏性更改:用户端点现在返回`userId`而不是`id`。
迁移指南:更新所有API消费者使用新字段名。
# 多段落
fix(database): 优雅处理连接超时
之前,连接超时会导致整个请求失败而无重试。此更改实施指数退避,最多重试3次再失败。
基于p99延迟分析,超时阈值从5秒增加到10秒。
修复 #456
审核者: @alice
最佳实践
应做事项
- 遵循Conventional Commits - 启用自动化
- 写清晰消息 - 未来的你会感谢你
- 引用问题 - 链接提交到工单
- 一致使用范围 - 定义团队约定
- 自动化发布 - 减少手动错误
不应做事项
- 不混用更改 - 每个提交一个逻辑更改
- 不跳过验证 - 使用commitlint
- 不手动编辑 - 仅使用生成的变更日志
- 不忘记破坏性更改 - 用
!或页脚标记 - 不忽略CI - 在管道中验证提交