文件门路: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 源码正在解析中,欢送捧场!
~