半自动化维护Git Tag和package.json的版本号

项目的静态资源使用CDN加速,在Jenkins中,build完成后会自动执行OSS上传的脚本,为了避免多分支/多版本的静态资源互相影响,项目组在每次需要发布版本的时候需要更新package.json的版本号,并且静态资源根据版本号作为路径区分,避免不同版本的静态资源冲突。

本文基于Brembo项目,各项目组实现方式可能各有不同,仅供参考。

自动更新package版本

捕获用户输入

使用inquirer提供捕获用户输入的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
inquirer.prompt([
{
type: 'confirm',
name: 'needUpdateVersion',
prefix: `${projectInfo}-`,
suffix: `(当前版本号:${chalk.yellow(currentVersion)})`,
message: `是否需要更新版本号?`,
default: false
},
{
type: 'input',
name: 'version',
default: getNextDefaultVersion(),
prefix: `${projectInfo}-`,
suffix: `(当前版本号:${chalk.yellow(currentVersion)}) : \n`,
message: `请输入版本号:`,
when (question) {
return question.needUpdateVersion
},
validate (version) {
if (!regVersion.test(version)) {
console.log(chalk.yellow('输入的版本号格式未通过检查,请检查格式(eg:1.0.0,1.1.0)'))
return false
}
return true
}
}]
)

上述代码中,基于when方法第二个object会在一个问题的答案为true的时候被激活,validate方法会验证用户的输入的版本号格式是否符合规范

1
2
3
4
5
6
// getNextDefaultVersion()
const getNextDefaultVersion = () => {
const numbers = currentVersion.split('.') // currentVersion是从package.json读取的version
const lastNumber = Number(numbers.pop())
return numbers.concat(lastNumber + 1).join('.')
}

执行脚本会提示:

123

处理版本号并提交commit

在inrequirer获取到用户的输入后,将用户输入传递给下一步进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 if (newVersion && newVersion !== currentVersion) {
let spinner
try {
await command(`yarn version --no-git-tag-version --new-version ${newVersion}`)
console.log(chalk.green(`\n ${projectName} package.json版本号更新成功,当前版本为:${newVersion}`))
spinner = ora('git commit中...请等待git hooks执行..').start()
await command(`git add package.json && git commit -m "chore(package.json): 更新项目版本号为:${newVersion}"`)
} catch (err) {
err ? spinner.fail('git执行失败,请手动提交package.json') : spinner.succeed('git commit成功,请直接push代码')
console.log(chalk.red(err))
console.log('\n')
process.exit(1)
}

function command (cmd, options = {}) {
return new Promise((resolve, reject) => {
exec(cmd, options, (err, stdout, stderr) => {
if (err) {
reject(err)
} else {
console.log(stdout)
resolve(true)
}
})
})
}

由于更新代码的脚本放在了pre-push的git hook中,所以并不是每一次push代码我们都需要进行版本更新,所以第一个问题我们问了是否需要更新版本号.如果不需要更新版本号就退出脚本。

脚本中使用了node.child_process的exec执行git命令。

为git打tag

为了便于跟踪版本,可以在每次更新版本的时候同时打上git的tag,便于使用git checkout随时切换到指定版本的代码

增加捕获输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
...something // 更新版本号的捕获
inquirer.prompt([
{
type: 'confirm',
name: 'needAddTag',
message: `${projectInfo}(${chalk.yellow(currentVersion)})-是否为当前版本添加Tag?`,
default: false,
when ({ needUpdateVersion }) {
return needUpdateVersion
}
},
{
type: 'list',
name: 'operationType',
message: '请选择本次更新的类型',
choices: ['fix', 'feat', 'chore', 'build', 'doc'],
when (question) {
return question.needAddTag
}
},
{
type: 'input',
name: 'versionDescr',
message: '请输入版本描述*',
when (question) {
return question.needAddTag
},
validate (desc) {
return !!trim(desc)
}
}]
)

上边的代码中增加了一个确认输入,一个选择输入,一个文本输入。通过确认来确定是否要更新git tag(推荐在需要发版的版本添加git tag而不是每个小版本都添加git tag,有利于git tags
干净整洁), 更新的类型符合commit type规范即可,版本描述填写本版本主要更新内容

处理新的输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (needAddTag) {
try {
spinner.start('正在对本次提交打tag,请耐心等待...')
await command(`git tag -a v${newVersion} -m "${operationType}-${versionDescr}"`)
spinner.start('正在push tag,请耐心等待...')
await command(`git push origin --tags`)
} catch (err) {
console.log(err)
consola.error('git Tag更新失败,请手动同步git tag')
process.exit(1)
}
}
await command(`git checkout develop`)
console.log(chalk.green(`\n 校验通过,分支已切换回develop`))

如果需要添加tag,对当前版本进行 git tag操作,并且自动push tag到远程仓库.

第13行,由于Brembo项目是在release分支做发布,所以为避免发布过后忘记切换到develop分支导致在release做提交,所以在提交后自动切换回develop分支。

使用

自动执行脚本

可以基于husky自动执行本脚本

husky4的配置方式:

package.json
1
2
3
4
5
6
7
{
"husky": {
"hooks": {
"pre-push": "yarn [script-file-name]"
}
}
}

单分支发布

如果项目有多个分支,但我们只使用特定的发布分支做发布操作,可以在脚本最前方定义如下,以release分支做发布为例:

check-version.js
1
2
3
4
5
6
7
8

const getGitBranch = execSync('git name-rev --name-only HEAD', { encoding: 'utf-8' }).replace(regLineBreak, '')

if (!/release/.test(getGitBranch)) {
consola.warn('仅release分支用于版本更新,check-version脚本不会在其余分支运行,请勿手动修改package.json')
process.exit(0)
}

以上是实现自动更新代码和自动打git tag的主要逻辑,基本可以解决目前项目中的需求.

完整代码

check-version.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/* eslint-disable no-console */

const { exec, execSync } = require('child_process')
const inquirer = require('inquirer')
const chalk = require('chalk')
const ora = require('ora')
const consola = require('consola')
const { trim } = require('lodash')
const { name: projectName, version: currentVersion } = require('../package')

const regVersion = /^[1-9]{1}\d*\.\d+\.\d+$/

const regLineBreak = /\s/

const getGitBranch = execSync('git name-rev --name-only HEAD', { encoding: 'utf-8' }).replace(regLineBreak, '')

const projectInfo = `(${chalk.yellowBright(getGitBranch)})`

const getNextDefaultVersion = () => {
const numbers = currentVersion.split('.')
const lastNumber = Number(numbers.pop())
return numbers.concat(lastNumber + 1).join('.')
}

if (!/release/.test(getGitBranch)) {
consola.warn('仅release分支用于版本更新,check-version脚本不会在其余分支运行,请勿手动修改package.json')
process.exit(0)
}

console.log('\n')

inquirer.prompt([
{
type: 'confirm',
name: 'needUpdateVersion',
prefix: `${projectInfo}-`,
suffix: `(当前版本号:${chalk.yellow(currentVersion)})`,
message: `是否需要更新版本号?`,
default: false
},
{
type: 'input',
name: 'version',
default: getNextDefaultVersion(),
prefix: `${projectInfo}-`,
suffix: `(当前版本号:${chalk.yellow(currentVersion)}) : \n`,
message: `请输入版本号:`,
when (question) {
return question.needUpdateVersion
},
validate (version) {
if (!regVersion.test(version)) {
console.log(chalk.yellow('输入的版本号格式未通过检查,请检查格式(eg:1.0.0,1.1.0)'))
return false
}
return true
}
},
{
type: 'confirm',
name: 'needAddTag',
message: `${projectInfo}(${chalk.yellow(currentVersion)})-是否为当前版本添加Tag?`,
default: false,
when ({ needUpdateVersion }) {
return needUpdateVersion
}
},
{
type: 'list',
name: 'operationType',
message: '请选择本次更新的类型',
choices: ['fix', 'feat', 'chore', 'build', 'doc'],
when (question) {
return question.needAddTag
}
},
{
type: 'input',
name: 'versionDescr',
message: '请输入版本描述*',
when (question) {
return question.needAddTag
},
validate (desc) {
return !!trim(desc)
}
}
]).then(async (answers) => {
const { version: newVersion, versionDescr, operationType, needAddTag } = answers
if (!answers.needUpdateVersion) { process.exit(0) }
if (newVersion && newVersion !== currentVersion) {
let spinner
try {
await command(`yarn version --no-git-tag-version --new-version ${newVersion}`)
console.log(chalk.green(`\n ${projectName} package.json版本号更新成功,当前版本为:${newVersion}`))
spinner = ora('git commit中...请等待git hooks执行..').start()
await command(`git add package.json && git commit -m "chore(package.json): 更新项目版本号为:${newVersion}"`)
} catch (err) {
err ? spinner.fail('git执行失败,请手动提交package.json') : spinner.succeed('git commit成功,请直接push代码')
console.log(chalk.red(err))
console.log('\n')
process.exit(1)
}
if (needAddTag) {
try {
spinner.start('正在对本次提交打tag,请耐心等待...')
await command(`git tag -a v${newVersion} -m "${operationType}-${versionDescr}"`)
spinner.start('正在push tag,请耐心等待...')
await command(`git push origin --tags`)
} catch (err) {
console.log(err)
consola.error('git Tag更新失败,请手动同步git tag')
process.exit(1)
}
}
await command(`git checkout develop`)
console.log(chalk.green(`\n 校验通过,分支已切换回develop`))
spinner.stop()
process.exit(0)
} else {
console.log(chalk.green(`本次未修改版本号,version:${newVersion} ! \n`))
}
})

function command (cmd, options = {}) {
return new Promise((resolve, reject) => {
exec(cmd, options, (err, stdout, stderr) => {
if (err) {
reject(err)
} else {
console.log(stdout)
resolve(true)
}
})
})
}

参考

BremboFront

文章作者: Siyuan
文章链接: http://example.com/2022/07/15/FED/git-tag/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明来自 Neverland