开源项目的自动化发版工具链:ChangeLog 生成与语义化版本的工程实践 开源项目的自动化发版工具链ChangeLog 生成与语义化版本的工程实践一、发版的负担手工整理 Release Notes 为什么是一种工程浪费开源项目的发布流程通常涉及整理 commit log → 编写 ChangeLog → 确定版本号 → 打 tag → 创建 GitHub Release → 发布到 npm/PyPI 包管理器。如果每次发版都手动完成这些步骤保守估计需要 15-30 分钟。如果你每周发版一次这是可接受的。但如果你维护多个项目或者你的项目活跃度很高每周 3-5 个 PR手动发版会成为瓶颈。更糟的是人工确定版本号容易出错——一个 breaking change 的 PR 被当作 patch 版本发布用户升级后系统崩溃。自动发版工具链的价值不是节省 15 分钟而是消除人工判断的不一致性。版本的提升major/minor/patch应该由代码变更的内容决定而不是由开发者的主观判断。graph LR A[开发者提交 PR] -- B[Conventional Commitsbr/feat: / fix: / BREAKING CHANGE:] B -- C[PR 合并到 main] C -- D[semantic-release 分析] D -- E{commit 类型} E --|feat:| F[minor 版本 1] E --|fix:| G[patch 版本 1] E --|BREAKING CHANGE:| H[major 版本 1] F -- I[生成 ChangeLog] G -- I H -- I I -- J[创建 GitHub Release] J -- K[npm publish] K -- L[通知 Slack/Discord] style H fill:#ff6b6b,color:#fff style F fill:#ffd43b,color:#000 style G fill:#51cf66,color:#fff本文将搭建一套基于 semantic-release 的自动化发版工具链覆盖 Commit 规范、ChangeLog 生成和发布验证。二、Conventional Commits自动化发版的基石自动化发版的前提是 commit message 的规范化。如果每个 commit 都是update、fix bug、WIP这样的描述没有任何工具能从 commit 信息中推断出版本应该怎么变。Conventional Commits 规范定义了标准的 commit message 格式type[optional scope]: description [optional body] [optional footer(s)]关键 type 与版本的影响Type版本影响示例feat:minorfeat: add user search APIfix:patchfix: resolve login timeout issueBREAKING CHANGE:majorfeat!: drop support for Node 14docs:不触发发版docs: update API referencechore:不触发发版chore: upgrade dependenciesrefactor:不触发发版refactor: extract auth middleware在团队中强制执行 Conventional Commits 需要两个工具commitlint本地提交时检查和 GitHub Actions 中的 commit message 检查。三、完整自动化发版方案实现commitlint 配置.commitlintrc.jsmodule.exports { extends: [commitlint/config-conventional], rules: { type-enum: [2, always, [ feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert, ]], subject-min-length: [2, always, 10], subject-max-length: [2, always, 100], }, };配合 HuskyGit hooksnpx husky add .husky/commit-msg npx --no -- commitlint --edit $1semantic-release 配置.releaserc.json{ branches: [main], plugins: [ semantic-release/commit-analyzer, semantic-release/release-notes-generator, semantic-release/changelog, semantic-release/npm, semantic-release/github, [ semantic-release/git, { assets: [CHANGELOG.md, package.json], message: chore(release): ${nextRelease.version} [skip ci] } ] ] }GitHub Actions 自动发版 Workflowname: Release on: push: branches: [main] jobs: release: runs-on: ubuntu-latest permissions: contents: write issues: write pull-requests: write id-token: write steps: - uses: actions/checkoutv4 with: fetch-depth: 0 - uses: pnpm/action-setupv2 - uses: actions/setup-nodev4 with: node-version: 20 cache: pnpm registry-url: https://registry.npmjs.org - run: pnpm install --frozen-lockfile - run: pnpm test - run: pnpm build - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-release发包前验证额外的质量检查在 semantic-release 之前执行- name: Pre-release checks run: | # 检查 package.json 的必要字段 node -e const pkg require(./package.json); const required [name, version, main, types, files]; const missing required.filter(f !pkg[f]); if (missing.length) { console.error(Missing fields:, missing.join(, )); process.exit(1); } # 检查构建产物 test -f dist/index.mjs || (echo Missing dist/index.mjs exit 1) test -f dist/index.cjs || (echo Missing dist/index.cjs exit 1) # 检查包大小 SIZE$(du -sh dist | cut -f1) echo Package size: $SIZE质量验证脚本在发布前运行 E2E 测试- name: E2E Smoke Test run: | # 本地安装构建产物测试 mkdir /tmp/test-project cd /tmp/test-project npm init -y npm install $GITHUB_WORKSPACE node -e const lib require(my-lib); console.log(Import OK:, Object.keys(lib));四、自动化发版的边界条件Monorepo 的支持如果使用 Monorepo需要配置 semantic-release-monorepo 插件按包分别计算版本、分别发布。预发布版本对于 alpha/beta 版本需要配置预发布分支。semantic-release 支持branches: [main, {name: beta, prerelease: true}]在 beta 分支上自动生成1.0.0-beta.1格式的版本。首次发版如果仓库还没有 tagv1.0.0 之前的版本semantic-release 默认从1.0.0开始。如果需要 0.x 版本需要显式配置initialVersion: 0.1.0。不适用场景非语义化版本的项目如使用日期版本号2024.01.15严格的手动审批流程如金融、医疗软件Commit message 不规范的历史仓库需要先用工具规范化历史 commit五、总结自动化发版工具链的核心价值是用 commit message 驱动版本号变更消除人工判断的不一致性。semantic-release 是这一领域最成熟的开源方案——社区支持广插件丰富。落地路径先用 commitlint husky 在团队中强制 Conventional Commits然后引入 semantic-release在 CI 中配置自动发版最后添加预发布验证步骤E2E smoke test、包体积检查。少即是多。自动发版不是为了让开发者更懒而是为了让每次发版都遵循一致的规则。消除了这次该升 minor 还是 patch的纠结你的精力可以更多投入到代码本身。