npm发包工作流分享

设计思路

在公司手动 npm publish 之后工作比较麻烦,涉及到在不同 npm 仓库发包,整理更新日志,在群里推送更新通知等等。因为本人很懒,所以打算写一个小工具来帮我处理上述事务。

这些事务包含
demo

自动升级版本号

这里本来设计的是直接执行自动递增小版本,但感觉不够智能,如果用户升级的是大版本,又得自己手动改,所以这里支持用户自己传版本号,如:

npm run release [版本号]

计算完新版本号后,重置 package.json,直接打 git tag,之后我们在生成 changelog 时候会用到。

发布到多个仓库

没有 scope 就很简单,直接运行命令npm publish --registry=xxxx就搞定了。但如果需要在多个 npm 仓库的某个 scope 下发布就比较复杂。

npm scope

一个 registry 可以有多个 scope,但 一个 scope 只能对应一个 registry,也就是说如果有一个包,名为:@xx/yy要发布到多个仓库,就要不断重置 scope。所以如何把这个部分自动化。

设计数据结构如下:

 publish: {
    dir: 'dist',
    registries: [
      { url: 'http://localhost:4873/' },
      { url: 'http://npm.yonghui.cn/repository/yh-npm/', scope: '@yh' },
    ],
  }

这个阶段的上传工作包含:根据 scope 修改 package.json、上传到不同的 npm 仓库、统计上传结果、收集出错数据。本来以为都写成 promise 之后,直接并发就 ok 了。然而这里有个陷阱是,因为修改的 package.json 是一份数据,promise 在实例化阶段是同步执行的,所以 package.json 边读边写就出现了错误。

后来写法改成 generator 的方式,才得以解决这个问题,示例如下:

const p1 = () =>
    new Promise((resolve, reject) => {
        setTimeout(() => {
            fs.writeFileSync('./log.txt', 'aaa')
            resolve(1)
        }, 2000)
    })

const p2 = () =>
    new Promise((resolve, reject) => {
        setTimeout(() => {
            fs.writeFileSync('./log.txt', 'bbbbbbb')
            resolve(2)
        }, 1000)
    })
const ps = [p1, p2]

function* gent() {
    const f1 = yield p1()
    const f2 = yield p2()
    console.log(f1, f2)
}

function run(gen) {
    const g = gen()
    function next(data) {
        const res = g.next(data)
        if (res.done) return res.value
        res.value.then((data) => {
            next(data)
        })
    }
    next()
}

run(gent)

获取 git 提交信息

git log

直接运行git log会打印很多用不到的信息,我们想要生成 changelog 只需关注 commit 的标题(commit 的内容),所以命令行

git log --pretty=format:%s

format 后跟所需内容的占位符(参考个性化你的 Git Log 的输出格式),%s 代表 commit 标题,%b 代表 commit 内容。

如何将 git log 与版本号挂钩?

上面的命令可以获取到所有的提交记录,但这些提交与包的版本号并无关联,如下:

$ git log --pretty=format:%s
fix: 修复细节 1. tableAction和tableStatus中status类型扩展number类型 2. patchConstant中对tslint的支持
feat: Table组件支持tableAction
feat: table组件添加tablestatus组件
feat: 添加校验
feat: 添加输入校验

所以如何获取最新版本号的提交信息呢?

git tag

获取两个 tag 之间的 git log(即每个 tag 更新的内容参考文档

git log [oldTag]..[newTag]
// 比如
git log 0.1.5..0.1.6

加上格式,这里只输出 commit 标题和提交信息,如下

git log 0.1.5..0.1.6 --pretty=format:%s__%ai

最终打印

fix:修复细节 1. tableAction和tableStatus中status类型扩展number类型 2. patchConstant中对tslint的支持__2021-06-24 15:08:32 +0800
feat: Table组件支持tableAction__2021-06-24 14:23:55 +0800
feat: table组件添加tablestatus组件__2021-06-24 14:11:27 +0800

更新的版本号,时间,和内容都有了,那我们就可以将其收集处理成文档

生成 changelog

拿到信息怎么处理成 md 格式的文本就不细说了。

这里写一个自己的例子,我们根据 commit message 中相关的 flag,对 commit message 进行分类,这里参考conventional commits的规范,只筛选 commit type 为 feat、fix 这两种,其余的都划分到 other 中,因为包使用者最关注的只有新的特性和 bug fix。(同时,要携带版本号和发布日期。)

这个过程就是处理文本的过程,最后生成的文本格式如下:

## FIX BUGS
==================

  * 修复细节  tableAction和tableStatus中status类型扩展number类型

## FEATURE
==================

  * Table组件支持tableAction
  * table组件添加tablestatus组件

插入 readme

可以把上面的内容生成的 changelog 放在一个新页中,或是插入到包的主页中都 ok,这里考虑到如果单拎出来一页,这个文件还是放到 git 中的,然而 git 有可能是私人仓库,所以有可能对包使用者是不可见的。所以说最终的方案还是插入到 readme 中。

这部分的实现思路就是读字符串,找到插入的 flag,插入到指定位置。

触发 webhook

接着就是发送 webhook 了,这里是用的飞书机器人,按照文档发 https 请求就 ok 了!效果如下:

demo