背景
可能大家在公司写业务的时候,可能都会涉及到不同环境的分支的上线部署。测试,预发,正式服也都是不同的分支,打不同的tag
去触发CI/CD
。我们可能会在测试服改很多次bug,打很多的tag
,发布不同的测试版本。
我个人是个非常不喜欢记太多命令的人,于是为了偷懒,为什么这么多的操作何不去写个脚本,一行命令就搞定呢。由于这样的契机,写了这个针对多环境的tag
触发脚本。
原理
- 通过写入的命令获取发布的环境,并判断环境和部署的分支名是否对应
- 打tag,更改
package.json
版本号 git push
实现
实现方式为node
,然后使用zx
run shell cmd
zx的介绍参考# zx, 如何用Javascript优雅的书写脚本命令
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 env2branch = {
test: 'dev', // test 对应 dev 分支
pre: 'pre', // pre 对应 pre 分支
prod: 'master' // prod 对应 master 分支
}
复制代码
然后判断当前分支是否在映射表里。
如何查看当前分支呢?
很简单,我们使用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 prefix = `${env}-`
const allVersions = res.stdout.split('\n').filter(tag => tag.includes(env)).map(tag => tag.replace(new RegExp(prefix), ''))
复制代码
然后在对veisons
进行正确的排序,排序算法参考的别人的算法,待会见源码
const sortVersions = sortVersion(allVersions) // 排序
复制代码
这样sortVersions[sortVersions.length - 1]
便是我们的最新的版本号
我们再进行+1操作,生成最新的版本号。
const latestVersion = sortVersions[sortVersions.length - 1]
const ltStr = latestVersion.split('.')
ltStr[ltStr.length - 1] = Number(ltStr[ltStr.length - 1]) + 1
return ltStr.join('.') // 最新的版本号, 也就是需要发布的版本号
复制代码
我们再去使用bumpp
去更改我们package.json
的version
。
runShell
功能已经全部完善了,最后我们再git push
就好了
async function runShell(version) {
const tag = `${env}-${version}`
await $`git add .`
await $`git commit -m "chore: release ${tag}"`
await $`git tag ${tag}`
await $`git push`
await $`git push origin --tags`
await $`clear`
console.log(`release success for ${tag} !`)
}
复制代码
源码
// package.json
{
"type": "module", // 需要开启
"scripts": {
"release:test": "zx scripts/release.mjs -- test", // 根据情况自己更改配置
"release:pre": "zx scripts/release.mjs -- pre", // 根据情况自己更改配置
"release:prod": "zx scripts/release.mjs -- prod" // 根据情况自己更改配置
}
}
复制代码
// release.mjs
import { $ } from 'zx'
const env = getEnv()
run()
async function run() {
const isRightBranch = await isEnvBranch(env)
if (!isRightBranch) {
console.log('不是正确的分支')
return
}
const version = await generateVersion()
await changeVersion(version)
await runShell(version)
}
function getEnv() {
const [,,,, env] = process.argv
return env
}
async function generateVersion() {
const [, lastVersion] = await getLatestTag(env)
const ltStr = lastVersion.split('.')
ltStr[ltStr.length - 1] = Number(ltStr[ltStr.length - 1]) + 1
return ltStr.join('.')
}
async function changeVersion(v) {
await $`pnpm exec bumpp ${v}`
}
async function runShell(version) {
const tag = `${env}-${version}`
await $`git add .`
await $`git commit -m "chore: release ${tag}"`
await $`git tag ${tag}`
await $`git push`
await $`git push origin --tags`
await $`clear`
console.log(`release success for ${tag} !`)
}
async function isEnvBranch(env) {
// 根据情况自己更改配置
const env2branch = {
test: 'dev',
pre: 'pre',
prod: 'master'
}
const res = await $`git branch`
const branchs = res.stdout.split('\n')
const currentBranch = branchs.find(b => b.includes('*')).replace(/[\*|\s]*/g, '')
const aimBranch = env2branch[env]
await $`clear`
return aimBranch === currentBranch
}
async function getLatestTag(env) {
const res = await $`git tag`
const prefix = `${env}-`
const allVersions = res.stdout.split('\n').filter(tag => tag.includes(env)).map(tag => tag.replace(new RegExp(prefix), ''))
const sortVersions = sortVersion(allVersions)
await $`clear`
return [prefix, sortVersions[sortVersions.length - 1]]
}
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
}
复制代码
不足
该脚本暂时只支持小版本号+1,如v1.0.1
,使用脚本只能发v1.0.2
,所以很局限性,大家可以根据自己的要求更改代码。好了,就这么多,欢迎使用。