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

分类: asiasports365 作者: admin 时间: 2025-10-27 18:30:47 阅读: 6726
一步搞定项目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

提交约束规范如下:

[optional scope]:

type具体类别如下:

feat:新功能(feature)

fix:修补bug

docs:文档(documentation)

style: 格式(不影响代码运行的变动)

refactor:重构(即不是新增功能,也不是修改bug的代码变动)

test:增加测试

chore:其他修改, 比如构建流程, 依赖管理

使用示例:

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

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

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

二、整体方案架构图

【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

cc 首先执行 git log --pretty ,拿到本地所有的git 记录, 所以数据源是git logs。

通过thorugh2这个库,创建一个转换流, 将可读流pipe到转换流里。每次往可读流里push commitMsg数 据,自动触发转换流的_transform。如果我们在初始化传入了自定义的transform函数,会执行transform。

没有传入使用默认transform函数,默认根据git tag标签对commit 分组 。

内部根据semver.valid 校验版本号。可配置具体参数支持提取lerna格式的版本和提交内容,对于不符合格式的commit会忽略。

cc的模版渲染引擎使用的是handlebar,渲染成md文件格式。

将组装好的版本commit信息 再次推送到一个新的转换流里,用handlebar处理成md格式数据。

cc最后返回一个转换流,只需要配置写流,就可以源源不断的生成changlog数据 。

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')

【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')

【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】:

【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把提交信息做分界。

【2.1.1-4】*

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

如图【2.1.1-5】:

【2.1.1-5】*

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

【2.1.1-6】*

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

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

【2.1.1-7】*

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

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

【2.1.1-8】*

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

整体架构图

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

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

【2.1.2-1】*

工作流程图

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

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

【2.1.3-1】*

完整效果图

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

【2.1.4-1】*

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

【2.1.4-2】*

【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】:

【2.2.2-1】*

工作流程图

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

【2.2.3-1】*

完整效果图

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

【2.2.3-1】*

六、应用

具体配置参考:

以工具包的发布为

{

...

"scripts": {

...

"prepublishOnly": "changelog",

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

...

},

...

}

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

七、参考文档

从 Commit 规范化到发布自定义 CHANGELOG 模版:(https://juejin.cn/post/6844903888072654856#heading-10)

AngularJS Git Commit Message Conventions:(https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.uyo6cb12dt6w)

conventional-changelog:(https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-core/README.md)

inquirer:(https://www.npmjs.com/package/inquirer)

Promise - Q:(https://www.npmjs.com/package/q)

read-pkg-up:(https://www.npmjs.com/package/read-pkg-up)

node api:(http://nodejs.cn/api/)

*文/莉莉橙&fog

相关推荐