VUE 3.0 源码 script/release.js 模块发布

文件路径:VUE 3.0 源码 /script/release.js

该脚本从主函数main()函数的运行开始:

1、使用 prompt CLI提示插件,引导用户 Select release type...

      ? Select release type ...
      > patch (3.0.0)
        minor (3.0.0)
        major (3.0.0)
        prepatch (3.0.1-rc.0)
        preminor (3.1.0-rc.0)
        premajor (4.0.0-rc.0)
        prerelease (3.0.0-rc.6)
        custom

2、引导用户设置版本号,如果选择custom自定义版本,会提示当前的package.json version 为初始值进行编辑。

       √ Select release type · custom
       ? Input custom version » 3.0.0-rc.5

3、使用 semver 插件验证版本有效性。semver 全称Semantic Version 版本命名规范,可以比较两个版本号的大小、验证某个版本号是否合法、提取版本号,例如从“=v1.2.1”体取出"1.2.1"等功能。

4、再次确认版本

? Releasing v3.0.0-rc.6. Confirm? (y/N) » false

5、发布前做单元测试

6、更新 package.json 版本 & 内部依赖模块版本

7、从git元数据生成变更日志,判断有修改信息,进行code提交

8、Pushing to GitHub...

源码注释

/**
 * minimist 轻量级的命令行参数解析引擎
 * process.argv.slice(2) 对应 执行命令参数位置(即第3个起始位,对等下例中"-x 3 -y 4 -n 5 -abc --beep=boop foo bar baz"这部分数据) 实例如下:
 * node example/parse.js -x 3 -y 4 -n 5 -abc --beep=boop foo bar baz
 * args 结果为:{
 *      _: [ 'foo', 'bar', 'baz' ],
 *      x: 3,
 *      y: 4,
 *      n: 5,
 *      a: true,
 *      b: true,
 *      c: true,
 *      beep: 'boop'
 *  }
 */
const args = require('minimist')(process.argv.slice(2))
/** nodejs模块——fs模块:fs模块用于对系统文件及目录进行读写操作 */
const fs = require('fs')
/* nodejs模块:提供文件路径相关api */
const path = require('path')
/* 控制台日志标注样式 */
const chalk = require('chalk')
/**
 * Semantic Version 版本命名规范,提供以下等功能
 * 1. 比较两个版本号的大小
 * 2. 验证某个版本号是否合法
 * 3. 提取版本号,例如从“=v1.2.1”体取出"1.2.1"
 * 4.分析版本号是否属于某个范围或符合一系列条件
 */
const semver = require('semver')
/* 获取跟目录 package.json version信息 */
const currentVersion = require('../package.json').version
/**
 * enquirer: 用户友好、直观且易于创建的时尚CLI提示。
 * CLI(command-line interface,命令行界面)是指可在用户提示符下键入可执行指令的界面
 */
const { prompt } = require('enquirer')
/* 用于执行外部程序 例如:git */
const execa = require('execa')

/**
 * preId
 * prerelease('1.2.3-alpha.1') -> ['alpha', 1]
 */
const preId = args.preid || semver.prerelease(currentVersion)[0] || 'alpha'
const isDryRun = args.dry
const skipTests = args.skipTests
const skipBuild = args.skipBuild
/**
 * 获取模块名称列表
 * [
 *    'compiler-core',
 *    'compiler-dom',
 *    'compiler-sfc',
 *    'compiler-ssr',
 *    'reactivity',
 *    'runtime-core',
 *    'runtime-dom',
 *    'runtime-test',
 *    'server-renderer',
 *    'shared',
 *    'size-check',
 *    'template-explorer',
 *    'vue'
 * ]
 */
const packages = fs
  .readdirSync(path.resolve(__dirname, '../packages'))
  .filter(p => !p.endsWith('.ts') && !p.startsWith('.'))

const skippedPackages = []

const versionIncrements = [
  'patch',
  'minor',
  'major',
  'prepatch',
  'preminor',
  'premajor',
  'prerelease'
]

/**
 * inc 版本升级
 * semver.inc('1.2.3', 'prerelease', 'beta') // 1.2.4-beta.0
 */
const inc = i => semver.inc(currentVersion, i, preId)
// bin: 执行.bin下指令的 函数
const bin = name => path.resolve(__dirname, '../node_modules/.bin/' + name)
// run: 通过execa执行外部程序的函数封装
const run = (bin, args, opts = {}) =>
  execa(bin, args, { stdio: 'inherit', ...opts })
// dryRun: 打印执行程序的相关信息,不会实际执行
const dryRun = (bin, args, opts = {}) =>
  console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
// runIfNotDry: 通过 isDryRun 变量 决定run函数
const runIfNotDry = isDryRun ? dryRun : run
// getPkgRoot: 获取指定模块 pkg 的具体路径
const getPkgRoot = pkg => path.resolve(__dirname, '../packages/' + pkg)
// step: 封装 chalk 输出日志
const step = msg => console.log(chalk.cyan(msg))

// 主函数即入口函数
async function main() {
  let targetVersion = args._[0]
  if (!targetVersion) {
    // no explicit version, offer suggestions
    /**
     * prompt 函数,它接受一个“问题”对象或一组问题对象,并返回一个包含用户响应的对象。
     * @param questions {Array|Object}
     * @returns {Promise}
      ? Select release type ...
      > patch (3.0.0)
        minor (3.0.0)
        major (3.0.0)
        prepatch (3.0.1-rc.0)
        preminor (3.1.0-rc.0)
        premajor (4.0.0-rc.0)
        prerelease (3.0.0-rc.6)
        custom
     */
    const { release } = await prompt({
      type: 'select',
      name: 'release',
      message: 'Select release type',
      choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])
    })

    if (release === 'custom') {
      /**
       * 自定义版本,会提示当前的package.json version 为初始值进行编辑。
       √ Select release type · custom
       ? Input custom version » 3.0.0-rc.5
       */
      targetVersion = (await prompt({
        type: 'input',
        name: 'version',
        message: 'Input custom version',
        initial: currentVersion
      })).version
    } else {
      /**
       * 'prerelease (3.0.0-rc.6)'.match(/\((.*)\)/)[1]
       * 返回:'3.0.0-rc.6'
       */

      targetVersion = release.match(/\((.*)\)/)[1]
    }
  }

  /**
   * 验证版本有效性
   * semver.valid('a.b.c') // null
   * semver.valid('1.2.3') // '1.2.3'
   */
  if (!semver.valid(targetVersion)) {
    throw new Error(`invalid target version: ${targetVersion}`)
  }

  /**
   * 二次确认版本信息
   * ? Releasing v3.0.0-rc.6. Confirm? (y/N) » false
   */
  const { yes } = await prompt({
    type: 'confirm',
    name: 'yes',
    message: `Releasing v${targetVersion}. Confirm?`
  })

  if (!yes) {
    return
  }

  // run tests before release
  // 发布前单元测试
  step('\nRunning tests...')
  if (!skipTests && !isDryRun) {
    await run(bin('jest'), ['--clearCache'])
    await run('yarn', ['test'])
  } else {
    console.log(`(skipped)`)
  }

  // update all package versions and inter-dependencies
  // 更新 package.json 版本 & 内部依赖模块版本
  step('\nUpdating cross dependencies...')
  updateVersions(targetVersion)

  // build all packages with types
  step('\nBuilding all packages...')
  if (!skipBuild && !isDryRun) {
    await run('yarn', ['build', '--release'])
    // test generated dts files
    step('\nVerifying type declarations...')
    await run('yarn', ['test-dts-only'])
  } else {
    console.log(`(skipped)`)
  }

  /**
   * generate changelog
   * 从git元数据生成变更日志
   * conventional-changelog -p angular -i CHANGELOG.md -s
   */
  await run(`yarn`, ['changelog'])

  const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })
  // 判断有修改信息,进行code提交
  if (stdout) {
    step('\nCommitting changes...')
    await runIfNotDry('git', ['add', '-A'])
    await runIfNotDry('git', ['commit', '-m', `release: v${targetVersion}`])
  } else {
    console.log('No changes to commit.')
  }

  // publish packages
  step('\nPublishing packages...')
  for (const pkg of packages) {
    await publishPackage(pkg, targetVersion, runIfNotDry)
  }

  // push to GitHub
  step('\nPushing to GitHub...')
  await runIfNotDry('git', ['tag', `v${targetVersion}`])
  await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
  await runIfNotDry('git', ['push'])

  if (isDryRun) {
    console.log(`\nDry run finished - run git diff to see package changes.`)
  }

  if (skippedPackages.length) {
    console.log(
      chalk.yellow(
        `The following packages are skipped and NOT published:\n- ${skippedPackages.join(
          '\n- '
        )}`
      )
    )
  }
  console.log()
}

function updateVersions(version) {
  // 1. update root package.json
  updatePackage(path.resolve(__dirname, '..'), version)
  // 2. update all packages
  packages.forEach(p => updatePackage(getPkgRoot(p), version))
}

function updatePackage(pkgRoot, version) {
  const pkgPath = path.resolve(pkgRoot, 'package.json')
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
  pkg.version = version
  updateDeps(pkg, 'dependencies', version)
  updateDeps(pkg, 'peerDependencies', version)
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
}

/**
 * 更新内部模块依赖包版本
 * "dependencies": {
 *   "@vue/shared": "3.0.0-rc.5",
 *   "@vue/compiler-dom": "3.0.0-rc.5",
 *   "@vue/runtime-dom": "3.0.0-rc.5"
 * },
 */
function updateDeps(pkg, depType, version) {
  const deps = pkg[depType]
  if (!deps) return
  Object.keys(deps).forEach(dep => {
    // 判断 "@vue/shared" => shared 是 packages 内部模块时再修改版本
    if (
      dep === 'vue' ||
      (dep.startsWith('@vue') && packages.includes(dep.replace(/^@vue\//, '')))
    ) {
      console.log(
        chalk.yellow(`${pkg.name} -> ${depType} -> ${dep}@${version}`)
      )
      deps[dep] = version
    }
  })
}

// 软件包发布到 npm 注册表,该注册表用于在全球范围内分发软件包。
async function publishPackage(pkgName, version, runIfNotDry) {
  if (skippedPackages.includes(pkgName)) {
    return
  }
  const pkgRoot = getPkgRoot(pkgName)
  const pkgPath = path.resolve(pkgRoot, 'package.json')
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
  if (pkg.private) {
    return
  }

  // for now (alpha/beta phase), every package except "vue" can be published as
  // `latest`, whereas "vue" will be published under the "next" tag.
  const releaseTag = pkgName === 'vue' ? 'next' : null

  // TODO use inferred release channel after official 3.0 release
  // const releaseTag = semver.prerelease(version)[0] || null

  step(`Publishing ${pkgName}...`)
  try {
    await runIfNotDry(
      'yarn',
      [
        'publish',
        '--new-version',
        version,
        ...(releaseTag ? ['--tag', releaseTag] : []),
        '--access',
        'public'
      ],
      {
        cwd: pkgRoot,
        stdio: 'pipe'
      }
    )
    console.log(chalk.green(`Successfully published ${pkgName}@${version}`))
  } catch (e) {
    if (e.stderr.match(/previously published/)) {
      console.log(chalk.red(`Skipping already published: ${pkgName}`))
    } else {
      throw e
    }
  }
}

main().catch(err => {
  console.error(err)
})

如果您对 “前端源码” 情有独钟,可微信关注前端源码解析公众号,内容持续更新中!

当前 VUE3.0 源码正在解析中,欢迎捧场!
VUE 3.0 源码 script/release.js 模块发布_第1张图片
~

你可能感兴趣的