发包主要用到了scripts/release.js脚本,其中包括版本的更改(每次发布所有包一起同步发布),打 git tag 以及最终的 npm 发包。发包的版本支持灰度处理(canary)。
{
"release": "node scripts/release.js",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
}{
"release": "node scripts/release.js",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
}以上就是两个相关的命令,主要就是执行scripts/release.js脚本,其中changelog已经包含在了脚本执行里。
基本参数
和dev.js, build.js脚本一样,我们可以通过参数控制脚本的执行。
--preid: 为准备版本的类型prepatch, preminor, premajor, prerelease加上的标识,灰度忽略此参数bashpnpm release --preid=beta # 可选版本类型即对应版本 patch (3.3.3) minor (3.4.0) major (4.0.0) prepatch (3.3.3-beta.0) preminor (3.4.0-beta.0) premajor (4.0.0-beta.0) prerelease (3.3.3-beta.0) custompnpm release --preid=beta # 可选版本类型即对应版本 patch (3.3.3) minor (3.4.0) major (4.0.0) prepatch (3.3.3-beta.0) preminor (3.4.0-beta.0) premajor (4.0.0-beta.0) prerelease (3.3.3-beta.0) custom--dry: 执行一遍基本流程,但不实际执行命令,主要用于开发调试;默认为false--skipTests: 是否跳过测试(pnpm test run); 默认为false会包含检查 ci 的通过情况--skipBuild: 是否跳过pnpm build --withTypes && pnpm test-dts-only;默认为false--canary: 是否发布灰度版本;为true时版本格式为3.yyyyMMdd.0;优先级比指定的版本高;如会忽略指定版本而使用灰度的版本号node scripts/release.js 3.3.3 --canary -> x.yyyymmdd.x;其次为true时相当于skipPrompts和skipGit--skipPrompts: 是否跳过选择版本号的提示;默认为false,即在没有指定版本时会提示选择--skipGit: 是否执行 git 操作,包括一下命令--tag: 发布到 npm 的 tagbash# 提交版本变更代码 git diff # 如果有改变 git add -A git commit -m"release: v<发布的版本号>" # 打tag和推送代码 git tag v<发布的版本号> git push origin refs/tags/v<发布的版本号> git push# 提交版本变更代码 git diff # 如果有改变 git add -A git commit -m"release: v<发布的版本号>" # 打tag和推送代码 git tag v<发布的版本号> git push origin refs/tags/v<发布的版本号> git push
发布流程
接下来我们看一下整体的发布流程
确定发布的版本号
确定版本的过程分为三种情况:
- 指定版本号:
node scripts/release.js 3.3.3, 此时版本号就为3.3.3 - 未指定版本号:
node scripts/release.js, 会提示选择对应版本号 - 指定版本号的灰度发布:
node scripts/release.js 3.3.3 --canary, 此时版本号为3.yyyymmdd.3, 其中yyyymmdd为发布时当前机器的年月日
提供的版本号
部分相关代码如下:
const versionIncrements = [
'patch',
'minor',
'major',
...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : [])
]
const inc = i => semver.inc(currentVersion, i, preId)
const { release } = await prompt({
type: 'select',
name: 'release',
message: 'Select release type',
choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])
})
if (release === 'custom') {
const result = await prompt({
type: 'input',
name: 'version',
message: 'Input custom version',
initial: currentVersion
})
// @ts-ignore
targetVersion = result.version
} else {
targetVersion = release.match(/\((.*)\)/)[1]
}const versionIncrements = [
'patch',
'minor',
'major',
...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : [])
]
const inc = i => semver.inc(currentVersion, i, preId)
const { release } = await prompt({
type: 'select',
name: 'release',
message: 'Select release type',
choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])
})
if (release === 'custom') {
const result = await prompt({
type: 'input',
name: 'version',
message: 'Input custom version',
initial: currentVersion
})
// @ts-ignore
targetVersion = result.version
} else {
targetVersion = release.match(/\((.*)\)/)[1]
}其中preId为--preId的值,即准备版本的标识;inc方法是根据版本更新的类型versionIncrements来推测出对应的要发布的版本号,其使用的是semver这个包。
此外最后会 append 一个custom选项去手动输入版本号。
最后如果选择的是提供的选项,通过正则就可以匹配出要发布的版本了:patch (3.3.3) -> 3.3.3.
灰度发布
如果有指定灰度发布--canary, 那么不管是否提供指定版本,最终都会处理为灰度发布的版本格式,且发包的名字为灰度包名格式:vue -> @vue/canary;其他核心(@vue开头)包 -> 包名--canary 如: @vue/runtime-core -> @vue/runtime-core--canary。
首先获取当前机器的时间(UTC date -> yyyyMMdd),接着主要对patch版本做递增:如当天当前的灰度版本为3.yyyyMMdd.1,那么此次发布的灰度版本就是3.yyyyMMdd.2,如果当天未发布过,此次发布的灰度版本就是3.yyyyMMdd.0。
为了防止在当天重复发布多个相同patch版本的包,会有个检查过程;一开始的canaryVersion的patch版本号为0。
// 改成灰度命名格式,这里只是用vue包来获取当天已经发布的灰度版本,因为所有包版本都应该是同步的
const pkgName = renamePackageToCanary('vue')
const { stdout } = await run(
'pnpm',
['view', `${pkgName}@~${canaryVersion}`, 'version', '--json'],
{ stdio: 'pipe' }
)
let versions = JSON.parse(stdout)
versions = Array.isArray(versions) ? versions : [versions]
const latestSameDayPatch = /** @type {string} */ (
semver.maxSatisfying(versions, `~${canaryVersion}`)
)
canaryVersion = /** @type {string} */ (semver.inc(latestSameDayPatch, 'patch'))// 改成灰度命名格式,这里只是用vue包来获取当天已经发布的灰度版本,因为所有包版本都应该是同步的
const pkgName = renamePackageToCanary('vue')
const { stdout } = await run(
'pnpm',
['view', `${pkgName}@~${canaryVersion}`, 'version', '--json'],
{ stdio: 'pipe' }
)
let versions = JSON.parse(stdout)
versions = Array.isArray(versions) ? versions : [versions]
const latestSameDayPatch = /** @type {string} */ (
semver.maxSatisfying(versions, `~${canaryVersion}`)
)
canaryVersion = /** @type {string} */ (semver.inc(latestSameDayPatch, 'patch'))其中pnpm view vue/canary@~3.yyyymmdd.0 version --json用来获取当天已经发布的灰度版本号,如已经发布了3.yyyymmdd.1和3.yyyymmdd.2,那么versions就为['3.yyyymmdd.1', '3.yyyymmdd.2']。
semver.maxSatisfying用于获取版本数组中满足~canaryVersion的最大的版本号,即当天的patch的最大版本,上面例子就是3.yyyymmdd.2,所以此次发布的灰度版本应该为3.yyyymmdd.3。
检查测试
如果没有指定--skipTests,在执行测试(pnpm test run)前会先通过调用github CIapi 检查此次发版的提交(git rev-parse HEAD)是否已经通过了CI,如果通过了就提示是否要跳过本地测试的执行,因为CI上面已经执行过一遍通过了。
更新 package.json 的版本
根据确定好要发布的版本号更新每个包里package.json里的version; 首先更新根目录下的package.json,接着遍历所有包来更新其package.json以及更新其dependencies和peerDependencies下的依赖包(vue 相关的核心包)的package.json。
需要特殊处理的是灰度发布时的版本更新,前面说到灰度发布会用特定标识灰度的格式重命名包名,所以对于dependencies和peerDependencies下的依赖包,我们需要通过npm:<package name>@<package version>的方式额外指定包别名。
如对于vue包
// 发布前
{
"dependencies": {
"@vue/runtime-dom": "3.3.2",
}
}
// 灰度发布后
{
"dependencies": {
"@vue/runtime-dom": "npm:@vue/runtime-dom--canary@3.yyyymmdd.0",
}
}// 发布前
{
"dependencies": {
"@vue/runtime-dom": "3.3.2",
}
}
// 灰度发布后
{
"dependencies": {
"@vue/runtime-dom": "npm:@vue/runtime-dom--canary@3.yyyymmdd.0",
}
}打包构建
如果未指定--skipBuild和--dry, 接下来会执行一遍打包构建出要发布的实际代码,即pnpm run build --withTypes && pnpm test-dts-only。
生成 changelog
接着执行pnpm run changelog来通过conventional-changelog来生成对应的 changelog,其会自动识别更新的commit message里提取信息写进CHANGELOG.md里
更新 pnpm-lock.yaml
如果是非灰度版本,会执行一下更新pnpm-lock.yaml的操作,即pnpm install --prefer-offline
发包和 git 同步
最后就是提交代码之后将更新好的代码发布(只发布 public 包)到 npm,然后打上发布版本的 tag 并将代码 push 到 github。
其中发布的命令是pnpm publish --tag <alpha/beta/rc> --access public,其中tag是只有在alpha/beta/rc版本才有,或者可以通过参数--tag指定。
vue3-reading