About Auto Release Script 🚀
Mon Oct 17 2022 8 months ago
background #
可能大家在公司写业务的时候,可能都会涉及到不同环境的分支的上线部署。测试,预发,正式服也都是不同的分支,打不同的tag
去触发CI/CD
。
我们可能会在测试服改很多次 bug,打很多的tag
,发布不同的测试版本。基于我司的CI/CD
是自动化流程,和相匹配的契机,便写了一个自动化的脚本,来帮助我们快速的发布各种版本。
principle #
- 通过命令获取发布的环境
- 根据环境和配置进行匹配
- 修改
package.json
中的version
字段 - 通过
bummp
进行Release
implementation #
我们借助zx
来实现这个脚本。
zx
A tool for writing better scripts
script #
首先我们在scripts
文件夹下面建立我们的release
脚本文件,然后思考,如何接受我们需要接受的环境变量env
呢?
于是我们可以在package.json
中定义我们命令,通过命令的方式传入写入环境。
// package.json
"scripts": {
"release:test": "zx scripts/release.mjs -- test",
"release:pre": "zx scripts/release.mjs -- pre",
"release:prod": "zx scripts/release.mjs -- prod",
}
env #
上面我们脚本命令传入参数,区别与不同的环境,于是我们接受环境变量env
const [,,,, env] = process.argv // test or pre or prod
这样我们就拿到了环境变量。但是出于严谨性判断,可能我们会在错误的分支上打tag
,于是我们需要对命令发布的时机和当前分支做判断
首先我们建立环境变量和正确分支之前的映射关系:
const tag2branch = {
test: 'test',
pre: 'pre',
prod: 'main',
}
然后判断当前分支是否在映射表里,如何查看当前分支呢?很简单,我们使用zx
执行 git branch
我们发现当前分支前面有一个*
进行标记,于是我们根据这一点,获取当前分支名。
const res = await $`git branch`
const branchs = res.stdout.split('\n')
const currentBranch = branchs.find(b => b.includes('*')).replace(/[\*|\s]*/g, '')
然后判断currentBranch
是否是env
所对应的正确分支。
newversion #
我们做了env
与branch
的判断,接下来我们需要生成下一次正确发布的版本。于是很简单 我们只需要对上一次的版本号进行+1操作就好了
如何获取上一次的发布的版本号呢? git tag
这个命令就很符合我们的要求,他会列出所有的tag
。
于是 我们使用zx
执行 git tag
。
截取一小部分,我们发现我们需要对
tags
进行env
的过滤
我们还发现tags
的排序也有问题,并不是按照正确的版本顺序排,我们还需要正确的sort
一下。
根据个人情况而定。我司的
tag
都是${env}-${version}
格式
获取env
对应的所有versions
const res = await $`git tag`
const allVersions = res.stdout.split('\n')
.filter(tag => tag.includes(env))
.map(tag => tag.replace(new RegExp(`v(.+)-${env}`), '$1'))
然后在对veisons
进行正确的排序。
排序算法参考的别人的算法,待会见源码
const sortVersions = sortVersion(allVersions)
这样sortVersions[sortVersions.length - 1]
便是我们的最新的版本号。
为了配合bummp
并且确保package.json
的版本号也是正确的,我们需要将package.json
的版本号也更新一下。
await modifyPkgVersion(sortVersions[sortVersions.length - 1] ?? '0.0.0')
async function modifyPkgVersion(version) {
const pkg = await $`cat package.json`
await $`echo ${pkg.stdout.replace(/\"version\":\s*\"[^\"]+\"/, `"version": "${version}"`)} > package.json`
}
bummp-realease #
我们已经将package.json
更新为最新的版本,那么我们就可以使用bummp
进行发布了。
async function release() {
await $`pnpm exec bumpp package.json --commit "chore: release ${env} v%s" --push --tag "v%s-${env}"`
console.log(`${env} release success !`)
}
每个人的
bumpp
的配置可能不一样,这里只是一个简单的示例,我司的tag
格式是v${version}-${env}
。
source-code #
import { $ } from 'zx'
const env = getEnv()
const tag2branch = {
test: 'test',
pre: 'pre',
prod: 'main',
}
run()
async function run() {
const isRightBranch = await isEnvBranch(env)
if (!isRightBranch) {
console.log('不是正确的分支')
return
}
await modifyLastVersion()
await release()
}
function getEnv() {
const [, , , , env] = process.argv
return env
}
// 修改最新的版本号
async function modifyLastVersion() {
const lastVersion = await getLatestTag()
if (!lastVersion)
console.log('没有历史 tag 版本,自动从 0.0.0 开始')
await modifyPkgVersion(lastVersion ?? '0.0.0')
}
// 修改package.json的版本号
async function modifyPkgVersion(version) {
const pkg = await $`cat package.json`
await $`echo ${pkg.stdout.replace(/\"version\":\s*\"[^\"]+\"/, `"version": "${version}"`)} > package.json`
await clearLog()
}
async function release() {
await $`pnpm exec bumpp package.json --commit "chore: release ${env} v%s" --push --tag "v%s-${env}"`
await clearLog()
console.log(`${env} release success !`)
}
async function isEnvBranch() {
const res = await $`git branch`
const branchs = res.stdout.split('\n')
const currentBranch = branchs.find(b => b.includes('*')).replace(/[\*|\s]*/g, '')
const aimBranch = tag2branch[env]
await clearLog()
return aimBranch === currentBranch
}
async function getLatestTag() {
const res = await $`git tag`
const allVersions = res.stdout.split('\n')
.filter(tag => tag.includes(env))
.map(tag => tag.replace(new RegExp(`v(.+)-${env}`), '$1'))
const sortVersions = sortVersion(allVersions)
await clearLog()
return sortVersions[sortVersions.length - 1] || undefined
}
async function clearLog() {
await $`clear`
}
function sortVersion(arr) {
const result = [...arr]
result.sort((a, b) => {
const items1 = a.split('.')
const items2 = b.split('.')
let k = 0
for (const i in items1) {
const a1 = items1[i]
const b1 = items2[i]
if (typeof a1 === 'undefined') {
k = -1
break
}
else if (typeof b1 === 'undefined') {
k = 1
break
}
else {
if (a1 === b1)
continue
k = Number(a1) - Number(b1)
break
}
}
return k
})
return result
}