源码学习——工程化相关—changelog的实现

552 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

前言

当前在开发小程序的组件库时,借鉴了很多开源的组件库。通过学习,在腾讯的tdesign组件库中,学到了如何生成changelog的原理,本篇文章就分析下tdesign是如何实现的。

ChangeLog是什么

ChangeLog就是发布新版本时,用来说明与上一个版本差异的文档。示例:

image.png

为了提高效率,这种文档,我们都是自动化生成的,而不是人工手写,不然效率太低了。

自动生产changelog的前置依赖

如果希望自动化的生成changelog文档,那我们就需要规范化的填写我们的commit信息,这样就可以直接采集对应的commit信息,当做changelog的内容。

这里commit的书写规范,推荐commitizen规范。

通过规范的书写commit信息,可以直接将commit类型为feat和fix的内容提取出来,当做changelog内容。

自动生成changelog原理解析

源码

一共86行代码,就完成了自动生产changelog的需求。下面我们来分析下源码内容。

源码查看:github.com/Tencent/tde…

核心就是standard-changelog这个库,本篇文章不深入该库的内容,只知道其是自动获取log内容的库即可。

解析

  • 入口方法updateChangeLog,直接执行。
  • 首先会先更新我们的版本号,因为只有发布新版本时,才会生成changelog。看代码内容:
    • 这里我们看到使用了readline方法,来自node的模块,可以获取用户的输入内容。
    • 更新版本时,优先使用用户输入的版本号,并对其进行校验,其次使用package.json中的版本。最后将更新后的版本保存至package.json。
function updateVersion() {
  return new Promise((resolve) => {
    const rl = readline.createInterface({ input: process.stdin, output: process.stdout });

    rl.setPrompt(`当前 package.json 版本号为: ${pkg.version}\n请输入本次要发布的版本号:(可按回车跳过)\n`);
    rl.prompt();

    // eslint-disable-next-line consistent-return
    rl.on('line', (input) => {
      let newVersion = '';
      if (!input) {
        newVersion = pkg.version.replace(/(\d+\.\d+\.)(\d+)/, (verion, $1, $2) => $1 + (Number($2) + 1));
      } else if (!VERSION_REG.test(input)) {
        console.log('\x1B[31m%s\x1B[0m', '\n⚡ 不要搞事年轻人,请输入正确版本号格式!\n');
        rl.prompt();
        return;
      } else {
        newVersion = input;
      }
      const newPkg = JSON.stringify(Object.assign({}, pkg, { version: newVersion }), null, 2);
      fs.writeFileSync('package.json', `${newPkg}\n`, 'utf8');
      console.log('\x1B[32m%s\x1B[0m', '\n🎉 good job! package.json 文件已更新.\n');
      rl.close();
    });

    rl.on('close', resolve);
  });
}

  • 接下来,获取最新一次的生成changeLog的commit信息。核心就是这段代码git blame CHANGELOG.md。利用execSync方法执行命令,获取内容。
function getLastChangeLogCommit() {
  const gitCommand = 'git blame CHANGELOG.md';
  const changeLogCommits = execSync(gitCommand, {
    cwd: process.cwd(),
    encoding: 'utf-8',
  }).split('\n');

  return changeLogCommits.find((cmt) => VERSION_REG.test(cmt)).slice(0, 8);
}
  • 然后,读取changelog.md的内容,并截取除文件信息表头信息的其它内容,转换成数组,方便数据操作。
let initialChangelogStr = fs.readFileSync('CHANGELOG.md', 'utf8');

const pageDataStr = initialChangelogStr.match(/---[\s\S]+---/)[0] + '\n';
const data = initialChangelogStr.split(/---[\s\S]+---/);
data.unshift(pageDataStr);
  • 之后就是最关键的一步,获取上一次生成changelog到这一次最新的所有commit信息,并将相关内容进行格式替换,转换成markdown格式。利用standardChangelog方法来实现。
new Promise((resolve) => {
    standardChangelog({}, null, { from: lastCommit, to: 'HEAD' })
      .on('data', (chunk) => {
        let changeLogStr = chunk.toString().trim();
        changeLogStr = changeLogStr.replace(/\(([\d\-]+)\)/g, '`$1`');
        changeLogStr = changeLogStr.replace(/^#\s/g, '## ').trim();
        data.splice(1, 0, `${changeLogStr}\n`);
      })
      .on('end', resolve);
  }).then(() => {
    getGitCommitMap(lastCommit);
    const writeStream = fs.createWriteStream('CHANGELOG.md', 'utf8');
    writeStream.write(data.join('\n'));
    writeStream.end();

    console.log('\x1B[32m%s\x1B[0m', '已生成最新 changeLog... 请打开 CHANGELOG.md 确认');
  });
  • 最后就是将更新后的changelog内容写入文件。

收获

  • 了解如何在node中执行shell命令,并获取执行后的内容。const { execSync } = require('child_process');
  • 了解并学习了node模块readline,能够在命令行获取用户的输入。
  • 了解根据commit自动生成changelog的核心库standard-changelog。根据提交的commit类型,如fixfeat等,自动生产更改的日志。
  • 学习了git命令git blame ,用来追溯一个指定文件的历史修改记录。

总结

其实社区是有专门的工具来生成changelog的,在看到这个在开源库中手工写脚本来生成changelog文件,就记录下其实现的原理,来完善自己工程化相关的知识广度,并应用到日常的项目中。并且借鉴其实现思路,可以复用到其他有关的功能上。比如命令行交互设计,完全可以扩展我们内部的cli工具。