本文基于 gulp 的自动化构建能力,结合 conventional-changelog-core 的规范日志生成特性,搭建一套组件库发版时 CHANGELOG 自动更新的方案。该方案可实现版本发布流程与日志更新的无缝联动,既保证了 CHANGELOG 的规范性与完整性,又大幅简化发版操作流程,有助于组件库的高效、标准化迭代。
前提条件
该方案基于规范的 git 提交消息格式:
<type>[optional scope]: <subject>
[optional body]
[optional footer(s)]
如何配置规范的
commitlint可以参考这篇文章 配置Commitlint与Husky实现Git提交消息规范化-开发者社区-阿里云
安装 gulp 和 conventional-changelog-core
考虑到 node 12 版本兼容性(主要是项目中有些依赖实在不兼容高版本 node),我这里选择了 gulp@^4.0.2 和 conventional-changelog-core@^4.2.1
npm i gulp@^4.0.2 conventional-changelog-core@^4.2.1
配置 .conventional-changelog.js
这里提供一份我项目中使用的配置供参考,详细配置可以自行查看 官方文档
// .conventional-changelog.js
const moment = require("moment")
// 配置模板,可以按需调整模板
// https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer#maintemplate
const mainTemplate = `
{{> header}}
{{#each commitGroups}}
{{#each commits}}
{{> commit root=@root}}
{{/each}}
{{/each}}
{{> footer}}
`
// https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer#headerpartial
const headerPartial = `
## {{#if isPatch~}} <small>
{{~/if~}} {{version}}
{{~#if title}} - {{title}}
{{~/if~}}
{{~#if date}} ({{date}})
{{~/if~}}
{{~#if isPatch~}} </small>
{{~/if}}
`
// 最多显示的发布版本个数
const maxReleaseCount = 15
let releaseCount = 0
module.exports = {
// options
// https://www.npmjs.com/package/conventional-changelog-core/v/4.2.1?activeTab=readme
preset: "angular",
outputUnreleased: false,
title: 'CHANGELOG',
config: {
gitRawCommitsOpts: {
// 配置起始提交日志的 commit sha
from: "xxx",
},
parserOpts: {
// https://github.com/conventional-changelog-archived-repos/conventional-commits-parser
mergePattern: /^Merge branch '(.*)' into (.*)$/,
mergeCorrespondence: ["id", "source"],
},
writerOpts: {
// https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer#api
groupBy: "type",
commitGroupsSort: ["feat", "fix", "perf", "docs", "style", "refactor", "test", "chore", "revert"],
commitsSort: ["scope", "subject"],
noteGroupsSort: "title",
notesSort: "title",
mainTemplate,
headerPartial
},
},
transform: function (commit, cb) {
// 配置发版消息的匹配格式并提取版本号,这里可以根据项目实际情况进行调整
const subjectReleasePattern = /elements@(\d+\.\d+\.\d+)/
const headerReleasePattern = /Update version (\d+\.\d+\.\d+)/
if (commit.subject && subjectReleasePattern.test(commit.subject)) {
commit.version = subjectReleasePattern.exec(commit.subject)[1]
} else if (commit.header && headerReleasePattern.test(commit.header)) {
commit.version = headerReleasePattern.exec(commit.header)[1]
} else if (/^(chore|docs|test|doc)/.test(commit.header) && !/^chore\(element-ui\)/.test(commit.header)) {
// 忽略部分提交日志信息,这里可以根据项目实际情况进行调整
cb(null, null)
return
}
if (commit.version) {
releaseCount++
}
if (releaseCount > maxReleaseCount) {
cb(null, null)
return
} else if (releaseCount === 1) {
commit.title = 'latest'
}
// 设置日期格式
if (commit.committerDate) {
commit.date = moment(new Date(commit.committerDate)).format("YYYY-MM-DD")
}
// 设置更新日志的文本模板,主要是提取信息并添加 markdown 格式,这里也可以按需加入一些 emoji 表情丰富日志
let header = ''
if (commit.type) {
header += `**${commit.type}`
}
if (commit.scope) {
header += `(${commit.scope})`
}
if (header.length) {
header += `:** ${commit.subject.replace('@', '-')}`
commit.header = header
}
cb(null, commit)
},
}
配置 gulp
在 gulp 入口文件 gulpfile.js 中添加一个 task,代码参考如下
const fs = require("fs")
var conventionalChangelog = require("conventional-changelog-core")
const conventionalChangelogOpts = require("./.conventional-changelog")
gulp.task("conventional-changelog", function () {
// CHANGE_LOG 文件的路径
const changelogPath = "./CHANGE_LOG.md"
return conventionalChangelog(conventionalChangelogOpts)
.on("end", () => {
// 生成文件完成后,读取文件内容
fs.readFile(changelogPath, "utf8", (err, data) => {
if (err) {
return console.error(err)
}
// 在内容首行添加标题
const newData = `# ${conventionalChangelogOpts.title || 'CHANGELOG'}\n\n` + data
// 将修改后的内容写回文件
fs.writeFile(changelogPath, newData, "utf8", err => {
if (err) {
return console.error(err)
}
})
})
})
.pipe(fs.createWriteStream(changelogPath)) // or any writable stream
})
执行任务脚本,生成 CHANGELOG
gulp 任务创建完成后,可以在命令终端执行 npx gulp conventional-changelog 自动生成 CHANGELOG,更新后的文档内容如下:
进阶:创建自动发布脚本实现 打包-发布-更新文档 全自动化执行
在实际的组件库发版实践中,打包发布还需要同步更新子版本号,这里可以使用 npm --no-git-tag-version version patch 命令来实现子版本号自动更新。同时更新日志一般都会跟随版本发布一起更新到组件文档上,那么就可以将 打包-发布-更新文档 这些环节一起写到一个执行脚本中,组件发布时直接执行脚本即可全自动完成所有流程,具体实现如下:
1. 创建 node 脚本 scripts/release.js
const { execSync } = require('child_process');
const path = require('path');
// 执行命令的函数
function runCommand(command) {
console.log(`执行命令: ${command}`);
try {
execSync(command, { stdio: 'inherit', shell: true });
console.log(`命令执行成功: ${command}`);
} catch (error) {
console.error(`命令执行失败: ${command}`);
console.error(error);
process.exit(1);
}
}
console.log('开始执行发布流程...');
console.log('\n===== 开始执行编译 =====');
runCommand('git fetch && git pull origin master && npm run lib');
console.log('\n===== 发布到 npm =====');
runCommand('npm --no-git-tag-version version patch && npm publish');
const { version } = require(path.join(__dirname, '../package.json'));
console.log('\n===== 提交版本号 =====');
runCommand(`git add package.json package-lock.json && git commit -m "chore: elementsc@${version}"`);
console.log('\n===== 提交更新日志 =====');
runCommand('gulp conventional-changelog && git add "./CHANGE_LOG.md" && git commit -m "docs: update changelog"');
console.log('\n发布流程执行完成!');
2. 在 package.json 中添加执行脚本
{
"name": "elements",
"version": "1.0.0",
"scripts": {
"publish:patch": "node scripts/release.js"
}
}
3. 执行 npm run publish:patch 发布版本
脚本执行成功后,自动提交版本发布日志和更新CHANGELOG,到此完成 打包-发布-更新文档 全自动化执行