logologo

About Auto Release Script 🚀

Mon Oct 17 2022 5 months ago

background #

可能大家在公司写业务的时候,可能都会涉及到不同环境的分支的上线部署。测试,预发,正式服也都是不同的分支,打不同的tag去触发CI/CD。

我们可能会在测试服改很多次 bug,打很多的tag,发布不同的测试版本。基于我司的CI/CD是自动化流程,和相匹配的契机,便写了一个自动化的脚本,来帮助我们快速的发布各种版本。

principle #

  1. 通过命令获取发布的环境
  2. 根据环境和配置进行匹配
  3. 修改package.json中的version字段
  4. 通过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

image.png

我们发现当前分支前面有一个*进行标记,于是我们根据这一点,获取当前分支名。

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。

image.png

截取一小部分,我们发现我们需要对tags进行env的过滤

image.png

我们还发现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}
蜀ICP备2022005364号 2022 © Chris