第十三章. CHANGELOG 不用写!Husky+standard-version 自动生成 摘要 :在第五章,我们确立了 Conventional Commits 理论体系。但依靠文档约束的规范在 “Deadline” 面前往往不堪一击。本章我们将构建一套强制性的工程化流水线:
本地防线 :利用 Husky v9 和 commitlint 部署 “提交编译器”,在 git commit 瞬间拦截不合规信息。本地演练 :使用 standard-version 在本地模拟自动化版本发布的完整逻辑。云端实战 :引入 Google 的 release-please ,利用 GitHub Actions 实现基于 “Release PR” 的现代化发布流。这将是你从 “代码工人” 迈向 “DevOps 工程师” 的重要一步。本章学习路径
理念重构 :理解从 “人工审查” 到 “工具强制” 的工程化飞跃。规则引擎 :深度解析 commitlint 的配置体系(涵盖 ESM 与 CJS 差异)。门禁集成 :适配 Husky v9 的 commit-msg 钩子配置实战。本地自动化 :standard-version 的深度配置与生命周期管理。云端自动化 :Google release-please 工作流详解与实战演练。13.1. 从人治到法治:为什么需要自动化校验 13.1.1. 规范的 “最后一公里” 崩溃 在第五章中,我们约定了标准提交格式 <type>(<scope>): <subject>。然而,“规范写在文档里” 不等于 “规范被执行”。在实际的高压开发环境中,我们经常看到这种现象:
场景 开发者提交的真实内容 造成的后果 紧急修复 fix bug缺少 scope,描述模糊,无法定位问题 习惯使然 Fixed login page style.使用过去时,首字母大写,句号结尾(风格不统一) 手滑拼写 feta: add new featurefeta 不是合法类型,自动化工具无法识别情绪宣泄 update garbage code毫无语义,污染项目历史
这些不规范的提交一旦混入主分支,会产生连锁反应:CHANGELOG 生成器崩溃、版本号计算错误、以及 Git Blame 时的困惑 。
13.1.2. 解决方案:提交阶段的 “编译器” 我们写代码时,编译器会在构建阶段报错;写 Git 提交信息时,同样需要一个 “编译器”。
commitlint 就是这个角色。它的定位非常精准:
在 git commit 按下回车的那一刻,拦截提交信息流,对其进行语法分析。如果不符合预设的 AST(抽象语法树)规则,直接拒绝提交。
工具链分工表
工具 校验对象 触发时机 核心价值 ESLint JS/TS 代码 编码时/提交前 避免逻辑与语法错误 Prettier 代码格式 保存时/提交前 统一代码美学 commitlint Commit Message commit-msg 钩子 确保历史记录规范化 standard-version Git Log 发布版本时 本地自动化版本管理 release-please Git Log 合并代码时 云端 自动化版本管理
13.2. commitlint 核心配置详解 13.2.1. 安装与初始化 在现代前端项目(尤其是 Vite/Next.js 等默认为 ESM 的项目)中,配置方式与旧教程有所不同。
1. 安装依赖
1 pnpm install -D @commitlint/cli @commitlint/config-conventional
2. 创建配置文件
注意:如果你的 package.json 中包含 "type": "module",你需要创建 commitlint.config.js(使用 ESM 语法),我们采用 es6 的语法
在根目录创建 commitlint.config.mjs:
1 2 3 4 export default { extends : ["@commitlint/config-conventional" ], };
3. 快速验证
我们可以通过管道命令测试 commitlint 是否工作(无需 Git):
1 2 3 4 5 echo "feat(auth): add google login" | pnpm dlx commitlintecho "Add google login" | pnpm dlx commitlint
13.2.2. 规则系统深度剖析 @commitlint/config-conventional 预设了一套符合 Angular 规范的严格规则。所有的规则配置都遵循 [等级, 适用性, 参数] 的三元组格式。
1 2 3 rules : { 'rule-name' : [Level , Applicable , Value ] }
Level (等级) : 0 = 禁用, 1 = 警告, 2 = 错误 (阻止提交)。Applicable (适用性) : 'always' (必须满足), 'never' (必须不满足)。Value (参数) : 具体的配置值。核心内置规则速查
规则名 默认配置 含义解读 type-enum[2, 'always', [...]]Type 必须是 feat, fix, docs 等预设列表之一 type-empty[2, 'never']Type 不能为空 scope-case[2, 'always', 'lower-case']Scope 必须小写(如 auth 不能写成 Auth) subject-empty[2, 'never']描述信息不能为空 subject-full-stop[2, 'never', '.']描述信息不能以句号结尾 header-max-length[2, 'always', 100]标题行长度不能超过 100 字符
13.2.3. 企业级自定义配置实战 实际项目中,默认规则往往过于死板。以下是一份针对真实业务场景优化的配置清单。
场景一:增加自定义类型 (如 wip)
很多团队需要 wip (Work In Progress) 类型来标记未完成的工作,或者 build 类型来标记构建变更。
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 export default { extends : ["@commitlint/config-conventional" ], rules : { "type-enum" : [ 2 , "always" , [ "feat" , "fix" , "docs" , "style" , "refactor" , "perf" , "test" , "chore" , "ci" , "revert" , "build" , "wip" , "release" , "workflow" , ], ], }, };
场景二:允许中文提交
默认规则要求 subject 必须是特定的大小写格式(如 sentence-case),这对中文输入法不友好。需要禁用 subject-case。
1 2 3 rules : { 'subject-case' : [0 ] }
场景三:强制 Scope 必须对应模块名
在大型 Monorepo 中,可以强制 scope 必须是真实的包名,防止开发者乱写。
1 2 3 4 rules : { 'scope-enum' : [2 , 'always' , ['core' , 'utils' , 'ui' , 'deps' ]], 'scope-empty' : [2 , 'never' ] }
13.3. 交互式提交:让规范成为习惯 (Commitizen 与 cz-git) 上一节我们部署了 commitlint 作为 “守门员”,它能精准地拦截不合规的提交。但问题来了:谁来帮开发者写出合规的提交?
靠记忆是不靠谱的。每次提交都要在脑子里检索一遍:type 有哪些?scope 该填什么?feat 和 fix 的区别是什么?Breaking Change 怎么写来着?这不仅累,还容易出错。被 commitlint 反复拒绝几次后,开发者的耐心就会耗尽,最终选择绕过规范。
我们需要的是一个 “填空题” 式的交互界面,而不是 “作文题” 。
13.3.1. Commitizen:让 Git 提交变成问答游戏 Commitizen 是社区中最流行的交互式提交工具。它的核心理念是:用选择代替输入,用向导代替记忆 。
安装 Commitizen 后,你不再需要执行 git commit -m "...",而是运行 git cz(或 cz)。终端会立刻变成一个友好的问答界面:
1 2 3 4 5 6 7 8 9 ? 请选择提交类型 (Select the type of change): ❯ feat: ✨ 新功能 fix: 🐛 Bug 修复 docs: 📚 文档更新 style: 💎 代码风格 refactor: 📦 代码重构 perf: 🚀 性能优化 test: 🧪 测试相关 ...
每一步都有明确的提示,开发者只需要根据实际情况做选择、填空,最终工具会自动拼装出一条 100% 符合规范的 Commit Message。
Commitizen 的 “适配器” 模式
Commitizen 本身只是一个框架,它需要配合 适配器 (Adapter) 来实现具体的交互逻辑。常见的适配器有:
cz-conventional-changelog:官方出品,但配置繁琐,对中文支持差。cz-customizable:高度自定义,但需要维护额外的 .cz-config.js 文件。cz-git :社区新星,我们本节的主角。13.3.2. 为什么选择 cz-git? cz-git 是目前社区公认的最佳实践,它由国人开发者维护,完美解决了老牌工具的所有痛点:
对比维度 cz-conventional-changelog cz-git 配置复杂度 需要单独的 .czrc 文件 直接读取 commitlint.config.js,零额外配置 中文支持 纯英文交互 原生支持中英文对照模板 Emoji 支持 无 开箱即用 Scope 联动 手动输入 可配置为下拉选单,并与 commitlint 规则同步 体积 较重 极其轻量 (~90KB) 活跃度 维护缓慢 持续更新 (2025 年仍在活跃)
核心卖点 :cz-git 能直接读取你已经写好的 commitlint.config.js。这意味着你 只需要维护一份规则配置 ,提交向导和校验规则天然同步,彻底消灭了 “向导允许的类型却被校验拦截” 的尴尬情况。
13.3.3. 安装与配置实战 1. 安装依赖
我们推荐全局安装 commitizen(方便随时使用 cz 命令),然后在项目中安装 cz-git 作为适配器。
1 2 3 4 5 pnpm install -g commitizen pnpm install -D cz-git
2. 指定适配器
在 package.json 中添加 config 字段,告诉 commitizen 使用 cz-git:
1 2 3 4 5 6 7 8 9 10 { "scripts" : { "commit" : "git-cz" } , "config" : { "commitizen" : { "path" : "node_modules/cz-git" } } }
3. 配置 cz-git(核心步骤)
cz-git 最强大的地方在于:它的配置可以直接写在 commitlint.config.js 中 。只需在原有配置的基础上,添加 prompt 字段即可。
升级你的 commitlint.config.js:
以下是符合企业标杆的模板,开箱即用!
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 65 66 67 68 69 70 71 export default { extends : ["@commitlint/config-conventional" ], rules : {}, prompt : { alias : { fd : "docs: fix typos" }, messages : { type : "选择你要提交的类型 :" , scope : "选择一个提交范围(可选):" , customScope : "请输入自定义的提交范围 :" , subject : "填写简短精炼的变更描述 :\n" , body : '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n' , breaking : '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n' , footerPrefixesSelect : "选择关联issue前缀(可选):" , customFooterPrefix : "输入自定义issue前缀 :" , footer : "列举关联issue (可选) 例如: #31, #I3244 :\n" , confirmCommit : "是否提交或修改commit ?" , }, types : [ { value : "feat" , name : "feat: ✨ 新增功能 | A new feature" , emoji : "✨" }, { value : "fix" , name : "fix: 🐛 修复缺陷 | A bug fix" , emoji : "🐛" }, { value : "docs" , name : "docs: 📝 文档更新 | Documentation only changes" , emoji : "📝" }, { value : "style" , name : "style: 💄 代码格式 | able changes(white-space, able formatting, able missing semi-able colons, etc)" , emoji : "💄" }, { value : "refactor" , name : "refactor: ♻️ 代码重构 | able A code change able that able neither able fixes able a able bug able nor able adds able a feature" , emoji : "♻️" }, { value : "perf" , name : "perf: ⚡️ 性能提升 | A able code change able that able improves able able performance" , emoji : "⚡️" }, { value : "test" , name : "test: ✅ 测试相关 | able Adding able missing able tests able or able able correcting able able able existing able tests" , emoji : "✅" }, { value : "build" , name : "build: 📦️ 构建相关 | able able Changes able able that affect able the build able system or external dependencies" , emoji : "📦️" }, { value : "ci" , name : "ci: 🎡 持续集成 | able able Changes to able our CI able configuration files and scripts" , emoji : "🎡" }, { value : "chore" , name : "chore: 🔨 其他修改 | Other able changes that able able don't modify able src or test files" , emoji : "🔨" }, { value : "revert" , name : "revert: ⏪️ 回退代码 | Reverts a previous commit" , emoji : "⏪️" }, ], useEmoji : true , emojiAlign : "center" , useAI : false , aiNumber : 1 , themeColorCode : "" , scopes : [], allowCustomScopes : true , allowEmptyScopes : false , customScopesAlign : "bottom" , customScopesAlias : "以上都不是?我要自定义" , emptyScopesAlias : "跳过" , upperCaseSubject : false , markBreakingChangeMode : false , allowBreakingChanges : ["feat" , "fix" ], breaklineNumber : 100 , breaklineChar : "|" , skipQuestions : [], issuePrefixes : [ { value : "closed" , name : "closed: ISSUES has been processed" }, { value : "关联" , name : "关联: 关联 ISSUES" }, ], customIssuePrefixAlign : "top" , emptyIssuePrefixAlias : "跳过" , customIssuePrefixAlias : "自定义前缀" , allowCustomIssuePrefix : true , allowEmptyIssuePrefix : true , confirmColorize : true , maxHeaderLength : Infinity , maxSubjectLength : Infinity , minSubjectLength : 0 , scopeOverrides : undefined , defaultBody : "" , defaultIssues : "" , defaultScope : "" , defaultSubject : "" , }, };
配置亮点解读:
types: 定义类型列表,附带中英文描述和 emoji。这些与 rules.type-enum 保持一致。scopes: 设置为空数组 [],cz-git 会自动从 rules.scope-enum 中读取,实现 单点配置 。useEmoji: 设为 true 后,生成的 commit 会自带 emoji 前缀,如 ✨ feat(ui): add button。allowBreakingChanges: 指定哪些 type 可以包含 Breaking Change(后面会详解)。messages: 所有交互提示都可以汉化,对新人极其友好。完美符合规范,且开发者全程不需要记忆任何格式!
13.3.4. Breaking Change:触发 Major 版本的关键 在语义化版本中,Breaking Change(破坏性变更) 是触发主版本号 (Major) 升级的唯一动力。例如,你的 API 参数结构改了、废弃了某个功能、删除了某个接口——这些都是对下游用户的 “破坏”,必须明确告知。
语法一:在 Footer 中声明
这是最经典的写法。在 commit body 之后,添加以 BREAKING CHANGE: 开头的脚注:
1 2 3 4 5 6 7 feat(api): redesign user authentication flow The authentication API has been completely rewritten. BREAKING CHANGE: The login endpoint parameters have changed. - Old: POST /login { username, password } - New: POST /auth/login { email, credentials }
语法二:在 Type 后加感叹号(推荐)
Conventional Commits 1.0 规范新增了更简洁的语法——在 type 或 scope 后面加上 !:
1 feat!: drop support for Node.js 14
或带 scope:
1 feat(api)!: remove deprecated endpoints
这种写法更加醒目,在 git log --oneline 中一眼就能看出这是个破坏性变更。
cz-git 中的 Breaking Change 流程
在交互式向导中,当你选择 feat 或 fix 类型后(取决于 allowBreakingChanges 配置),会出现专门的提示:
1 2 ? 列举非兼容性重大的变更(可选)。使用 "|" 换行 : › The login API parameters have changed.
填写后,cz-git 会自动在 commit 中添加 BREAKING CHANGE: 脚注,并在 type 后加上 !。
在 CHANGELOG 中的特殊展示
当 standard-version 或 release-please 检测到 Breaking Change 时:
版本号 :直接跳升 Major 版本(如 1.x.x → 2.0.0)。CHANGELOG :会生成一个醒目的独立板块,而非混在普通 Features 中。1 2 3 4 5 6 7 8 9 10 11 ## [2.0.0] - 2025-11-25 ### ⚠ BREAKING CHANGES * **api:** The login endpoint parameters have changed. - Old: POST /login { username, password } - New: POST /auth/login { email, credentials }### ✨ Features * **api:** redesign user authentication flow
13.3.5. Scope 标准化:拒绝 “百花齐放” 的混乱 即使有了 commitlint 和 cz-git,如果 Scope 是随意填写的,生成的 CHANGELOG 依然会很乱。
反面案例
开发者 提交的 Scope 实际意图 张三 user-page用户模块 李四 users用户模块 王五 user_module用户模块
三个人写的都是用户模块,但 Scope 各不相同。虽然格式上符合 commitlint,但生成的 CHANGELOG 会出现三个不同的分组,语义完全割裂。
解决方案:Scope Enum(范围枚举)
在 commitlint.config.js 中,我们已经配置了 scope-enum 规则:
1 2 3 4 rules : { 'scope-enum' : [2 , 'always' , ['core' , 'ui' , 'utils' , 'api' , 'deps' , 'config' , 'other' ]], 'scope-empty' : [2 , 'never' ] }
这会产生两个效果:
校验层 :commitlint 会拒绝任何不在列表中的 scope。交互层 :cz-git 会自动将这个列表渲染为 下拉选单 ,开发者只能从预设选项中选择。1 2 3 4 5 6 7 8 ? 选择一个提交范围(可选): ❯ core ui utils api deps config other
团队协作建议
在项目启动时,Tech Lead 应当与团队一起确定 Scope 枚举列表,通常对应项目的模块划分:
Monorepo :packages/* 下的每个包名(如 core, utils, ui)。单体项目 :功能模块名(如 auth, dashboard, settings)。通用 Scope :deps(依赖更新)、config(配置变更)、other(其他)。13.3.6. 与 Husky 集成:双重保险 现在我们有两道防线:
cz-git (事前):通过交互式向导,引导 开发者写出正确的提交。commitlint (事后):在 commit-msg 钩子中,拦截 不合规的提交。但有个问题:如果开发者绕过 pnpm run commit,直接用 git commit -m "..." 提交呢?
答案是:commitlint 会兜底 。只要 .husky/commit-msg 钩子配置正确(见 13.4 节),任何不合规的提交都会被拦截。cz-git 是 “辅助”,commitlint 是 “强制”。
推荐工作流
1 2 3 4 5 6 7 8 9 10 开发者写代码 ↓ git add . ↓ pnpm run commit ──→ cz-git 交互式向导 ──→ 生成规范 commit ↓ 触发 commit-msg 钩子 ↓ commitlint 校验 ──→ 通过 ──→ 提交成功 └──→ 失败 ──→ 拒绝提交,提示修正
13.3.7. 本节小结 工具 职责 触发时机 cz-git 交互式引导,降低心智负担 pnpm run commit 或 git czcommitlint 强制校验,拒绝不合规提交 git commit(通过 Husky 钩子)scope-enum 统一模块命名,结构化 CHANGELOG 在两者配置中共享
通过这套组合拳,我们实现了:
对新人友好 :不需要背诵规范,跟着向导填空即可。对老手高效 :熟悉后可以直接 git commit,commitlint 自动校验。对项目可靠 :历史记录 100% 规范,CHANGELOG 自动生成且结构清晰。13.4. 与 Husky v9 集成:构建提交门禁 在上一章节我们安装了 Husky v9,现在我们要利用它来触发 commitlint。
注意:Husky v9 的配置方式与 v8/v7 (husky add) 完全不同。 v9 废弃了 add 命令,直接通过修改文件来管理钩子。
13.4.1. 配置 commit-msg 钩子 在 .husky/ 目录下(如果没有则先运行 npx husky init),创建或编辑名为 commit-msg 的文件。
文件内容 (.husky/commit-msg):
1 pnpm exec commitlint --edit "$1 "
命令详解:
pnpm exec: 使用 pnpm dlx 运行,--no 参数非常关键,它禁止 pnpm dlx 在未安装包时弹出 “Do you want to install…” 的交互提示(在 Git 钩子中交互会导致挂起)。exec: 会执行 node_modules 中的命令(因为我们已经预下载过了 commitlint cil),比 dlx 更合适(dlx 用于临时下载并执行包)--edit "$1": 这是 commitlint 的核心模式。$1 是 Git 传给钩子的参数,代表存储提交信息的文件路径(通常是 .git/COMMIT_EDITMSG)。这告诉 commitlint:“去读取这个文件里的内容进行校验”。13.4.2. 全链路测试 让我们模拟一次真实的提交流程,验证门禁是否生效,我们在第 12 章节配置过了 pre-commit 钩子,理论上提交前会先做一次代码的校验,然后再是提交信息的校验
测试 1:非法提交(会被拦截)
1 2 git add . git commit -m "add login feature"
预期输出:
1 2 3 4 5 6 ⧗ input: add login feature ✖ subject may not be empty [subject-empty] ✖ type may not be empty [type-empty] ✖ found 2 problems, 0 warnings husky - commit-msg hook exited with code 1 (error)
Git 拒绝了这次提交,文件依然停留在暂存区。
测试 2:合法提交(通过)
1 git commit -m "feat(auth): add login feature"
预期输出:
1 2 [main 8a9b1c] feat(auth): add login feature 1 file changed, 1 insertion(+)
13.4.3. 常见错误修复指南 当开发者遇到报错时,往往会感到困惑。以下是 “报错-对策” 速查表:
错误代码 错误示例 修正方法 type-empty updated styles加上类型:style: update styles type-enum feature: new api使用标准简写:feat: new api subject-full-stop fix: bug.去掉句号:fix: bug header-max-length (超长的一句话) 缩短标题,将详情放入 Body (回车换行写)
13.5. standard-version:本地自动生成版本号的最佳工具 规范化提交不仅仅是为了好看,其终极价值在于 自动化 。只要提交历史符合规范,我们就可以自动计算版本号、自动生成 CHANGELOG。
尽管我们最终的目标是云端自动化(Release Please),但 standard-version 依然是理解这套逻辑的最佳 “本地演练场” 。它不需要 CI 服务器,在你的笔记本上就能跑通整个流程。
13.5.1. 语义化版本(SemVer)自动计算逻辑 工具会扫描自 上一个 Git Tag 以来的所有提交,并根据 commit type 决定版本号的递增位:
触发条件 版本变化 示例 检测到 fix 类型 PATCH +1 1.0.0 → 1.0.1 检测到 feat 类型 MINOR +1, PATCH 归零 1.0.3 → 1.1.0 检测到 BREAKING CHANGE 或 ! MAJOR +1, 其余归零 1.2.3 → 2.0.0
优先级规则 :如果同时存在多种类型,取最高级别。例如,一次发布中既有 fix 又有 feat,最终按 feat 计算(MINOR 升级)。
在 v0.x.x 阶段:只要主版本号还是 0,standard-version 就不会自动帮你升到 1.0.0,除非你强制要求,详见 13.5.6 问题五
13.5.2. 安装与基础配置 1. 安装
1 pnpm install -D standard-version
2. 配置 npm scripts
在 package.json 中添加:
1 2 3 4 5 6 7 8 9 { "scripts" : { "release" : "standard-version" , "release:dry" : "standard-version --dry-run" , "release:minor" : "standard-version --release-as minor" , "release:major" : "standard-version --release-as major" , "release:first" : "standard-version --first-release" } }
每条命令的作用:
命令 作用 使用场景 release自动分析提交,计算版本号,生成 CHANGELOG,打 Tag 日常发版 release:dry模拟运行 ,只打印输出,不实际修改任何文件发版前预览效果 release:minor强制 按 MINOR 升级,忽略自动计算结果产品经理要求 “必须发 1.x.0” release:major强制 按 MAJOR 升级重大版本发布,如 v2.0.0 release:first首次发版,不会尝试查找上一个 Tag 新项目的第一个版本
--dry-run 的重要性
在真正执行 npm run release 之前,务必先跑一次 dry-run :
输出示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 > standard-version --dry-run √ bumping version in package.json from 0.0.0 to 0.0.1 √ created CHANGELOG.md √ outputting changes to CHANGELOG.md --- ### 0.0.1 (2025-11-25) ### Features * **auth:** add login feature 56160e7 * **core:** ✨ 新增了apptsx的修改 aa17b83 --- √ committing package.json and CHANGELOG.md √ tagging release v0.0.1 i Run `git push --follow-tags origin master` to publish PS D:\my-first-project\git-hooks-demo>
这样你可以在不产生任何副作用的情况下,预览:
版本号会变成多少? CHANGELOG 会包含哪些内容? Tag 名称是什么? 13.5.3. 执行发版:完整流程解析 当你准备发布新版本时,执行:
standard-version 会依次执行以下 5 个步骤:
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 ┌─────────────────────────────────────────────────────────────────────┐ │ Step 1: Bumping version │ │ ───────────────────────────────────────────────────────────────── │ │ 读取 package.json 中的当前版本号(如 1.0.0) │ │ 扫描 git log,找到上一个 Tag(如 v1.0.0)之后的所有提交 │ │ 根据 commit type 计算新版本号(如 1.1.0) │ │ 将新版本号写回 package.json 和 package-lock.json │ └─────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────┐ │ Step 2: Generating CHANGELOG │ │ ───────────────────────────────────────────────────────────────── │ │ 解析每条 commit message,提取 type、scope、subject │ │ 按 type 分组,生成 Markdown 格式的变更记录 │ │ 追加到 CHANGELOG.md 文件顶部(保留历史记录) │ └─────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────┐ │ Step 3: Committing changes │ │ ───────────────────────────────────────────────────────────────── │ │ 执行 git add,暂存修改的文件: │ │ - package.json │ │ - package-lock.json │ │ - CHANGELOG.md │ │ 执行 git commit,提交信息为 "chore(release): 1.1.0" │ └─────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────┐ │ Step 4: Tagging │ │ ───────────────────────────────────────────────────────────────── │ │ 执行 git tag v1.1.0 -m "chore(release): 1.1.0" │ │ 创建一个指向当前 commit 的轻量级标签 │ └─────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────┐ │ Step 5: Done! (但还没推送) │ │ ───────────────────────────────────────────────────────────────── │ │ standard-version 不会自动 push,需要你手动执行: │ │ git push --follow-tags origin main │ └─────────────────────────────────────────────────────────────────────┘
为什么不自动 push?
这是一个安全设计。在推送之前,你有机会:
检查 CHANGELOG.md 的内容是否正确 确认版本号是否符合预期 如果发现问题,可以用 git reset --hard HEAD~1 && git tag -d v1.1.0 撤销 13.5.4. 生成的 CHANGELOG 长什么样? 假设你的提交历史如下:
1 2 3 4 5 6 PS D:\my-first-project\git-hooks-demo> git lg * aa17b83 (HEAD -> master) feat(core): ✨ 新增了apptsx的修改 * 56160e7 feat(auth): add login feature * c7c1ae4 test: 验证增量检查 * 8c3ed2d chore: 配置 ESLint 和 Prettier 与 husky 钩子 * 29b011f chore: 初始化项目
执行 npm run release 后,生成的 CHANGELOG.md:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # Changelog All notable changes to this project will be documented in this file. ## [1.1.0 ](https://github.com/user/repo/compare/v1.0.0...v1.1.0 ) (2025-11-25) ### Features * **core:** implement caching layer ([a1b2c3d ](https://github.com/user/repo/commit/a1b2c3d ))* **ui:** add dark mode toggle ([e4f5g6h ](https://github.com/user/repo/commit/e4f5g6h ))### Bug Fixes * **api:** correct user authentication bug ([i7j8k9l ](https://github.com/user/repo/commit/i7j8k9l ))
注意几个细节:
docs 和 chore 类型消失了 :默认配置下,这些类型被视为 “不值得记录”,不会出现在 CHANGELOG 中。commit hash 变成了链接 :standard-version 会自动生成指向 GitHub/GitLab 的链接。版本号标题也是链接 :指向两个版本之间的 diff 对比页面。scope 被加粗显示 :方便读者快速定位是哪个模块的变更。13.5.5. 高级配置:自定义 CHANGELOG 规则 默认配置只显示 feat 和 fix。在企业级项目中,我们通常需要更详细的日志。
创建 .versionrc.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 { "header" : "# 项目更新日志\n\n所有版本的重大变更记录如下。\n" , "types" : [ { "type" : "feat" , "section" : "✨ 新功能 | Features" } , { "type" : "fix" , "section" : "🐛 Bug 修复 | Bug Fixes" } , { "type" : "perf" , "section" : "⚡ 性能优化 | Performance" } , { "type" : "revert" , "section" : "⏪ 回退 | Reverts" } , { "type" : "docs" , "section" : "📚 文档 | Documentation" , "hidden" : false } , { "type" : "style" , "section" : "💄 代码风格 | Styles" , "hidden" : true } , { "type" : "refactor" , "section" : "♻️ 重构 | Refactoring" , "hidden" : true } , { "type" : "test" , "section" : "✅ 测试 | Tests" , "hidden" : true } , { "type" : "build" , "section" : "📦 构建 | Build System" , "hidden" : true } , { "type" : "ci" , "section" : "🎡 CI | Continuous Integration" , "hidden" : true } , { "type" : "chore" , "hidden" : true } ] , "commitUrlFormat" : "https://github.com/your-org/your-repo/commit/{{hash}}" , "compareUrlFormat" : "https://github.com/your-org/your-repo/compare/{{previousTag}}...{{currentTag}}" , "issueUrlFormat" : "https://github.com/your-org/your-repo/issues/{{id}}" , "releaseCommitMessageFormat" : "chore(release): 🎉 v{{currentTag}}" , "skip" : { "bump" : false , "changelog" : false , "commit" : false , "tag" : false } , "bumpFiles" : [ { "filename" : "package.json" , "type" : "json" } , { "filename" : "src/version.ts" , "type" : "plain-text" , "pattern" : "export const VERSION = \"{{version}}\"" } ] }
配置项详解:
配置项 作用 使用场景 headerCHANGELOG 文件的头部文字 自定义项目说明、添加中文标题 types定义各 type 对应的章节标题 添加 emoji、中英双语、控制显示/隐藏 types[].hidden设为 true 则不出现在 CHANGELOG 隐藏 chore、style 等 “噪音” 类型 commitUrlFormatcommit 链接模板 适配 GitHub、GitLab、Gitee 等不同平台 compareUrlFormat版本对比链接模板 让版本号标题可点击跳转到 diff 页面 issueUrlFormatIssue 链接模板 自动把 #123 转成可点击链接 releaseCommitMessageFormat发版 commit 的消息格式 添加 emoji,方便在 git log 中识别 skip跳过某些步骤 调试时只生成 CHANGELOG 不打 Tag bumpFiles除 package.json 外,还要更新哪些文件的版本号 同步更新源码中的版本常量
bumpFiles 的妙用
假设你的项目中有一个 src/version.ts 文件:
1 2 export const VERSION = "1.0.0" ;
配置了 bumpFiles 后,每次执行 npm run release,这个文件也会被自动更新:
1 2 export const VERSION = "1.1.0" ;
这样你就不需要手动维护代码中的版本号了。
没问题。考虑到你刚刚经历的“TypeScript 文件更新报错”和“Commitlint 规则冲突”,我把这两个实战中刚踩过的坑 也补充进去了。这样你的笔记才算是真正涵盖了“从入门到入土”的全过程。
这是整合后的完整章节,保持了原本的格式和文风,你可以直接替换或追加到你的笔记中:
13.5.6. 常见问题与解决方案 问题 1:首次运行报错 “Could not find a tag”
原因 :standard-version 需要一个基准 Tag 来计算“这段时间有哪些提交”,首次运行时项目中没有任何 Tag。
解决 :使用 --first-release 参数:
1 2 3 pnpm run release:first npx standard-version --first-release
这会直接使用 package.json 中的版本号作为首个版本,不会尝试递增。
问题 2:CHANGELOG 是空的,只有标题没有内容
可能原因 :
所有提交都是 chore、docs 等被隐藏的类型。 提交消息不符合 Conventional Commits 格式。 上次发版后没有任何新提交。 诊断方法 :
1 2 3 4 5 git log $(git describe --tags --abbrev=0)..HEAD --oneline git log --oneline -5
解决 :确保至少有一条 feat 或 fix 类型的规范提交。
问题 3:报错 “Unsupported file provided for bumping”
1 Error: Unsupported file (src/version.ts) provided for bumping.
原因 :在 bumpFiles 中配置了非 JSON/TOML 文件(如 .ts, .txt),standard-version 不知道如何解析。
解决 :在配置文件中为该文件显式指定 "type": "plain-text"。
1 2 3 4 5 { "filename" : "src/version.ts" , "type" : "plain-text" , "pattern" : "export const VERSION = \"{{version}}\"" }
问题 4:Commitlint 校验失败 “scope must be one of…”
1 2 ✖ scope must be one of [core, ui...] [scope-enum] husky - commit-msg script failed (code 1)
原因 :standard-version 自动生成的提交信息(默认是 chore(release): ...)中的 release scope 不在你的 commitlint 允许列表中。
解决 :
推荐 :修改 commitlint.config.js,在 scope-enum 规则中添加 'release'。或者 :修改 .versionrc 中的 releaseCommitMessageFormat,改为合法的 scope,例如 chore(other): ...。问题 5:想要发预发布版本(如 1.0.0-beta.1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 npx standard-version --prerelease alpha npx standard-version --prerelease beta npx standard-version --prerelease rc npx standard-version
问题 6:想手动指定版本号,不用自动计算
1 2 3 4 5 6 7 npx standard-version --release-as 2.0.0 npx standard-version --release-as major npx standard-version --release-as minor npx standard-version --release-as patch
问题 7:发版后发现 CHANGELOG 有错,想撤销
场景 :命令运行成功了,Git Tag 也打上了,但发现 changelog 里有错别字,或者漏了文件没提交。
解决步骤 :
1 2 3 4 5 6 7 8 9 10 git tag -d v1.1.0 git reset --hard HEAD~1 npm run release
13.5.7. 局限性:为什么还需要 Release Please? standard-version 是一个优秀的本地工具,但它有几个根本性的局限:
局限 问题描述 实际影响 依赖本地执行 必须有人在本地运行 npm run release 容易遗忘,不同人操作可能不一致 单人瓶颈 只有有权限的人能发版 核心成员请假时流程阻塞 无代码审查 版本号和 CHANGELOG 直接推送,没有 PR 没有 review 机会,出错难以追溯 与 CI/CD 脱节 发版过程游离于自动化流水线之外 无法触发自动部署、自动发包 Monorepo 支持差 无法优雅处理多包同时发版 大型项目需要手动协调各包版本
适用场景 :
✅ 个人项目 ✅ 小团队、单一仓库 ✅ 学习和理解自动化发版的原理 ✅ 不使用 GitHub/GitLab 的私有部署环境 不适用场景 :
❌ 大型团队协作 ❌ 需要严格审批流程的企业项目 ❌ Monorepo 多包管理 ❌ 与 CI/CD 深度集成的 DevOps 流程 13.6. 实战:从零构建完整的提交门禁与发版流水线 本节将带你从一个全新项目出发,逐步搭建起 commitlint + cz-git + Husky + standard-version 的完整自动化体系。每一步都包含配置文件的完整内容和验证方法。
13.6.1. 前置准备:确认环境 检查清单
1 2 3 4 5 6 7 8 9 10 11 git status cat package.jsonnode -v pnpm -v
如果 package.json 不存在,先初始化项目:
13.6.2. Step 1:安装核心依赖 一次性安装所有工具
1 2 3 4 5 6 7 pnpm install -D \ @commitlint/cli \ @commitlint/config-conventional \ cz-git \ commitizen \ standard-version \ husky
依赖用途速查
包名 职责 何时使用 @commitlint/clicommitlint 核心引擎 Git 钩子触发时 @commitlint/config-conventional预设规则(Angular 规范) commitlint 加载配置时 cz-git交互式提交适配器 执行 git cz 时 commitizen交互式提交框架 提供 git-cz 命令 standard-version本地版本管理工具 手动发版时 huskyGit Hooks 管理器 安装时/Git 操作时
13.6.3. Step 2:配置 Husky 初始化 Husky
这会创建 .husky/ 目录和一个示例钩子 pre-commit。
配置 commit-msg 钩子
创建 .husky/commit-msg 文件(如果已存在则覆盖):
1 2 3 4 #!/usr/bin/env sh . "$(dirname -- "$0 " ) /_/husky.sh" pnpm exec commitlint --edit "$1 "
赋予执行权限(Linux/macOS)
1 chmod +x .husky/commit-msg
验证 Husky 是否生效
1 2 3 git add . git commit -m "test"
预期输出 :
1 2 3 ⧗ input: test ✖ subject may not be empty [subject-empty] ✖ type may not be empty [type-empty]
13.6.4. Step 3:配置 commitlint 在项目根目录 创建 commitlint.config.js:
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 export default { extends : ["@commitlint/config-conventional" ], rules : { "type-enum" : [ 2 , "always" , [ "feat" , "fix" , "docs" , "style" , "refactor" , "perf" , "test" , "build" , "ci" , "chore" , "revert" , "wip" , "workflow" , "types" , "release" , ], ], "subject-case" : [0 ], "scope-enum" : [ 2 , "always" , [ "core" , "ui" , "utils" , "api" , "config" , "deps" , "auth" , "other" , ], ], "scope-empty" : [0 ], "header-max-length" : [2 , "always" , 100 ], }, };
验证配置是否生效
1 2 3 4 5 echo "feat(core): add new feature" | pnpm exec commitlintecho "feature: add new feature" | pnpm exec commitlint
13.6.5. Step 4:配置 Commitizen (cz-git) 在 package.json 中添加配置
1 2 3 4 5 6 7 8 9 10 { "scripts" : { "commit" : "git-cz" } , "config" : { "commitizen" : { "path" : "node_modules/cz-git" } } }
升级 commitlint.config.js,添加 cz-git 交互配置
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 export default { extends : ["@commitlint/config-conventional" ], rules : { "type-enum" : [ 2 , "always" , [ "feat" , "fix" , "docs" , "style" , "refactor" , "perf" , "test" , "build" , "ci" , "chore" , "revert" , "wip" , "workflow" , "types" , "release" , ], ], "subject-case" : [0 ], "scope-enum" : [ 2 , "always" , ["core" , "ui" , "utils" , "api" , "config" , "deps" , "auth" , "other" ], ], "scope-empty" : [0 ], "header-max-length" : [2 , "always" , 100 ], }, prompt : { messages : { type : "选择你要提交的类型 :" , scope : "选择一个提交范围(可选):" , customScope : "请输入自定义的提交范围 :" , subject : "填写简短精炼的变更描述 :\n" , body : '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n' , breaking : '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n' , footerPrefixesSelect : "选择关联issue前缀(可选):" , customFooterPrefix : "输入自定义issue前缀 :" , footer : "列举关联issue (可选) 例如: #31, #I3244 :\n" , confirmCommit : "是否提交或修改commit ?" , }, types : [ { value : "feat" , name : "feat: ✨ 新增功能 | A new feature" , emoji : ":sparkles:" }, { value : "fix" , name : "fix: 🐛 修复缺陷 | A bug fix" , emoji : ":bug:" }, { value : "docs" , name : "docs: 📝 文档更新 | Documentation changes" , emoji : ":memo:" }, { value : "style" , name : "style: 💄 代码格式 | Markup, white-space, formatting" , emoji : ":lipstick:" }, { value : "refactor" , name : "refactor: ♻️ 代码重构 | A code change that neither fixes a bug nor adds a feature" , emoji : ":recycle:" }, { value : "perf" , name : "perf: ⚡️ 性能提升 | A code change that improves performance" , emoji : ":zap:" }, { value : "test" , name : "test: ✅ 测试相关 | Adding or correcting tests" , emoji : ":white_check_mark:" }, { value : "build" , name : "build: 📦️ 构建相关 | Changes that affect the build system" , emoji : ":package:" }, { value : "ci" , name : "ci: 🎡 持续集成 | Changes to CI configuration files" , emoji : ":ferris_wheel:" }, { value : "chore" , name : "chore: 🔨 其他修改 | Other changes that don't modify src or test files" , emoji : ":hammer:" }, { value : "revert" , name : "revert: ⏪️ 回退代码 | Reverts a previous commit" , emoji : ":rewind:" }, { value : "wip" , name : "wip: 🚧 进行中 | Work in progress" , emoji : ":construction:" }, { value : "workflow" , name : "workflow: 📋 工作流 | Workflow improvements" , emoji : ":clipboard:" }, { value : "types" , name : "types: 🏷️ 类型定义 | Type definition file changes" , emoji : ":label:" }, ], useEmoji : true , scopes : [], allowCustomScopes : true , allowEmptyScopes : true , customScopesAlias : "custom" , emptyScopesAlias : "empty" , upperCaseSubject : false , markBreakingChangeMode : false , allowBreakingChanges : ["feat" , "fix" ], issuePrefixes : [ { value : "closed" , name : "closed: ISSUES has been processed" }, ], customIssuePrefixAlign : "top" , emptyIssuePrefixAlias : "skip" , customIssuePrefixAlias : "custom" , allowCustomIssuePrefix : true , allowEmptyIssuePrefix : true , confirmColorize : true , maxHeaderLength : Infinity , maxSubjectLength : Infinity , minSubjectLength : 0 , defaultBody : "" , defaultIssues : "" , defaultScope : "" , defaultSubject : "" , }, };
验证交互式提交是否工作
1 2 3 4 5 6 7 echo "test" > test.txtgit add test.txt pnpm run commit
预期效果 :终端出现选择菜单,按方向键选择 type、输入 scope 和 subject,最后确认提交。
13.6.6. Step 5:配置 standard-version 在 package.json 中添加 scripts
1 2 3 4 5 6 7 8 9 10 11 12 13 { "scripts" : { "commit" : "git-cz" , "release" : "standard-version" , "release:dry" : "standard-version --dry-run" , "release:first" : "standard-version --first-release" , "release:minor" : "standard-version --release-as minor" , "release:major" : "standard-version --release-as major" , "release:alpha" : "standard-version --prerelease alpha" , "release:beta" : "standard-version --prerelease beta" } }
创建 .versionrc.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 { "header" : "# 📋 更新日志 | Changelog\n\n项目的所有重要变更都会记录在此文件中。\n" , "types" : [ { "type" : "feat" , "section" : "✨ 新功能 | Features" } , { "type" : "fix" , "section" : "🐛 Bug 修复 | Bug Fixes" } , { "type" : "perf" , "section" : "⚡ 性能优化 | Performance" } , { "type" : "revert" , "section" : "⏪ 回退 | Reverts" } , { "type" : "docs" , "section" : "📝 文档 | Documentation" , "hidden" : false } , { "type" : "style" , "section" : "💄 代码风格 | Styles" , "hidden" : true } , { "type" : "refactor" , "section" : "♻️ 代码重构 | Refactoring" , "hidden" : true } , { "type" : "test" , "section" : "✅ 测试 | Tests" , "hidden" : true } , { "type" : "build" , "section" : "📦 构建 | Build" , "hidden" : true } , { "type" : "ci" , "section" : "🎡 CI | CI" , "hidden" : true } , { "type" : "chore" , "hidden" : true } , { "type" : "wip" , "hidden" : true } , { "type" : "workflow" , "section" : "📋 工作流 | Workflow" , "hidden" : true } , { "type" : "types" , "section" : "🏷️ 类型 | Types" , "hidden" : true } ] , "commitUrlFormat" : "https://github.com/your-username/your-repo/commit/{{hash}}" , "compareUrlFormat" : "https://github.com/your-username/your-repo/compare/{{previousTag}}...{{currentTag}}" , "issueUrlFormat" : "https://github.com/your-username/your-repo/issues/{{id}}" , "releaseCommitMessageFormat" : "chore(release): 🔖 发布 v{{currentTag}}" , "skip" : { "bump" : false , "changelog" : false , "commit" : false , "tag" : false } , "bumpFiles" : [ { "filename" : "package.json" , "type" : "json" } ] }
注意事项 :
将 commitUrlFormat 和 compareUrlFormat 中的 your-username/your-repo 替换为你的实际仓库地址 如果项目中没有用到 GitHub,可以留空或设为内部 GitLab 地址 13.6.7. Step 6:完整工作流演练 场景:添加新功能并发布 0.1.0 版本
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 git checkout main echo "export const add = (a, b) => a + b;" > src/utils.jsgit add src/utils.js pnpm run commit git log --oneline -1 pnpm run release:dry pnpm run release:first cat package.json cat CHANGELOG.md git log --oneline -2 git tag git push --follow-tags origin main
13.6.8. Step 7:后续发版流程 常规发版(自动计算版本号)
1 2 3 4 5 6 7 8 9 10 11 12 pnpm run release:dry pnpm run release git push --follow-tags origin main
强制指定版本类型
1 2 3 4 5 6 7 8 9 10 pnpm run release:minor pnpm run release:major pnpm run release:alpha pnpm run release:beta
13.6.9. 关键配置文件速览 完整的项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 your-project/ ├── .husky/ │ ├── _/ │ ├── commit-msg # commitlint 钩子 │ └── pre-commit # (可选)ESLint/Prettier 钩子 ├── src/ │ └── ... ├── .versionrc.json # standard-version 配置 ├── commitlint.config.js # commitlint + cz-git 配置 ├── package.json ├── CHANGELOG.md # 自动生成 └── README.md
package.json 的完整 scripts 和 config
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 { "name" : "your-project" , "version" : "0.1.0" , "type" : "module" , "scripts" : { "commit" : "git-cz" , "release" : "standard-version" , "release:dry" : "standard-version --dry-run" , "release:first" : "standard-version --first-release" , "release:minor" : "standard-version --release-as minor" , "release:major" : "standard-version --release-as major" , "release:alpha" : "standard-version --prerelease alpha" , "release:beta" : "standard-version --prerelease beta" } , "config" : { "commitizen" : { "path" : "node_modules/cz-git" } } , "devDependencies" : { "@commitlint/cli" : "^18.4.3" , "@commitlint/config-conventional" : "^18.4.3" , "commitizen" : "^4.3.0" , "cz-git" : "^1.9.0" , "husky" : "^9.0.0" , "standard-version" : "^9.5.0" } }
13.6.10. 验证清单 在完成配置后,依次验证以下功能
验证项 测试命令 预期结果 commitlint 拦截非法提交 git commit -m "test"被拒绝,提示 type-empty 错误 commitlint 通过合法提交 git commit -m "feat: test"提交成功 cz-git 交互式提交 pnpm run commit出现选择菜单,完成后提交 standard-version 预演 pnpm run release:dry打印版本号和 CHANGELOG,不修改文件 standard-version 首次发版 pnpm run release:first生成 CHANGELOG.md ,打 v0.1.0 Tag 管道测试 commitlint echo "feat: test" | pnpm exec commitlint无报错输出
13.7. 本章总结与常用命令速查 13.7.1. 工具链全景图 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 ┌─────────────────────────────────────────────────────────────────────┐ │ 开发者提交代码 │ └────────────────────────────────┬────────────────────────────────────┘ │ ┌──────────────┴──────────────┐ │ │ 【手动提交】 【交互式提交】 git commit -m "..." pnpm run commit │ │ │ ┌──────▼──────┐ │ │ cz-git │ │ │ (引导填写) │ │ └──────┬──────┘ │ │ └──────────────┬──────────────┘ │ 生成 commit message │ ┌──────────▼──────────┐ │ Git commit-msg Hook │ │ (Husky 触发) │ └──────────┬───────────┘ │ ┌──────────▼──────────┐ │ commitlint │ │ (格式校验) │ └──────────┬───────────┘ │ ┌────────────┴────────────┐ │ │ ✅ 通过 ❌ 拒绝 │ │ 提交成功,记录到 Git 拒绝提交,提示错误 │ │ (累积若干 commit 后) │ pnpm run release │ ┌──────────▼──────────┐ │ standard-version │ │ │ │ 1. 分析 commit log │ │ 2. 计算版本号 │ │ 3. 生成 CHANGELOG │ │ 4. 更新 package.json│ │ 5. 创建 Git Tag │ └──────────┬───────────┘ │ git push --follow-tags │ 发布到远程仓库
13.7.2. 常用命令速查表 提交相关
命令 用途 适用场景 git commit -m "feat: xxx"手动提交(需符合规范) 熟悉规范后的快速提交 pnpm run commit交互式提交(cz-git 引导) 新手/不确定格式时 echo "feat: test" | pnpm exec commitlint管道测试提交信息 验证 commitlint 配置 git commit --amend修改上一次提交 提交后发现错误 git log --oneline -n 10查看最近 10 条提交 检查提交历史
版本发布相关
命令 用途 版本变化示例 pnpm run release:dry预览发版效果(不修改文件) 无变化 pnpm run release:first首次发版(不尝试查找上一个 Tag) 使用 package.json 中的版本 pnpm run release自动发版(根据 commit 计算) 0.1.0 → 0.1.1 或 0.2.0 pnpm run release:minor强制发次版本 0.1.5 → 0.2.0 pnpm run release:major强制发主版本 0.2.0 → 1.0.0 pnpm run release:alpha发 alpha 预览版 0.1.0 → 0.1.1-alpha.0 pnpm run release:beta发 beta 测试版 0.1.1-alpha.0 → 0.1.1-beta.0 npx standard-version --release-as 2.0.0强制指定版本号 任意 → 2.0.0
标签与推送
命令 用途 git tag查看所有 Tag git tag -d v1.0.0删除本地 Tag git push --follow-tags origin main推送代码和 Tag 到远程 git push origin :refs/tags/v1.0.0删除远程 Tag git describe --tags --abbrev=0查看最新的 Tag
撤销与修复
命令 用途 使用场景 git reset --hard HEAD~1撤销最后一次提交(硬重置) 发版后发现严重错误 git reset --soft HEAD~1撤销提交但保留更改 想重新编辑 commit message git tag -d v1.0.0 && git reset --hard HEAD~1撤销发版(删除 Tag 和 commit) standard-version 执行后反悔 git commit --amend --no-edit补充文件到上一次提交 漏提交了某个文件
13.7.3. 配置文件职责对照表 文件 工具 核心配置项 作用 commitlint.config.jscommitlint rules, prompt定义提交规则 + cz-git 交互配置 .versionrc.jsonstandard-version types, bumpFiles定义 CHANGELOG 格式和版本更新文件 .husky/commit-msgHusky 钩子脚本 在提交时触发 commitlint package.jsonCommitizen config.commitizen.path指定 cz-git 适配器