Skip to content

发包主要用到了scripts/release.js脚本,其中包括版本的更改(每次发布所有包一起同步发布),打 git tag 以及最终的 npm 发包。发包的版本支持灰度处理(canary)。

json
{
  "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加上的标识,灰度忽略此参数

    bash
    pnpm 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
    pnpm 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时相当于skipPromptsskipGit

  • --skipPrompts: 是否跳过选择版本号的提示;默认为false,即在没有指定版本时会提示选择

  • --skipGit: 是否执行 git 操作,包括一下命令

  • --tag: 发布到 npm 的 tag

    bash
    # 提交版本变更代码
    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为发布时当前机器的年月日

提供的版本号

部分相关代码如下:

js
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版本的包,会有个检查过程;一开始的canaryVersionpatch版本号为0

js
// 改成灰度命名格式,这里只是用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.13.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以及更新其dependenciespeerDependencies下的依赖包(vue 相关的核心包)的package.json

需要特殊处理的是灰度发布时的版本更新,前面说到灰度发布会用特定标识灰度的格式重命名包名,所以对于dependenciespeerDependencies下的依赖包,我们需要通过npm:<package name>@<package version>的方式额外指定包别名

如对于vue

json
// 发布前
{
  "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指定。