持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
前言
当前在开发小程序的组件库时,借鉴了很多开源的组件库。通过学习,在腾讯的tdesign组件库中,学到了如何生成changelog的原理,本篇文章就分析下tdesign是如何实现的。
ChangeLog是什么
ChangeLog就是发布新版本时,用来说明与上一个版本差异的文档。示例:
为了提高效率,这种文档,我们都是自动化生成的,而不是人工手写,不然效率太低了。
自动生产changelog的前置依赖
如果希望自动化的生成changelog文档,那我们就需要规范化的填写我们的commit信息,这样就可以直接采集对应的commit信息,当做changelog的内容。
这里commit的书写规范,推荐commitizen规范。
通过规范的书写commit信息,可以直接将commit类型为feat和fix的内容提取出来,当做changelog内容。
自动生成changelog原理解析
源码
一共86行代码,就完成了自动生产changelog的需求。下面我们来分析下源码内容。
核心就是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类型,如fix,feat等,自动生产更改的日志。 - 学习了git命令
git blame,用来追溯一个指定文件的历史修改记录。
总结
其实社区是有专门的工具来生成changelog的,在看到这个在开源库中手工写脚本来生成changelog文件,就记录下其实现的原理,来完善自己工程化相关的知识广度,并应用到日常的项目中。并且借鉴其实现思路,可以复用到其他有关的功能上。比如命令行交互设计,完全可以扩展我们内部的cli工具。