一步搞定项目changelog的生成和实时通知

技术
背景

一个好的项目通常都是多人合作的结果,当你在一个版本迭代后,想要对本次迭代复盘,了解哪些是新增功能点,哪些是项目原有功能的优化,你还在依赖翻阅 gitlab/github 的 history 记录来复盘吗?

2021年了,对这种繁琐且没有统计归类的复盘说 NO!

当前版本发布后,你想要让大家能及时了解到项目迭代内容,收到项目迭代推送,你还在手动组装语句,一个一个发送到你想要通知的 IM 里吗?如果需要通知的 IM 比较多,会有未通知到和阐述不准确的情况;同时阐述的模板不一致,阐述可能也无法具体到哪个项目哪个分支哪个版本;信息自动化时代,我们怎样做到定向精准投送呢?

一、解决方案

一份友好地更新日志(CHANGELOG.md),让用户和开发人员可以更好的知道每一个版本有哪些改动,是新增功能点还是项目原有功能的优化;同时在项目复盘时,更新日志提供了直观的复盘依据,方便快速浏览。

有了规范的更新日志,一个月后的你依然记得自己在某个迭代版本做了哪些工作。

规范的更新日志,对大家的 git commit message 做到了统一约束,统一 git commit message 提交方式使项目迭代内容更趋于工程统一化,一目了然。得物前端团队已经产出相应的实时提交约束工具库,约束遵循 Angular 规范,链接指向👉 https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit

提交约束规范如下:

<type>[optional scope]: <subject>

type具体类别如下:

feat:新功能(feature)                   
fix:修补bug 
docs:文档(documentation)           
style: 格式(不影响代码运行的变动)
refactor:重构(即不是新增功能,也不是修改bug的代码变动)
test:增加测试
chore:其他修改, 比如构建流程, 依赖管理      

使用示例:

feat: 支付二清商家入驻流程                  

项目发布后,为了让大家感知项目迭代内容,这时就需要统一规范的发布模板,外加一个能够自动实时通知的机器人帮你干这些累活,通知到你想要发布的IM。基于目前团队使用的 IM 是飞书,接入了飞书机器人,当项目发布后触发机器人,定向发布通知,做到即时通知。

从上述两个出发点,产出了内部工具库 @du/changelog-robot 。该库基于成熟的 conventional-changelog,根据本地 tags 归类生成对应的 CHANGELOG.md;并将更新日志原样输出给飞书机器人,实时通知到对应群组。

二、整体方案架构图

picture.image

【1.1】*

在用户 npm publish 的过程中,主要涉及 publish 过程中的两个钩子,prepublishOnly 和 postpublish 。

有了相应的钩子,我们就可以针对钩子触发的时间节点,对整个功能做大致分配。项目发布前生成CHANGELOG.md,项目发布后实时通知到对应群组。

如图【1.1】,整体方案分为2大模块,生成 CHANGELOG.md 模块和飞书机器人通知模块,两个模块独立存在,命令使用不会互相影响。

  • 生成 CHANGELOG.md 模块:该模块主要在 conventional-changelog 开源包的基础上,解决多人协同开发导致的 CHANGELOG.md 内容紊乱,并依据 npm version xxx 原理新增自动提交 CHANGELOG.md 功能。
  • 实时通知模块:该模块主要结合飞书机器人 api,把生成的 CHANGELOG.md 内容原样的通知到对应的飞书群。

三、方案实现

为了在项目发布前自动生成所需的 CHANGELOG.md 文档,并且在项目成功发布后实时自动在飞书群里进行通知,在调研 conventional-changelog 和飞书机器人后,设计了一套解决方案。方案分2个大模块,生成 CHANGELOG.md 模块和飞书机器人通知模块。

怎样生成 CHANGELOG.md

conventional-changelog 是一个成熟的工具包,用于根据模板生成相应的 CHANGELOG.md 。

conventional-changelog 生成文件流,主要依赖 git log ,获取对应 tag 下的所有 commit 信息,具体原理如下:

1、获取当前仓库下的所有 tags

var reverseTags = context.gitSemverTags.slice(0).reverse()               

2、形成可读流

var streams = reverseTags.map((to, i) => {       
  const from = i > 0
  ? reverseTags[i - 1]  
  : ''
  return commitsRange(from, to)                
})

3、commitsRange 方法是形成可读流的关键方法,方法通过 git log,根据你设置的模板生成对应的信息;其中args为数组。

  • args[0]: "log"
  • args[1]: "--format=%B%n-hash-%n%H%n-gitTags-%n%d%n-committerDate-%n%ci%n-authorName-%n%an%n-authorEmail-%n%ae%n" // git log 模板
  • args[2]: "v1.2.5" // 对应的tag号
  • args[3]: "--no-merges"
var child = execFile('git', args, {                   
    cwd: execOpts.cwd,
    maxBuffer: Infinity
  })

经过上面一段代码,实际上是在控制台执行:

git log --format=%B%n-hash-%n%H%n-gitTags-%n%d%n-committerDate-%n%ci%n-authorName-%n%an%n-authorEmail-%n%ae%n 换成你自己的版本号 --no-merges               

会得到当前 tag 下的所有提交信息:

1.2.30
                                         
-hash-
3c5949d1db635dcfe6fa4dc0331b9003ca8f091c                
-gitTags-
 (tag: v1.2.30)             
-committerDate-       
2020-10-25 14:33:03 +0800       
-authorName-
chengli          
-authorEmail-               
xxx@youremail.com
                      
fix: 测试changelog老功能-npm打tag,1.2.30,基于1.2.29打过tag
                      
-hash-                
d60c34320bff8fc807e4decd139755bd4b4c07a4                
-gitTags-
                      
-committerDate-             
2020-10-25 14:32:56 +0800       
-authorName-
chengli          
-authorEmail-               
xxx@youremail.com
                      
.... 

这些信息形成对应的可读流 streams,如图【2.1.1-1】。

四、Conventional-Changelog 工作流程

下面为了描述方便 以cc代替conventional-changelog。

cc 是根据git logs,和git commitMsg,自动生成版本信息;所以 cc 想要正常生成版本信息有个重要前提 —— git commitMsg要符合提交规范 && git logs有效。

cc 预设了很多配置项,详细可以看https://github.com/conventional-changelog/conventional- changelog/tree/master/packages/conventional-changelog-core

  1. cc 首先执行 git log --pretty ,拿到本地所有的git 记录, 所以数据源是git logs。
  2. 通过thorugh2这个库,创建一个转换流, 将可读流pipe到转换流里。每次往可读流里push commitMsg数 据,自动触发转换流的_transform。如果我们在初始化传入了自定义的transform函数,会执行transform。
  3. 没有传入使用默认transform函数,默认根据git tag标签对commit 分组 。
  4. 内部根据semver.valid 校验版本号。可配置具体参数支持提取lerna格式的版本和提交内容,对于不符合格式的commit会忽略。
  5. cc的模版渲染引擎使用的是handlebar,渲染成md文件格式。
  6. 将组装好的版本commit信息 再次推送到一个新的转换流里,用handlebar处理成md格式数据。
  7. cc最后返回一个转换流,只需要配置写流,就可以源源不断的生成changlog数据 。
  8. http://nodejs.cn/api/stream.html
const changelogStream = conventionalChangelog({                   
        preset: 'angular',  // 预设的changelog类型
        warn: function (warning) {
            console.log('warning; ', warning)
        },
        append: false,  // a+            
        releaseCount: 0,  // 0全部重新生成 
        transform: function (commit, cb) {
            if (typeof commit.gitTags === 'string') {
                var match = rtag.exec(commit.gitTags)           
                rtag.lastIndex = 0
                if (match) {
                    commit.version = match[1]  // 版本号需要符合规则  xx.xx.xx这种格式
                }
            }     
            if (commit.committerDate) {         
                commit.committerDate = moment(new Date(commit.committerDate)).format('YYYY-MM-DD HH:mm:ss')  //时间格式 字段=内置模版占位符,或者自定义扩展模版
            }
                      
            return cb(null, commit)         
        }
    })             
changelogStream.pipe('./changelog.md')            

picture.image

【2.1.1-1】*

可读流进行一些列的 parse,最终组装成图【2.1.1-2】的数据格式:

const changelogStream = conventionalChangelog({                   
        preset: 'angular',  // 预设的changelog类型
        warn: function (warning) {
            console.log('warning; ', warning)
        },
        append: false,  // a+            
        releaseCount: 0,  // 0全部重新生成 
        transform: function (commit, cb) {
            if (typeof commit.gitTags === 'string') {
                var match = rtag.exec(commit.gitTags)           
                rtag.lastIndex = 0
                if (match) {
                    commit.version = match[1]  // 版本号需要符合规则  xx.xx.xx这种格式
                }
            }     
            if (commit.committerDate) {         
                commit.committerDate = moment(new Date(commit.committerDate)).format('YYYY-MM-DD HH:mm:ss')  //时间格式 字段=内置模版占位符,或者自定义扩展模版
            }
                      
            return cb(null, commit)         
        }
    })             
changelogStream.pipe('./changelog.md')            

picture.image

【2.1.1-2】*

拿到了数据根据 hbs 模板生成符合 markdown 规范所需的流。

*{{#if scope}} **{{scope}}:**                   
{{~/if}} {{#if subject}}
  {{~subject}}
{{~else}}        
  {{~header}}             
{{~/if}}
                      
{{~!-- commit link --}} {{#if @root.linkReferences~}}              
  ([{{shortHash}}](
  {{~#if @root.repository}}   
    {{~#if @root.host}}
      {{~@root.host}}/
    {{~/if}}
    {{~#if @root.owner}}          
      {{~@root.owner}}/
    {{~/if}}
    {{~@root.repository}}          
  {{~else}}
    {{~@root.repoUrl}}           
  {{~/if}}/
  {{~@root.commit}}/{{hash}})) by: {{authorName}} {{committerDate}}           
{{~else}}     
  {{~shortHash}}             
{{~/if}}
                      
{{~!-- commit references --}}              
{{~#if references~}}
  , closes  
  {{~#each references}} {{#if @root.linkReferences~}}            
    [
    {{~#if this.owner}}                 
      {{~this.owner}}/
    {{~/if}}
    {{~this.repository}}#{{this.issue}}](        
    {{~#if @root.repository}}
      {{~#if @root.host}}
        {{~@root.host}}/
      {{~/if}}
      {{~#if this.repository}}        
        {{~#if this.owner}}
          {{~this.owner}}/
        {{~/if}}
        {{~this.repository}}      
      {{~else}}
        {{~#if @root.owner}}       
          {{~@root.owner}}/
        {{~/if}}
          {{~@root.repository}}      
        {{~/if}}
    {{~else}}      
      {{~@root.repoUrl}}         
    {{~/if}}/
    {{~@root.issue}}/{{this.issue}})         
  {{~else}}
    {{~#if this.owner}}           
      {{~this.owner}}/
    {{~/if}}
    {{~this.repository}}#{{this.issue}}          
  {{~/if}}{{/each}}
{{~/if}}

剖析了 conventional-changelog 生成 CHANGELOG.md 主要核心代码,但是当前开源的 conventional-changelog 库并不能满足需求,它没有对生成的 CHANGELOG.md 文件做提交处理,对多人协作同一个分支的项目没有很好的同步版本 tags,对于需要 npm publish 的项目,没有对用户手动更改 version 进行校验,这些问题会导致生成的 CHANGELOG.md 内容紊乱,达不到用户想要的效果。

在对相关的工具包做调研后,根据自己需要实现的功能,总结当前的工具包满足需求的程度,基于现有工具包完善功能,并根据用户需求进行迭代优化。

工具包迭代历程:

1、用户手动更改 version 能 publish 成功,但是对应的 commit message 挂载版本号紊乱:

  • 第一次提交信息后,手动更新 package.json 的 version,由于没有走正常发布流程,当前发布没有生成 tag,生成的 changelog.md 如下图【2.1.1-3】:

picture.image

【2.1.1-3】*

  • 基于图【2.1.1-3】,第二次提交信息后,手动更新 package.json 的 version ,生成的 changelog.md 如下图【2.1.1-4】,由于手动更新 version,没有 tags 区分记录,所以两个版本的提交信息都融合在了一起,挂在当前 version 上。如果用户每次都手动更新 version,之后所有的 commit message 都会揉到一起挂载在当前 version,直到中间有打tag把提交信息做分界。

picture.image

【2.1.1-4】*

  • 用户通过 npm version xxx,打上 tag,之前未打过 tag 的版本提交内容,全挂在当前 tag 上,当前版本有了 tag。

如图【2.1.1-5】: picture.image

【2.1.1-5】*

  • 基于上一个打过 tag 的版本,进行正确的版本提交,即 npm version xxx,此时内容会基于 tag 归类展示,是我们想要达到的效果,如图【2.1.1-6】: picture.image

【2.1.1-6】*

解决方案:通过上面几个分析,得出结论,必须通过 npm version xxx 打 tag ,commit message 才会正确归类展示。

2、正确生成 changelog.md ,用户执行 npm publish 后,changelog.md 文件存在在本地暂存区,用户需要再次提交,如图【2.1.1-7】: picture.image

【2.1.1-7】*

解决方案:类似 npm version xxx 的原理,集成到工具包中,自动帮用户提交信息。

3、同一个分支多人协同开发,本地 tags 不同步,导致相同的 commit message 内容在 changelog.md 中归类混乱,如图【2.1.1-8】: picture.image

picture.image

【2.1.1-8】*

解决方案:生成 changelog.md 之前,拉取远程 tags ,生成后推送本地 tags。

整体架构图

主要依赖 conventional-changelog 开源包的功能,在生成前期对项目版本号和 tags 做校验,并且同步本地和远程。引导用户在发布前生成符合规范的 version,并自动帮用户提交记录内容。

  • 整体方案设计如图【2.1.2-1】所示:

picture.image

【2.1.2-1】*

工作流程图

方案是一个指引,就像人体的骨架,支撑了整个架构。有了整体的设计方案,接下来就需要设计代码流程,实现想要的功能,落地 idea。

  • 工作流程如下图【2.1.3-1】

picture.image

【2.1.3-1】*

完整效果图

整体流程下来,控制台会出现交互式命令,供用户规范生成版本号并打 tag,如图【2.1.4-1】: picture.image

【2.1.4-1】*

根据 npm version 规范,选择生成需要的版本号,成功生成 CHANGELOG.md ,如图【2.1.4-2】,生成后的文件会自动帮用户暂存并提交(此处功能参考了 npm version xxx 的逻辑,黑盒处理暂存区内容),如图【2.1.4-3】: picture.image

【2.1.4-2】*

picture.image

【2.1.4-3】*

五、实时通知模块

如何在 npm publish 的时候自动唤起飞书机器人针对群组进行通知,需要我们组合飞书开发者 api,形成一个类,完成一系列的操作。

怎样对接飞书机器人

根据飞书开发者文档,用户可创建自己的机器人,文档指引:飞书开放平台(https://open.feishu.cn/document/uQjL04CN/uYTMuYTMuYTM)。

有了机器人,我们需要按照 CHANGELOG.md 的样式,通知到群组。

需要查阅飞书机器人通知消息api,选择符合要求的api进行展示通知,依据 api 的入参格式,就需要对 conventional-changelog 生成的流进行组合处理,对流组装成符合的格式。

飞书的消息卡片支持部分 markdown格式(https://open.feishu.cn/document/ukTMukTMukTM/uADOwUjLwgDM14CM4ATN)。

拿到飞书机器人的 APP_ID、APP_SECRET、APP_VERIFICATION_TOKEN ,写一个 Robot 类,实现对接功能。同时用户可对通知的群做特定配置。

整体架构图

整体方案架构图如图【2.2.2-2】: picture.image

【2.2.2-1】*

工作流程图

配合方案设计图,做如下代码流程设计,如图【2.2.3-1】:

picture.image

【2.2.3-1】*

完整效果图

postpublish 钩子触发通知后的效果如图: picture.image

【2.2.3-1】*

六、应用

具体配置参考:

以工具包的发布为

{                   
  ...                     
   "scripts": {                 
    ...       
    "prepublishOnly": "changelog",               
    "postpublish": "robot"  // 当通知到对应群组,配置"robot [真·B端战友群,xxxx]"
    ...
  },               
  ...                  
}             

目前前端组工具库基本接入 @du/changelog-robot,实现项目发布实时通知,并生成规范 CHANGELOG.md。

七、参考文档

*文/莉莉橙&fog

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论