Skip to content

Latest commit

 

History

History
523 lines (344 loc) · 16.4 KB

README.md

File metadata and controls

523 lines (344 loc) · 16.4 KB

standard-version: 以git提交为核心的发布工作流

关于记录程序变更日志

Situation

我们自己每次发布之后都可能会想起来,好像忘了写发布日志 (︶︹︺),这次发布都写了些啥?呵呵 ╮(╯▽╰)╭。

而当看到别人发布程序之后,都会首先想看看都更新了些什么?神马,竟然什么都没说,难道还要我读源码吗?

Task

我们确认需要发布日志。这个需求可能来自领导,或是自我驱动。

其实对于业务开发,大多数的变更日志是需要单独编写面向用户的文档的,本文的内容可能并不适用。

但对于基于npm平台发布的项目,直接将变更日志集成到项目首页的README.md文档中就十分必要了,而且这也是一个行业通用惯例。

Action

              需要规范吗?
              ↙      ↘
          不需要       需要
          ↙              ↘
  跌入手动处理的深渊        如何写
      ↙                  ↙    ↘
game over  ←  ←  自己随便写     规范选型

为了日志的自动化生成,我们需要做一些调研工作---规范选型

Result

Result目标是自动化版本升级并自动生成更新日志。

概念讲解

概念 I semver —— 语义版本

semver,全称为Semantic Versioning,它既是一个概念,也是一个npm工具,该npm工具就是这个概念的程序实现。

我们在安装一个npm package之后,该package会自动写入项目的package.json文件中。

比如这样

  "prettier": "^1.18.2" 

左边的部分是npm包的名字,右边是版本号,一目了然。

数字部分版本号解释

① 符号^,版本范围提示符,文档里叫Advanced Range Syntax

该符号是使用yarn/npm安装的版本约定的默认行为。锁定从左起第一个非0的版本号,之后的版本号都可以升级。参考

^1.2.3 := >=1.2.3 <2.0.0
^0.2.3 := >=0.2.3 <0.3.0
^0.0.3 := >=0.0.3 <0.0.4

此外还有一些预发版本号,例如alphabeta这些在首个稳定版本发布之前的试用版本号,大家可以详细阅读文档。

数字部分不能按数字来理解,其中的.不是数学上的小数点,只是起分割符作用。

经过.分割之后的三部分分别为,主版本号、次版本号、修订版本号,参考

  1. 主版本号: 当前程序经过重构,生成了与之前版本不兼容的api,则主版本号升级。例如angular的各个大版本,vue的1、2、3版本。

  2. 次版本号: 也可以叫功能版本号,每次在没有破坏上一个版本api调用方法的情况下,扩展了新api或添加了其他新功能,升级的就是次版本号。

  3. 修订版本号: 每次bug修正引起的升级,即升级修正版本号,修订版本号的变化既不会引起api调用的变化,也没有新的扩展功能,对于使用者来说,仅升级修订版本号是必要且安全的。

概念 II GIT提交格式

为了配合上面讲述的semver,对于git的提交描述就有了一系列的详细规则。

我们在使用git commit时,需要填写的提交内容,需要符合以下格式。

  type(scope): subject
  // 空行
  body
  // 空行
  footer

必填项: typesubject

type含义
  • type是一个枚举类型,业界通用的选项如下
枚举值 含义
build 构建相关
ci 持续集成相关
chore 其他情况
docs 文档
feat 特性
fix bug修正
perf 性能优化
refactor 和特性修正无关的重构,例如重命名
revert 由于之前的某个错误提交,生成恢复代码的一次提交
style 编码风格相关
test 测试

参考 阮一峰教程 Commit message 和 Change log 编写指南,其中第四、五部分内容已经过期,已有现成的成熟工具,不再需要手动处理。

  • subject: 关于当前提交的一个最简化描述

选填项: scopebodyfooter

  • scope 当前软件项目的内部分类名,没有固定选项值,按需填写,例如core、cli、rules。

  • body 详细描述,根据实际需要填写即可,支持markdown格式。

推荐body写成markdown格式,在最终的CHANGELOG.md文件中显示的会更好看。

feat(cli): 添加命令行参数-p

* -p后面可以指定一个名称,例如`-p prjectA`
* -p后面可以指定多个名称,用`,`分割,例如`-p prjectA,projectB`
  • footer,只包含两种情况:

    • 不兼容变动,例如: BREAKING CHANGE: api方法改名.

    • 关闭Issue,例如: Closes #234

在讲解之后概念之前,将结合一个示例工程应用上述概念

🍧 在该示例中大部分情况使用yarn代替npm

上面讲解了 semver版本号管理git提交规则 这两个概念之后,以下用一个实际工程引入一些工具演示在生产中如何使用?

工程示例

先准备示例项目

初始化工程
mkdir example
cd example
yarn init -y
git init
echo node_modules > .gitignore

将git提交按上面的提交规范起来

首先需要把我们的git提交规范起来。

我一直相信的一个核心思想是,人是靠不住的,必须用工具来统一约束。

对团队如此,对个人也是如此,人的惰性与惯性需要靠外置工具来约束或修正。

git提交内容校验工具: commitlint
安装`commitlint`系列工具,并添加配置

安装,并添加配置commitlint.config.js,放到项目根目录。

# 安装工具
yarn add -D commitlint @commitlint/prompt-cli @commitlint/config-conventional
# 添加配置
echo "module.exports = { extends: ['@commitlint/config-conventional'] }" > commitlint.config.js

安装完毕后,用命令实验,会出现规则校验失败的提示。

echo 'xxx: yyy' | npx commitlint

commitlint辅助工具commitizen

一个命令行下,用交互的方式生成合规的提交格式的工具,对于还不熟悉提交消息格式的人起到自动生成合规消息的作用,可有可无。

  • commitizen配套的规则包cz-conventional-changelog
安装`commitizen`与配套规则,并配置
# 安装工具
yarn add commitizen cz-conventional-changelog -D
# 添加配置
echo '{ "path": "cz-conventional-changelog" }' > .czrc

安装完毕之后,即可使用git-cz命令代替git commit提交。

运行`commitizen`
yarn git-cz

我们再尝试生成一次不合规的提交。

git commit -m 'xxxyyy'

🤔 为什么不合规的内容还是可以提交呢?

我们还需要一个把校验规则和每次的提交动作强制关联起来的环节。

概念III: git hooks

好比ReactVue组件的生命周期,在挂载前、后,都可以插入一些自定义行为,在git的这个概念上,该行为称为git hooks参考git hooks又分为 服务端 运行和 本地 运行,以下所讲的全都是 本地 运行hooks

githook工具介绍

commitlint绑定到gitcommit-msg提交钩子上,在每次生成提交前调用commitlint检测提交文字格式,不通过验证则无法生成提交。

安装yorkie:

yarn add yorkie

在package.json文件中添加提交消息验证

yorkie配置,在package.json中

"gitHooks": {
  "commit-msg": "npx commitlint -E GIT_PARAMS"
}

安装:

yarn add husky

husky配置,在package.json中

"husky": {
  "hooks": {
    "commit-msg": "npx commitlint -E HUSKY_GIT_PARAMS"
  }
},

两个工具都不错,我个人感觉husky的错误提示信息可能更好一些。

🐾 必须先将项目纳入git管理,再安装husky/yorkie,否则不会安装git hooks

而且husky安装时,如果当前目录没有建立git环境,安装时会有报错提示,如下所示:

husky > Setting up git hooks
fatal: 不是一个 git 仓库(或者直至挂载点 /mnt 的任何父目录)
停止在文件系统边界(未设置 GIT_DISCOVERY_ACROSS_FILESYSTEM)。
husky > Failed to install
关于`githooks`的补充说明

git hooks分为服务器hook和本地hook,此处讲的全部都是本地hook。

详细的hooks说明需要看官方文档,想不起来的时候,可以快速看一下当前项目里的.git/hooks文件夹,里面的文件就是当前本地git支持的hook,这些文件都是见名知意的。

localhooks

我们还可以在其他的git生命周期中注入hooks,例如pre-commit/pre-push自动运行测试等,测试不通过则阻止提交/推送。

概念IV: CHANGELOG

经过了之前这么多铺垫,终于回到最初变更日志这个目标上了。

为git提交内容添加各种校验,其目的就是为了生成可以具有固定规则从而可以被提取的日志记录,生成当前发布版本的CHANGELOG.md

安装:

yarn add conventional-changelog-cli conventional-changelog-conventionalcommits -D

生成日志:

npx conventional-changelog -p conventional -i CHANGELOG.md -s -r 0

conventional-changelog有很多可调整的参数,具体参考conventional-changelog文档即可。

执行之后,会自动生成更新日志CHANGELOG.md文件。

💡 每次需要发布(执行npm publish)之前,才需要生成changelog,不需要每次修改或提交都生成。

合战: 集成

工具: standard-version

除发布之外的全自动化工具

运行`standard-version`

安装:

yarn add standard-version

执行:

npx standard-version

运行示例

其运行流程如下:

收集上一次打tag的version到当前为止是否有feat和fix或BREAKING CHANGE
                  ↓
if 有BREAKING CHANGE,则自动升级major版本号
                  ↓
else if 有任何一个feat,则自动升级minor版本号
                  ↓
else if 只有fix,则自动升级patch版本号
                  ↓
            生成新版本号
                  ↓
将新版本号写入`package.json`
                  ↓
自动生成一个内容为`chore(release): x.x.x`的提交
                  ↓
      将最新的提交上添加版本`git tag`,与`pakcage.json`中的一致
                  ↓
根据收集的提交记录汇总,生成最新`CHANGELOG.md`

🐾 无论是公网npm平台还是内部npm平台,只要发布版本就无法被覆盖,但可以撤销。

可以使用npm publish --dry-run模拟发布流程,自动调用prepublishOnly来。

🐾 此处使用npm publish 而没有用yarn publish 因为yarn publish会有命令行交互,提示让用户输入新版本号,打断了自动流程。

🐾 1.0.0之前与之后的两种策略

  • Before 🚲: 所有小于1.0.0的版本都视为非正式版本,比如alphabeta版。即使有BREAKING CHANGE也不会升主版本号(因为软件还没有发布,也不会有什么破坏兼容问题)。所有feat都不会升minor版本号,只会升级patch版本号。

  • After 🛺: 按正式规则办事,按提交日志关键字升级。如果上次与本次发布之间没有可以升级的git tag,则会自动将patch版本号升级。

集成到npm发布流

package.json中添加发布hook

通常在prepublishOnly中添加,再打包编译,然后自动升级版本的发布流程。

{
  "scripts": {
    "release": "standard-version",
    "prepublishOnly": "yarn test && yarn build && yarn release" ② ③
  }
}

② 不要用prepublishprepublish在每次yarn add新依赖时也执行。

③ 将standard-version的执行放到测试与编译等步骤之后,否则如果先生成了版本号,但测试或编译失败了,需要手动git回滚,删除CHANGELOG内容,去掉git tag等一系列错误。

总结: 以上所有工具之间的关系

git release flow

用更简单一些的图来表示关系

㊧:最简化执行的流程 ———— ㊨: 添加的调用关系的详细流程

               ㊧                               ㊨
                                           before git提交
                                          自动调用git hook
                                           commintlint校验
                                                 ↓
            git提交                           git提交
               ↓                      ↙          ↓            ↘
               ↓                     ↓   before (npm publish)  ↓
               ↓                     ↓ 自动调用standard-version  ↓
               ↓                     ↓           ↓              ↓
               ↓                     ↓      自动提升版本          ↓
               ↓                     ↓   将新版本添加git tag      ↓
               ↓                      ↘   生成CHANGELOG.md     ↙
               ↓                                 ↓ 
               ↓                                 ↓ 
               ↓                                 ↓ 
          npm publish                         npm publish

最简化执行步骤:

㊀创建项目,添加git与npm环境。

# 创建项目
mkdir yourapp && cd yourapp
yarn init -y
git init
echo 'node_modules' > .gitignore

㊁安装工具

# 安装工具
yarn add commitlint @commitlint/prompt-cli @commitlint/config-conventional husky standard-version

㊂添加commitlint规则配置文件

echo "module.exports = { extends: ['@commitlint/config-conventional'] }" > commitlint.config.js

㊃增加配置到package.json

{
  "scripts": {
    "release": "standard-version",
    "prepublishOnly": "yarn release"
  },
  "husky": {
    "hooks": {
      "commit-msg": "npx commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}

㊄执行测试一下查看结果

npm publish --dry-run
# 查看结果
cat CHANGELOG.md
git tag

最后添加一些细节,就完成了🍻!

事后补充

针对于monorepo项目,在执行的时候添加-t参数来仅为当前子packagegit tag

  "scripts": {
    "build": "cross-env NODE_ENV=production webpack",
    "release": "standard-version -t \"@yourscope/packagename@\"",
    "prepublishOnly": "yarn build && yarn release"
  },