自动化CHANGELOG,持续集成
前言
最近在开发使用 Github Issue 编写 blog 的项目。目前还在持续开发中,已经 300+ commits,npm 上已经发布的版本迭代有 9个。后面仍然会继续维护,有许多准备增加的能供,需要优化的点,以及已知的需要重构的逻辑,应该还会有相当的bug需要修复。
抱着负责任的态度,个人认为需要一个渠道使得 Isubo 的使用者获知这些一系列的变动。因此,需要编写 CHANGELOG 记录每个项目版本的变动。
但是,由于目前还是一个人在开发此项目,精力有限,需要更多地聚焦在功能新增、优化、重构和Bug修复。
CHANGELOG 自动化是最终的答案!
自动化的优点
简化了 changelog 的创建流程:手动创建 changelog 可能需要花费大量的时间和精力,特别是对于大型项目来说。自动化创建 changelog 可以减少这种繁琐的工作,允许开发者更专注于编写代码和解决问题。
提高了 changelog 的准确性:手动创建 changelog 可能会出现遗漏或错误,因为开发者需要手动记录每个版本的更改。自动化创建 changelog 可以消除这些错误,因为它们会自动从版本控制系统中提取信息。
帮助团队更好地协作:自动化创建 changelog 可以帮助团队更好地协作和沟通。开发者可以更轻松地了解项目的演变历史和当前状态,从而更好地协调和分配任务。
提高了项目的可维护性:自动化创建 changelog 可以提高项目的可维护性。由于 changelog 可以自动生成,因此开发者可以更容易地了解每个版本的更改,从而更好地维护和更新代码。
总之,自动化创建 changelog 可以提高项目的开发效率、降低错误率、加强团队协作,从而提高项目的可维护性和稳定性。
CHANGELOG 的预期
页面是列表的结构,列表的子项是版本变动说明。下面是子项的结构:
1 | // 标题 |
主要由标题和内容两部分构成:
- 标题:版本号
- 内容:子列表描述变动,包含但不限于功能新增、Bug修复等等
自动化思路
1. Commits 规范
所谓巧妇难为无米之炊,所以得先有“米”才有 CHANGELOG,这里的“米”就是commits。
既然要描述新增的功能、修复的bug 和 优化等不同类型的实现,那些就要让这些实现对应的 commits 符合规范。
因此,第一步是了解现有那些 commits 的规范。选择需要遵循的规范或者基于它们指定符合预期的规范。
下面将分别详细介绍:Angular规范、Conventional Commits规范 和 Gitmoji规范。
2. 约束 Commit 编写
既有规范,则需要有 commit msg 的校验逻辑。使用 git-hooks
钩子拦截开发者输入的 commit msg,然后对其进行校验,打回或通过后提交commit到 git-log
。
下文将使用 husky 拦截 commit-msg
钩子,然后使用 Commitlint
校验。
3. 优化 Commit 编写
校验终归是校验而已。如果对所遵循的规范不熟悉,编写格式正确的 commit 存在一定的心智成本。或许该增加辅助工具降低心智成本,无需数值规范,按着步骤输入即可。
下文将分别介绍 2 款 prompt 工具辅助commit的编写,它们分别是 @commitlint/prompt-cli ↗ 和 Commitizen ↗。
4. 版本管理
CHANGELOG 的生成节点是版本的创建。由创建版本这个行为,产生 CHANGELOG 这个结果,因此得先处理如何进行版本管理这个问题。
制定合理的版本管理策略前,同样需要了解现有的版本管理规范,在前人路上汲取养分。同样是需要工具去约束,以版本创建的结果符合规范。
下文将介绍SemVer规范,版本管理工具 npm-version
和 standard-version
。包含2个工具的使用和各自工作的生命周期
5. CHANGELOG自动化
结合前面提到的 2 个版本管理工具,利用它们各自的生命周期,适时触发 CHANGELOG 的生成。
standard-version
的功能不限于版本管理,还兼具 CHANGELOG 的生成。以及与 standard-version
来自同一个工具集的 conventional-changelog-cli
。2 个都是比较常用的工具。
下文将 CHANGELOG自动化 详细价绍它们的安装、使用、配置。
工作流
1. 基于 main 分支创建 develop 分支(feat/xxx、fix/xxx、…);
2. commit 变动;
3. develop 分支内容开发完成,push分支;
4. 合并 develop 分支,触发 post-merge
Git 钩子;
5. 创建版本、创建 git-tag 和生成 CHANGELOG
6. 发布 git-tag 和 CHANGELOG 变动、build 源码并发布至 npm
Git Commit 规范
首先要解决的问题是
规范名称 | 描述 |
---|---|
Angular规范 ↗ | Angular规范是非常流行的Git Commit规范之一,拥有众多的用户和贡献者。它提供了一套完整的Git Commit规范。Angular规范要求Commit message必须包含三个部分:类型、范围和描述。类型可以是feat、fix、docs、style、refactor、test、chore等。范围是可选的,用于表示代码变更的影响范围。描述应该清晰地描述代码变更的内容。 |
Conventional Commits规范 ↗ | Conventional Commits是一种通用的Git Commit规范,它要求Commit message必须包含三个部分:类型、作用域和描述。类型可以是feat、fix、docs、style、refactor、test、build等。作用域是可选的,用于表示代码变更的影响范围。描述应该清晰地描述代码变更的内容。Conventional Commits还支持关键词,用于表示代码变更的重要性,例如:BREAKING CHANGE表示这个Commit会破坏向后兼容性。 |
Gitmoji规范 ↗ | Gitmoji是一种基于Emoji表情符号的Git Commit规范,它要求Commit message必须包含一个Emoji表情符号,用于表示代码变更的类型。例如::sparkles:表示新增功能,:bug:表示修复Bug,:pencil2:表示修改文档等等。Gitmoji规范还支持在Emoji后面添加一个简短的描述,用于更详细地描述代码变更的内容。 |
下面是每种规范的格式的列表:
Angular 规范
Angular 规范要求每个 commit message 都包含三个部分:Header、Body 和 Footer。其中,Header 包含一个必填字段和一个可选字段,必填字段为 Type,可选字段为 Scope。Body 和 Footer 都是可选的,用于提供更详细的信息。
Type 字段包含以下值:
feat
:新功能fix
:修复问题docs
:文档修改style
:代码格式修改,不影响代码逻辑refactor
:重构代码,既不修复错误也不添加功能perf
:性能优化test
:添加或修改测试代码build
:构建系统或外部依赖项修改ci
:持续集成修改chore
:其他修改,如修改构建流程或辅助工具等revert
:回滚到之前的提交
Angular 规范的格式为:
1 | <type>[(scope)]: <subject> |
其中,<type>
表示 commit 的类型,[scope]
表示 commit 的影响范围,<subject>
表示 commit 的简短描述,[body]
表示 commit 的详细描述,<footer>
表示 commit 的元信息,如关闭 issue、引入变更等。
Conventional Commits 规范
Conventional Commits 规范要求每个 commit message 都包含三个部分:Type、Scope 和 Subject。其中,Type 和 Subject 是必填的,Scope 是可选的。
Type 包含以下值:
feat
:新功能fix
:修复问题docs
:文档修改style
:代码格式修改,不影响代码逻辑refactor
:重构代码,既不修复错误也不添加功能perf
:性能优化test
:添加或修改测试代码build
:构建系统或外部依赖项修改ci
:持续集成修改chore
:其他修改,如修改构建流程或辅助工具等revert
:回滚到之前的提交feat!
: 不兼容的新功能fix!
: 不兼容的修复问题docs!
: 不兼容的文档修改style!
: 不兼容的代码格式修改refactor!
: 不兼容的重构代码perf!
: 不兼容的性能优化test!
: 不兼容的添加或修改测试代码build!
: 不兼容的构建系统或外部依赖项修改ci!
: 不兼容的持续集成修改chore!
: 不兼容的其他修改,如修改构建流程或辅助工具等
Conventional Commits 规范的格式为:
1 | <type>[scope]: <subject> |
其中,<type>
表示 commit 的类型,[scope]
表示 commit 的影响范围,<subject>
表示 commit 的简短描述,[body]
表示 commit 的详细描述,[footer]
表示 commit 的元信息。
Gitmoji 规范
以下是 Gitmoji 规范中一些常用的 emoji 和它们的含义:
- ✨
:sparkles:
:新增功能 - 🐛
:bug:
:修复 bug - 📚
:books:
:修改文档 - 💄
:lipstick:
:修改样式 - ♻️
:recycle:
:重构代码。 - ✅
:white_check_mark:
:修改测试用例 - 🔧
:wrench:
:修改构建过程或工具 - 🚀
:rocket:
:优化性能 - 💚
:green_heart:
:修改持续集成流程 - 📦
:package:
:修改构建过程 - ⏪
:rewind:
:撤销之前的提交
Gitmoji 规范的格式为:
1 | <gitmoji> <description> |
其中,<gitmoji>
是一个表情符号,表示 commit 的类型和含义,<description>
表示 commit 的简短描述 ,[body]
?: commit 的详细描述。
小结
从以上可以看出 Conventional Commits 规范 与 Angular 规范有比较多的相同之处。是的,Conventional Commits 规范借鉴了 Angular 规范。
事实上,Conventional Commits 规范的创始人 Tim Pope 是 Karma 团队的成员,他在开发 Karma 过程中使用了 Angular 规范,认为这个规范非常有用。因此,他在 Angular 规范的基础上,扩充和修改了一些内容,提出了 Conventional Commits 规范。可以说,Conventional Commits 规范是在 Angular 规范的基础上发展而来的,但是相对于 Angular 规范,Conventional Commits 规范更加通用,可以适用于更多的项目和开发语言。
以下是 Angular 规范和 Conventional Commits 规范在某些方面存在的一些差异,具体如下:
Header 格式不同:Angular 规范要求 Header 必须包含 Type 字段和可选的 Scope 字段,如 “feat(core): add new feature”;而 Conventional Commits 规范要求 Header 必须包含 Type 和 Subject 字段,Scope 字段是可选的,如 “feat: add new feature”。
Type 值的定义略有不同:Angular 规范和 Conventional Commits 规范都定义了一些 Type 值,但有些值的含义略有不同。例如,Conventional Commits 规范将 “build” 和 “ci” 两个 Type 值分别定义为构建和持续集成修改,而 Angular 规范将它们合并为 “build”。
Conventional Commits 规范定义了特殊的 Type 值:Conventional Commits 规范定义了一些特殊的 Type 值,如 “feat!” 和 “fix!”,用于表示不兼容的新功能或修复问题。而 Angular 规范没有这样的定义。
Gitmoji 规范的显著特点是供了大量的 emoji 来描述不同类型和目的的 Git 提交,这使得开发者可以更加准确地描述自己的 Git 提交,简单易懂,具有很强的可读性。Commit的格式在一定程度上与前两者有相似之处,从提出时间上看,它算是采百家之长的集大成者了。
Gitmoji 规范的制定者 Carlos Cuesta 在规范的 Github 页面上并没有明确提到他借鉴了哪些规范。然而,从 Gitmoji 规范的内容来看,它借鉴了一些其他的规范和标准,例如:
Emoji:Gitmoji 规范使用 Emoji 表情符号来表示不同类型的提交,这一做法和 Slack、微信等工具中使用 Emoji 表情符号的方式类似。
Semantic Versioning:Gitmoji 规范中使用了类似 Semantic Versioning 的方式来表示版本,例如 “:bookmark:” 表示打标签,”:bookmark: v1.0.0” 表示打了一个 v1.0.0 的标签。
Conventional Commits:Gitmoji 规范和 Conventional Commits 规范类似,都是使用 commit message 来描述代码库的变化。不过,Gitmoji 规范使用 Emoji 表情符号来表示不同类型的提交,而 Conventional Commits 规范使用文本标识符来表示。
Commit 语法检测
Commitlint ↗ 是一个用于检查 commit message 是否符合规范的工具,可以自定义规则和配置。它支持多种规范,如 Angular 规范、Conventional Commits 规范、ESLint 规范等。
Commitlint ↗ 的作用仅仅是检测 Commit 语法。还需要使用 Git Hook (commit-msg
)拦截 git commit
动作以达到强制执行规范的目的。
commit-msg
This hook is invoked by git-commit[1] and git-merge[1], and can be bypassed with the--no-verify
option. It takes a single parameter, the name of the file that holds the proposed commit log message. Exiting with a non-zero status causes the command to abort.The hook is allowed to edit the message file in place, and can be used to normalize the message into some project standard format. It can also be used to refuse the commit after inspecting the message file.
The default commit-msg hook, when enabled, detects duplicate
Signed-off-by
trailers, and aborts the commit if one is found.Refenrence: githooks - Hooks used by Git
Husky ↗ 是一个 Git hook 工具,它可以在 Git 执行特定操作时自动触发预定义的脚本。常用于配合 Commitlint 进行 commit message 的校验。与原生的 Git hook 相比,Husky 有以下优点:
易于使用:Husky 提供了简单易用的 API,可以轻松地在项目中添加和配置 Git hook。与原生的 Git hook 相比,Husky 的配置更加直观和简单,不需要手动编写脚本。
跨平台支持:Husky 可以在 Windows、Linux、macOS 等多个平台上运行,而原生的 Git hook 可能会因为操作系统和 shell 的不同而产生兼容性问题。
更强大的功能:Husky 支持多个 Git hook,可以在不同的 Git 操作时自动触发相应的任务。而原生的 Git hook 只支持有限的几个 hook,需要手动编写脚本来实现更复杂的功能。
安全性:Husky 的配置文件存储在项目的 package.json 文件中,这意味着可以将配置文件提交到代码仓库中进行版本控制,保证配置的安全性和一致性。而原生的 Git hook 需要手动将 hook 脚本添加到 .git/hooks 目录中,容易被意外覆盖或删除。
因此,接下来需要做的事情是,安装 Husky,配置 commit-msg
拦截git commit
动作,再安装 Commitlint 对拦截到的 commit 信息进行校验。
安装 Husky
1 | npm |
使用 Husky 安装 Git Hooks
1 | npx 调用 局部命令 husky |
💡 npx 是什么?
npx
是一个Node.js命令行工具,它提供了一种方便的方式来运行本地安装的Node.js包中的可执行文件。npx的作用是在不全局安装包的情况下,运行这些包中的命令。通常情况下,在运行命令行工具时,需要全局安装相关的包和依赖项。但是,这种方式可能会导致一些问题,例如不同版本的包之间的冲突,或者需要手动更新全局安装的包等。
npx
提供了一个解决方案,可以在不全局安装包的情况下,运行这些包中的命令。使用
npx
,可以直接在命令行中指定需要运行的包和命令,npx
将会自动查找并运行该包中的命令。例如,可以使用以下命令运行"create-react-app
"包中的命令来创建一个新的React应用程序:npx create-react-app my-app
在这个例子中,
npx
将在本地查找"create-react-app
"包,并运行它中的"create-react-app
"命令,然后使用"my-app
"作为应用程序的名称创建一个新的React应用程序。更多 npx 相关信息可参考:Npx | Run a command from a local or remote npm package
添加 prepare
脚本到 package.json
的 scripts
中,使得在新环境初始化项目时,自动安装 Git Hooks。
📢 此为可选操作,不做也不影响后续操作,但是推荐执行
1 | npm pkg set scripts.prepare="husky install" |
prepare (since npm@4.0.0)
Runs BEFORE the package is packed
Runs BEFORE the package is published
Runs on local “npm install” without any arguments
Run AFTERprepublish
, but BEFOREprepublishOnly
NOTE: If a package being installed through git contains aprepare
script, itsdependencies
anddevDependencies
will be installed, and the prepare script will be run, before the package is packaged and installed.Refenrence: How npm handles the “scripts” field
Husky 配置 Hooks 的方式如下
1 | npx husky add .husky/<git hook> "<command that needs to be executed when the hook is triggered>" |
至此,Git Hooks 的准备工作已经完成,commit-msg
钩子的配置要在 commitlint 安装完成后配置。
安装 Commitlint
@commitlint
是一个由多个相关包组成的集合,可以根据需要安装和配置这些包来实现不同的功能。
@commitlint
的核心包是@commitlint/cli
,它提供了命令行工具,用于检查提交信息是否符合规范。@commitlint/cli
可以通过命令行参数来指定规范,也可以通过配置文件来指定规范。例如,可以使用@commitlint/config-conventional
包来定义一个常规的提交信息规范,然后使用@commitlint/cli
来检查提交信息是否符合该规范。
除了@commitlint/cli
之外,@commitlint还提供了其他几个相关包,包括:
@commitlint/load
: 提供了一个函数,用于加载配置文件并解析它们,以便@commitlint/cli
可以使用它们进行检查。@commitlint/config-conventional
: 提供了一组常见的规范,用于检查常规的Git提交信息格式。@commitlint/config-angular
: 提供了一个用于检查Angular项目的提交信息规范。@commitlint/config-lerna-scopes
: 提供了一个用于检查Lerna项目的提交信息规范。- 更多相关包…
这些包可以根据具体需要进行安装和配置。
📢 根据当前的需求,接下来则安装 @commitlint/cli
和 @commitlint/config-conventional
(Conventional Commits 规范)
1 | npm |
添加 配置文件
@commitlint/cli
支持以下这些默认的配置文件名:
commitlint.config.js
.commitlintrc.js
.commitlintrc
.commitlintrc.json
.commitlintrc.yml
为了避免切换模块化语法问题,接下来使用 .commitlintrc.yml
作为配置文件
1 | # .commitlintrc.yml |
更多的配置项参考:Commitlint > Configuration
Husky + Commitlint
使用 Husky 设置 commit-msg
钩子执行 commitlint-cli
, 对 git commit
动作提交的信息进行校验。
1 | npx husky add .husky/commit-msg 'npx --no -- commitlint --edit ${1}' |
在这个命令中,--no
参数是用来禁用 npx
的默认行为的。
默认情况下,npx
会在运行目标命令之前检查本地是否已经安装了目标命令所在的包,如果没有安装,则会先安装该包,然后再运行目标命令。这种行为通常是有用的,因为它可以确保运行的命令使用的是最新的包版本,并且可以避免不同版本之间的兼容性问题。
但是,在某些情况下,我们可能不想让 npx
自动安装包,而是希望使用本地已经安装的包。在这种情况下,可以使用 --no
参数来禁用 npx
的默认行为,以便直接使用本地安装的包。
在这个具体的命令中,--no
参数用来禁用 npx
自动安装 commitlint
包,而是使用本地已经安装的 commitlint
包。
--edit ${1}
是用来编辑指定文件的第一个参数的提交信息,${1}
代表第一个参数的值,通常是一个文件路径。这个命令的作用是使用本地安装的 commitlint
包来检查指定文件的提交信息是否符合规范,并在编辑器中打开该文件,以便修改提交信息。
半自动编写 Commit
以下是使用 Markdown 表格输出 @commitlint/prompt-cli 和 Commitizen 的信息:
工具名称 | 描述 | npm周下载量(2023/07/17) |
---|---|---|
@commitlint/prompt-cli ↗ | 一个命令行交互式工具,用于帮助开发人员规范化提交信息。它使用 commitlint 配置文件中定义的规则来检查提交信息,确保它们符合预定的格式和风格。该工具还提供了一些提示,帮助开发人员更好地理解如何编写符合规则的提交信息。 | 67,802 |
Commitizen ↗ | 一个命令行交互式工具,用于帮助开发人员规范化提交信息。它使用预定义的提交信息模板来引导开发人员编写符合规则的提交信息,并根据模板中的规则进行验证。与@commitlint/prompt-cli不同的是,Commitizen不检查提交信息是否符合commitlint配置文件中定义的规则,而是依靠模板中的规则来确保提交信息的正确性。此外,Commitizen还提供了一些功能,例如自动填充提交信息,以帮助开发人员更快地编写提交信息。 | 917,033 |
1 | npm |
版本管理
Node.js 遵循的版本号命名规范是 语义化版本号(SemVer)规范。很多 Node.js 模块和库的版本号也同样如此。
SemVer 规范定义了一个三位数字的版本号,格式为 MAJOR.MINOR.PATCH
,其中:
MAJOR
:主版本号,表示不兼容的 API 变化或重大功能变化。MINOR
:次版本号,表示向后兼容的新功能添加。PATCH
:补丁版本号,表示向后兼容的 bug 修复。
除了这三位数字之外,SemVer 规范还可以包含一个预发布版本号和一个构建版本号。预发布版本号以连字符 -
开头,构建版本号以加号 +
开头,例如 1.2.3-beta+build.123
表示预发布版本号为 beta
,构建版本号为 build.123
。
预发布版本号可以使用以下标识符:
alpha
:表示内部测试版本或仍在开发中的不稳定版本,可能会包含较多的 bug,不建议用于生产环境。beta
:表示公开测试版本,已经完成了主要功能的开发,但仍需要进行测试和 bug 修复,建议用于测试环境和开发环境。rc
:表示候选版本(Release Candidate),已经完成了所有的功能开发和测试,可以用于生产环境,但仍需要进行最后的测试和验证。
需要注意的是,不同的项目可能会有自己的预发布版本号约定,以上标识符仅是语义化版本号规范中常见的预发布版本号标识符。在实际项目中,可以根据项目的特点和需求,自定义预发布版本号标识符。
常用命令
以下是常用的几个命令:
- 补丁(patch)预发布版本
1 | npm-version |
- 次(minor)预发布版本
1 | npm-version |
- 主(major)预发布版本
1 | npm-version |
- 基于当前预发布的
preid
自增预发布版号
1 | npm-version |
- 切换至下一个阶段的
preid
1 | npm-version |
- 正式发布
1 | 如果最初是以 npm version prepatch 开始 |
例子
下面以发布补丁的预发布版本为例,假定初始版本是 0.0.1
Step-1:更新补丁的 alpha
预发布版本
1 | npm version prepatch --preid=alpha |
Step-2:更新补丁的 alpha
预发布版本版号自增
1 | npm version prerelease |
Step-3:更新补丁的下一个阶段的预发布版本,beta
1 | 切换预发布版本至 beta |
Step-4:更新补丁的 beta
预发布版本版号自增
1 | 在beta上,自增预发布版号 |
Step-5:更新补丁的下一个阶段的预发布版本,rc
1 | 切换预发布版本至 rc |
Step-6:更新补丁的 rc
预发布版本版号自增
1 | 在rc上,自增预发布版号 |
Step-7: 发布正式版本
1 | npm version patch |
Npm-Version生命周期
在介绍 conventional-changelog-cli
使用的章节中,它配合了 package.json
的 scripts
脚本使用。
1 | # package.json |
通过 npm version <patch | minor | major ...>
命令生成 CHANGELOG。
❓ 为什么是 version
脚本?
❓ 为什么是 npm version
而不是 npm run version
?
下面将带着这两个疑问拆解 npm-version
的生命周期。
npm version <cmd>
在执行后,按顺序先后执行以下流程:
执行
preversion
脚本(如果有定义);更新
package.json
文件中的版本号;执行
version
脚本(如果有定义);提交版本更新;
创建 Git 标签;
执行
postversion
脚本(如果有定义);
例如,执行 npm version patch
命令会触发以下操作:
1. 执行 preversion
脚本(如果有定义):在执行版本更新操作之前执行 preversion
脚本。例如,如果在 package.json
文件中定义了以下 preversion
脚本:
1 | { |
则在执行 npm version patch
命令时,会先执行 npm run lint
命令,检查代码是否符合规范。
2. 更新 package.json
文件中的版本号:npm version patch
命令会将 package.json
文件中的版本号自动加1,并将新版本号写回 package.json
文件中。
3. 执行 version
脚本(如果有定义):在更新版本号之后执行 version
脚本。例如,如果在 package.json
文件中定义了以下 version
脚本:
1 | { |
则在执行 npm version patch
命令时,会执行 npm run build
命令,自动生成构建文件。
4. 提交版本更新:npm version patch
命令会自动执行 git add
和 git commit
命令,将更新后的 package.json
文件提交到 Git 仓库中。提交信息默认为 "v<new-version>"
,例如,如果新版本号为 1.0.1,则提交信息为 “v1.0.1”。
5. 创建 Git 标签:npm version patch
命令会自动执行 git tag
命令,为当前提交创建一个新的 Git 标签。标签名默认为 "v<new-version>"
,例如,如果新版本号为 1.0.1,则标签名为 “v1.0.1”。
6. 执行 postversion
脚本(如果有定义):在提交版本更新之后执行 postversion
脚本。例如,如果在 package.json
文件中定义了以下 postversion
脚本:
1 | { |
则在执行 npm version patch
命令并成功提交版本更新后,会执行 npm publish
命令,将新版本发布到 npm 仓库中。
上面提到了 standard-version
和 conventional-changelog-cli
,它们都被用于生成 CHANGELOG。
在使用 conventional-changelog-cli
时,生成 CHANGELOG 的命令被设置到了 package.json
中的 scripts.version
:
1 | # package.json |
小结
❓ 为什么是 npm version
而不是 npm run version
?
从上面可以知道,version
脚本的触发,是 npm-version
生命周期的一部分。所以可以通过 npm-version
命令更新版本的同时触发 version
脚本生成 CHANGELOG。
❓ 为什么是 version
脚本?
1 | conventional-changelog -p angular -i CHANGELOG.md -s \ |
这段命令包含了两部分:
生成 CHANGELOG,
conventional-changelog -p angular -i CHANGELOG.md -s
;将新版本对应的 CHANGELOG 变动添加到 Git 仓库的暂存区,
git add CHANGELOG.md
。
version
脚本的触发介乎于 “更新 package.json 文件中的版本号” 与 “提交版本更新” 之间。
生成 CHANGELOG 需要知道 新的版号。version
脚本的触发在 “提交版本更新” 之前,所以只要在 version
触发时将变动添加到Git仓库暂存区,“提交版本更新” 的 commit 就可以包含 CHANGELOG 的变动。
Standard-Version的生命周期
在上一节 Npm-version生命周期 中提到了 conventional-changelog-cli
,但是没有提到 standard-version
。因为 standard-version
有它自己的生命周期,而且在使用 standard-version
做版本号更新和 CHANGELOG 生成时,并不会触发 npm-version
的脚本(preversion
、version
和 postversion
)!
standard-version
的生命周期:
更新项目版本号;
生成 CHANGELOG;
提交版本 commit(包含 package.json的version 和 changelog的变动);
创建版本的
git-tag
;
📢 注意:上图中的这句命令并没有执行,仅仅是提示!
1 | Run `git push --follow-tags origin master && npm publish --tag prerelease` to publish |
standard-version
的生命周期中触发的脚本
prerelease
: 在任何事情发生之前执行。 如果prerelease
脚本返回非零退出代码(process.exit()
),版本控制将中止,但对进程没有其他影响。prebump/postbump
: 在版本更新之前和之后执行。 如果prebump
脚本返回一个版本号,将使用它而不是standard-version
计算的版本。prechangelog/postchangelog
: 在生成 CHANGELOG 之前和之后执行。precommit/postcommit
: 在提交版本 commit 之前和之后调用。pretag/posttag
: 在添加git-tag
步骤之前和之后调用。
Lifecycle Scripts
standard-version supports lifecycle scripts. These allow you to execute your own supplementary commands during the release. The following hooks are available and execute in the order documented:
prerelease
: executed before anything happens. If theprerelease
script returns a non-zero exit code, versioning will be aborted, but it has no other effect on the process.
prebump/postbump
: executed before and after the version is bumped. If theprebump
script returns a version #, it will be used rather than the version calculated bystandard-version
.
prechangelog/postchangelog
: executes before and after the CHANGELOG is generated.
precommit/postcommit
: called before and after the commit step.
pretag/posttag
: called before and after the tagging step.
📢 注意:*standard-version
触发的脚本是 strandard-version.scripts.*
,而不是 scripts.*
。*
小结
standard-version
并非基于 npm-version
实现,它与 npm-version
相互独立。standard-version
有自己的生命周期,与 npm-version
不重合,在 standard-version
工作过程中不会触发 npm-version
生命周期脚本(preversion
、version
和 postversion
)。
standard-version
的生命周期中,包含 4 个工作点:
更新项目版本号;
生成 CHANGELOG;
提交版本 commit(包含 package.json的version 和 changelog的变动);
创建版本的
git-tag
;
包含 9 个钩子脚本:
prerelease
prebump/postbump
prechangelog/postchangelog
precommit/postcommit
pretag/posttag
standard-version
与 npm-version
在 package.json
中定义脚本的格式不一样:
1 | npm-version |
CHANGELOG自动化
conventional-changelog-cli
conventional-changelog-cli
是一个命令行工具,用于生成符合规范的 changelog。它可以根据项目的 commit message 格式,自动解析 commit 信息,并将其转换为人类可读的 changelog。
这个工具的基本原理是将符合规范的 commit message 按照类型(type)和 scope 等信息进行分类,然后根据分类的结果生成 changelog。
conventional-changelog-cli
支持使用多种预设(preset)来生成 changelog,包括 angular
、atom
、codemirror
、conventionalcommits
、ember
、eslint
、express
、jquery
和 jshint
等。你也可以使用自定义的配置文件来生成 changelog。
以下是 conventional-changelog-cli
的一些常用命令:
conventional-changelog
: 生成 changelog,默认使用 Angular 规范。conventional-changelog -p [preset]
: 生成指定预设的 changelog。conventional-changelog -i [file]
: 将 changelog 写入到指定文件中。conventional-changelog -s
: 将 changelog 添加到文件的开头而不是结尾。conventional-changelog --release-count [number]
: 指定要包括的版本数量。conventional-changelog --config [file]
: 使用自定义的配置文件生成 changelog。
通过 conventional-changelog-cli
,你可以方便地生成符合规范的 changelog,并且可以根据自己的需要进行自定义配置和预设,以满足项目的需求。
📢 在默认情况下,conventional-changelog-cli
会匹配 feat
、fix
类型的commits,并根据它们生成CHANGELOG
npm install -g conventional-changelog-cli cd my-project conventional-changelog -p angular -i CHANGELOG.md -s
This will not overwrite any previous changelogs. The above generates a changelog based on commits since the last semver tag that matches the pattern of "Feature", "Fix", "Performance Improvement" or "Breaking Changes".
安装
1 | npm |
使用
1 | npx conventional-changelog -p conventionalcommits -i CHANGELOG.md -s |
-p conventionalcommit
:指定使用 conventionalcommit 规范生成 CHANGELOG 文件。conventionalcommit
是一种常见的规范,适用于大多数项目。-i CHANGELOG.md
:指定将生成的 CHANGELOG 文件输出到名为CHANGELOG.md
的文件中。如果该文件不存在,则会创建它;如果已存在,则会覆盖它。-s
:指定生成的 CHANGELOG 文件中是否应包含当前版本之前的所有版本的变更记录。默认情况下,只会生成当前版本的变更记录。
结合 npm version
使用
1 | package.json |
具体来说,当您执行 npm version
命令时,会按照以下顺序执行:
执行
scripts.version
脚本,如果已经定义的话。scripts.version
脚本会返回一个字符串,表示新的版本号,如果没有定义scripts.version
脚本,则使用默认的 SemVer 规范生成版本号。将新的版本号更新到 package.json 文件中。
自动化执行 Git 操作,包括添加修改的 package.json 和生成的 changelog 文件、提交代码并打 Git tag。
因此执行 npm version <version>
的结果是:1)更新 package.json
的 version
字段;2)更新 CHANGELOG;3)生成包含 1、2 的 commit 和 git-tag。
修改 version-commit
默认的 version-commit
是下面这样的:
1 | commit 1d37dcf6dc685d0a49319d0c2e0a0a272af8fa7a (tag: v3.3.8) |
显然这样是不符合规范的。下面有 2 个方法可是使之合乎规范。
手动设置
1 | %s 是版本号的占位符 |
配置文件设置
1 | .npmrc |
配置
从 conventional-changelog-cli
的 README 中没有太多关于配置的信息,仅仅是引导去查阅 conventional-changelog
和 conventional-changelog-core
。
从其他的一些参考资料确实有介绍 conventional-changelog-cli
是基于 conventional-changelog-core
开发的。
To fully customize the tool, please checkout conventional-changelog and conventional-changelog-core docs. You can find more details there. Note: config here can work with preset, which is different than options.config in conventional-changelog.
但是,实际上 conventional-changelog
并没有太多关于配置的信息,仅仅是一个类似一个一级引导页的README!
而 conventional-changelog-core
相对有用一点,会介绍 API 的参数,但是并没有明确那些参数是可以复用到配置文件上的。
使用 conventional-changelog --help
有看到关于配置相关的描述:
-n, --config
A filepath of your config script Example of a config script: https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-cli/test/fixtures/config.js
打开的是一个过于简单的页面,并没有注释介绍参数作用,有用的参考信息约等于没有!
1 |
|
综合以上,基本可以认识到一个事实:
conventional-changelog-cli
具备配置的能力,但是缺少配置指引,以致配置体验不友好。基本可以认为这个工具的配置能力约等于“无”
本着研究的态度阅读 conventional-changelog-cli
的源码,探索它的配置详情!
通过阅读 conventional-changelog-cli/cli.js
代码,发现以下5个配置项可以从从配置文件中读取:
options
templateContext
gitRawCommitsOpts
parserOpts
writerOpts
1 | try { |
在源码中可以知道, 以上 5 个配置项分别读取自不同的2个文件,它们分别是 context
配置文件 和 config
配置文件。
1 | // context |
context
配置文件:
templateContext
config
配置文件:
options
gitRawCommitsOpts
parserOpts
writerOpts
npx conventional-changelog --help
中关于 context
配置文件 和 config
配置文件的描述
-c, --context
A filepath of a json that is used to define template variables
-n, --config
A filepath of your config script. Example of a config script: https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-cli/test/fixtures/config.js
结合 --help
的描述和配置文件的引入方式(require
),可以推断 context
配置文件 和 config
配置文件的内容和语法。
context
配置文件
支持的配置项及其详情参考:conventional-changelog-writer > context
1 | /* |
使用context
配置文件:
1 | npx conventional-changelog --context conventional-changelog.context.js |
config
配置文件
支持的4个配置项及其详情参考:
gitRawCommitsOpts
: conventional-changelog/packages/git-raw-commits > gitoptsparserOpts
: conventional-commits-parser > optionswriterOpts
: conventional-changelog-writer > options
1 | // conventional-changelog.config.js |
使用config
配置文件:
1 | npx conventional-changelog --config conventional-changelog.config.js |
preset
Standard Version 是一个命令行工具,可用于自动生成符合语义化版本规范的版本标签和 CHANGELOG。它使用 Git 元数据(如提交消息)来确定下一个版本号,然后生成标签和更新日志。
standard-version
和 conventional-changelog-cli
都是基于 conventional-changelog
实现的工具。
但是有别于 conventional-changelog-cli
, standard-version
是明确支持配置文件,并且有较为详细的指引介绍如何配置(Standard Version > configuration)。
Configuration
You can configure
standard-version
either by:Placing a
standard-version
stanza in your package.json (assuming your project is JavaScript).
Creating a ‘.versionrc’, ‘.versionrc.json’ or ‘.versionrc.js’.
If you are using a.versionrc.js
your default export must be a configuration object, or a function returning a configuration object.
Any of the command line parameters accepted bystandard-version
can instead be provided via configuration. Please refer to the conventional-changelog-config-spec for details on available configuration options.
standard-version
配置文件的包含全部设置项的例子:
1 | // .versionrc.js |
两者都基于相同上游库开发,但是只有 standard-version
支持配置,而且是有别于上面 conventional-changelog-cli
介绍过的配置方式。这是为什么?
抱着这个疑问阅读 standard-version
相关的代码!
1 | // standard-version/lib/lifecycles/changelog.js |
🔬 通过“打印”的方式确认了是 presetLoader(args)
引入了 .versionrc.js
的配置内容
与上面例子有所区别是,在例子的基础上有增了 name
属性:
1 | { |
从 npx conventional-changelog --help
的描述上看,--preset
是指定 commit 规范。而 --preset
的设置值仅仅一个较短字符串,而不是上面探索到的对象形式的值。
-p, --preset
Name of the preset you want to use. Must be one of the following: angular, atom, codemirror, conventionalcommits, ember, eslint, express, jquery or jshint
从上面 standard-version
的源码可知道, preset
是对应到前文 conventional-changelog.config.js
中的 options.preset
,基于以上信息,进行下面的尝试。
1 | // conventional-changelog.config.js |
详细设置项参考:conventional-changelog-config-spec
结论是可以的!
但是,并不是全部属性都有效果!下面是几个尝试后得到的结论:
name
,必选设置,preset 的设置项生效的前提是们需要设置name
属性。这是测试出来的结果,没有继续深挖!name
属性在两种设置方式下有效:1)如上面文件的;2)name: 'conventional-changelog-conventionalcommits'
;header
,无效。查阅了standard-version
的源码。关于 header 的实现,是独立与conventional-changelog
的,所以不生效也正常。types
,正常有效的。其他,正常有效。
preMajor
commitUrlFormat
compareUrlFormat
issueUrlFormat
userUrlFormat
releaseCommitMessageFormat
issuePrefixes
小结
通过 preset 配置 conventional-changelog-cli
是从源码中得到的非正道的知识,在 conventional-changelog 的工具集中并没有相关的资料说明可以使用preset通过对象形式值去配置。所以这是一个不推荐在生产项目下使用的功能,是个不被保证的功能。
Standard Version
Standard Version 和 conventional-changelog-cli 都是用于自动生成版本更新和 CHANGELOG 的命令行工具。它们都是基于 conventional-changelog
事先。
Standard Version 除了能够生成 CHANGELOG 之外,还能够自动创建 Git 标签、增加版本号,以及自动推送标签到 Git 仓库等。conventional-changelog-cli 则只是生成 CHANGELOG 文件。
Standard Version 的社区支持度相对来说更高,有较多的用户和贡献者,开发维护更新也更加频繁。而 conventional-changelog-cli 的社区支持度相对较低,开发维护更新也不如 Standard Version 频繁。
安装
1 | npm |
使用
1 | { |
Standard Version 是推荐使用它来代替 npm version
进行版本管理的。
Standard Version 将版本管理与 CHANGELOG 结合在一起,在使用 standard-version
更新版本号时,会自动触发 CHANGELOG 的更新。
1 | release |
配置
默认的配置文件名是:.versionrc
, .versionrc.json
or .versionrc.js
详细设置项参考:conventional-changelog-config-spec
1 | // .versionrc.js |
工作流
1. 创建开发分支
1 | git fetch origin main |
2. Commit 变动
1 | 全手动commit,让commitlint检测 |
3. Push开发分支
1 | git push origin feat/xxx |
4. 合并开发分支
5. 版本管理
切换到主分支,并更新到最新的commit点
1 | git checkout main |
创建版本
1 | npx standard-version -r patch -p alpha |
6. 发布
推送commit 与 git-tag 到远端git仓库
1 | git push origin main && git push --tags |
发布到 npm
1 | 登录 |
附录
Husky支持的 Git hook
Husky 支持大部分 Git hook,以下是 Husky 支持的 Git hook 列表:
applypatch-msg
:在 Git 执行git am
命令时触发pre-applypatch
:在 Git 执行git am
命令前触发post-applypatch
:在 Git 执行git am
命令后触发pre-commit
:在 Git 执行git commit
命令前触发prepare-commit-msg
:在 Git 执行git commit
命令前触发,用于编辑提交信息commit-msg
:在 Git 执行git commit
命令后触发,用于验证提交信息post-commit
:在 Git 执行git commit
命令后触发pre-rebase
:在 Git 执行git rebase
命令前触发post-checkout
:在 Git 执行git checkout
命令后触发post-merge
:在 Git 执行git merge
命令后触发pre-push
:在 Git 执行git push
命令前触发pre-receive
:在 Git 执行git push
命令时,服务端接收到数据之前触发update
:在 Git 执行git push
命令时,服务端接收到数据之后触发post-receive
:在 Git 执行git push
命令后触发post-update
:在 Git 执行git push
命令后触发pre-auto-gc
:在 Git 执行自动垃圾回收之前触发post-rewrite
:在 Git 执行git filter-branch
和git commit --amend
命令后触发sendemail-validate
:在 Git 执行git send-email
命令前触发
以上 Git hook 具体作用可以参考 Git 的官方文档。Husky 可以通过在 package.json 文件的 husky.hooks
中定义相应的命令,来自动触发这些 Git hook。例如,在 husky.hooks
中定义 pre-commit
命令,就可以在每次执行 git commit
命令时自动触发该命令。
post-merge 钩子触发prompt失败
配置 post-merge 钩子,合并开发分支后触发版本更新逻辑。版本管理选择使用 @isubo-org/version
,它是prompt工具,选择的方式设定新版号。
配置 post-merge 钩子
1 | npx husky add .husky/post-merge 'npm run post-merge' |
配置post-merge脚本
1 | "scripts": { |
测试结果是:行不通!
合并后确实可以触发脚本,但是完全跳过选择流程