About Auto Release Script 🚀
Mon Oct 17 2022 5 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}