github release_轮子改造:github-release-notes

8320f2c1aaa36f75a7f0c7cd7ac3eb82.png

背景

github-release-notes,以下简称 gren ,是用来一键向 github 发布 release notes 的工具。基本步骤如下:

  1. 拉取 github 仓库的 tags 记录
  2. 拉取最新两个 tag 之间的 pr(或 issue)记录,总结成 release notes
  3. 将 release notes 推送到 github 仓库,作为最新 tag 的发布日志

ff3985e1ebf703c7e519d8e39f9f32b8.png

就是这么简单。不管你是想要:

  • 一次生成所有 tag 的发布日志
  • 定制化日志来源。选择是从 pr 还是 issue 获取;根据其中的 label 生成特定格式的文案
  • 其他功能

这一切都可以一条命令来实现。是不是很棒呢?

自动化发布流程

这里顺带提一下我们组件的自动化发布流程,看看 gren 在其中扮演的角色吧~

  1. 经过测试和实际项目检验的 dev 代码,合并到 master 啦
  2. 触发 travis-ci,做两件事
  3. 发版
  4. yarn build,生成可发布的代码
  5. 部署到 github page
  6. 发布到 npm
  7. 发更新日志
  8. 用 standard-version 根据 semver 和最新的 commit-msg 来更新 package 版本并打 tag
  9. 推送新 commit 和 tag 到 github 仓库
  10. 用 github-release-notes 生成 release-log
  11. 调用钉钉服务发群通知

遇到的问题

前面说了,github-release-notes 发布日志的格式是高度可配置的。那么具体配置来源于以下这样的配置文件。

// .grenrc.js
module.exports = {
  dataSource: 'prs',
  groupBy: {
    '✨ New Features:': ['enhancement'],
    '  Bug Fixes:': ['bug'],
    '  Documentation:': ['documentation'],
    '  Refactors:': ['refactor'],
    '♻️ Tests:': ['test'],
    '  Performance:': ['performance'],
    '⚓ Dependency upgrades:': ['dependencies'],
    '  Chore:': ['chore'],
    '  Style:': ['style'],
    '  Hack': ['hack'],
    '  Breaking Changes': ['breaking-change']
  }
}

而这个配置是放置在每个项目之内的。假设我们需要更新这份配置,比如更换 dataSource,或新增了一个 label;组件维护者得一个一个地去组件提 pr。这样既容易遗漏,也很麻烦。

思考

prettier 是允许用户将配置放在 npm 模块里,然后在 package.json 里引入

// package.json
{
  "devDependencies": {
    "@company/prettier-config": "1.0.0"
  },
  "prettier": "@company/prettier-config"
}

这是个好主意:可以统一管理配置文件了;但还有个问题:每次更新并发布配置后依然需要手动更新每个组件。

因此,我们的方案需要解决两个问题:

  1. 所有组件共享一份 gren 配置
  2. 更新 gren 配置时不需要再去更新每个组件

实践

首先,我们需要支持远程配置。

// package.json
{   
  "gren": "https://raw.githubusercontent.com/FEMessage/.github/master/.grenrc.js"
}

github-release-notes 先判断 package.json 是否有 gren 这个字段;有则优先去请求网络资源。这里我们先在团队 github 中管理资源。

但 github 不是用来做 cdn 的。这样的资源请求会极容易被拒绝(加 token也不行)。听说 jsdelivr 能缓存 github 资源,那么:

// package.json
{   
  "gren": "https://cdn.jsdelivr.net/gh/FEMessage/.github/master/.grenrc.js"
}

问题出现在了缓存上:我们希望每次更新配置就能立即被使用。所以最后还是用发 npm 包的形式来管理。

// package.json
{   
  "gren": "@femessage/grenrc"
}

步骤如下:

  1. 从 package.json 的 gren 字段拿到配置包名 packageName
  2. 通过请求 https://registry.npmjs.org/-/package/@femessage/grenrc/dist-tags 拿到最新版本 version
  3. 组合后请求 https://cdn.jsdelivr.net/npm/${packageName}@${version}/index.js 拿到最新的配置

结果

最终我们一劳永逸地实现了:

  1. 统一管理配置
  2. 更新配置组件不用发版

这两个需求。另外,github-release-notes 官方仓库也已接受了 pr。

插曲——如何在 node 中同步执行异步操作

一般的异步操作可以通过 async/await 来写“同步”形式的代码;但 github-release-notes 获取配置的过程是放在 class constructor 函数中的,不能简单地用 async/await 重构。那该怎么办?

我们参考了 request-from-url 库(像 require 本地仓库一样 require 远程仓库)的实现方案,使用 child_process 库,将网络请求放在子进程中进行来阻塞主进程,从而使得主进程的代码可以以同步执行的方式书写。

// 子进程获取 npm 包版本
const packageName = process.argv[2];
const url = `https://registry.npmjs.org/-/package/${packageName}/dist-tags`;
require('https').get(url, res => {
    const buffers = [];
    res.on('data', (data) => {
        buffers.push(data);
    }).on('end', () => {
        const { latest } = JSON.parse(Buffer.concat(buffers).toString());
        const result = [null, latest];
        process.stdout.write(JSON.stringify(result));
    });
});

// 主进程同步执行代码
const { spawnSync } = require('child_process');
const { stdout } = spawnSync('node', ['get-package-latest-version.js', gren], {
    cwd: __dirname
});
const [errMsg, version] = JSON.parse(stdout.toString());

善后

用脚本批量更新组件 & 提交 pr

const components = [
  'v-img',
  'vue-sfc-cli',
  'el-data-table',
  'el-form-renderer',
  'upload-to-ali',
  'el-select-area',
  'data-list',
  'v-editor',
  'el-semver-input',
  'el-number-range',
  'el-data-tree',
  'excel-it',
  'log-viewer',
  'count-down',
  'img-preview',
  'create-nuxt-app',
  'element',
  'vant',
  'direct-mail'
]

function exec(cwd, wholeCommand) {
  if (Array.isArray(wholeCommand)) {
    wholeCommand.forEach(c => exec(cwd, c))
    return
  }
  const [command, ...args] = wholeCommand.split(' ').map(arg => arg.replace(/%s/g, ' '))
  const {stdout, stderr} = require('child_process').spawnSync(command, args, {cwd})
  const errMsg = stderr.toString()
  if (errMsg) {
    console.error(`stderr:n${errMsg}`)
    // process.exit(1) // 许多用 stderr 来传递普通消息的……
  }
}

function addGrenToPackageJson(cwd) {
  const packageJsonPath = `${cwd}/package.json`
  const packageJson = require(packageJsonPath)
  packageJson.gren = '@femessage/grenrc'
  require('fs').writeFileSync(
    packageJsonPath,
    JSON.stringify(packageJson, null, 2) + 'n'
  )
}

function work() {
  components.forEach(c => {
    console.log('component:', c)
    const cwd = `/Users/donald-work/projects/${c}`
    const branch = 'chore-gren'
    exec(cwd, [
      'git checkout dev',
      'git pull upstream dev',
      `git checkout -b ${branch}`,
      'rm .grenrc.js',
      'yarn remove github-release-notes',
      'yarn add -D @femessage/github-release-notes',
    ])
    addGrenToPackageJson(cwd)
    const commitMsg = 'chore(deps):%supgrade%sgren'
    exec(cwd, [
      `git commit -am ${commitMsg}`,
      `git push --set-upstream origin ${branch}`,
      `hub pull-request -b femessage:dev -m ${commitMsg}`,
    ])
  })
}

work()

补充:为组件提交 pr 的流程

1、从 femessage/{component} 组件仓库 fork 一份到自己的仓库

2、从自己仓库拉代码到本地,基于 dev 分支切功能分支或 hotfix 分支,修改代码

3、push 分支,向 femessage 仓库提交 pr

4、写好 pr 的 title 和 content

  • title 格式与 commit 规范相同:feat: 某某功能。这样机器人 auto-add-label 就会自动打 label。不同 label 的 pr 会被 gren 归类,生成 release-log

a4f86385bee7886750a666d9757da099.png
  • content 可以参考模板填写内容
  1. why,需求场景是什么,或解决了什么 issue
  2. how,如果实现方式比较有技巧,那可以简单讲解一下,方便 maintainer 早点合 pr
  3. test,这个其实也很简单。你本地调试完肯定会跑跑看。
  4. 如果是新功能,截下新功能的图
  5. 如果是修复 bug,则需要截下修复前和修复后的对比图
  6. docs,如果是补充文档内容的 pr,则在这里文字或截图说明一下

02735740af35993237676415c94736a5.png

5、等待被添加为 contributors

f7811b1b8ad245bb6d6451a7c747a83a.png

鸣谢

  • Han,完成了初期迭代版本,找到了同步请求资源的方案
  • EVILLT,提供了批量更新组件依赖 & 提 pr 的脚本方案
  • levy,推动了整个方案的进行

参考

  • github-release-notes 官方概念详解
  • 揭秘vue-sfc-cli: 组件研发利器
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值