How is vue.js released
Original link: github.com/upupming/vu…
The preparatory work
Read the source code for the first time to participate in reading activities, read the summary of the tool function of the last period, learn a lot, feel like if chuan big brother to learn will certainly harvest full.
This time to read the source at: github1s.com/vuejs/vue-n… .
Clone the repository and run YARN to install dependencies.
There is a release script in package.json to run this file:
"release": "node scripts/release.js".Copy the code
You can add a — DRY (empty run) parameter to not test, compile, or push git.
You can then start debugging by pressing the Debug button directly above the scripts of the package.json file.
Run it first to see what it looks like:
You can see that the test and build sections are skipped, the commit and push sections are [dryrun], and the instructions are skipped as well (just print what dryRun would run if it wasn’t).
When it is done, it is found that Changelog.md has added all the commits since the last release (but the ones that chore does not care about are ignored) :
Since the entire project is a monorepo, the version numbers of all packages in package.json and their dependent internal packages are updated correctly:
Next, let’s analyze the source code.
Depend on the package
The first few lines show all the packages that the script depends on, so let’s look at each in detail.
const args = require('minimist')(process.argv.slice(2))
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const semver = require('semver')
const currentVersion = require('.. /package.json').version
const { prompt } = require('enquirer')
const execa = require('execa')
Copy the code
minimist
www.npmjs.com/package/min…
It is used to parse command line parameters.
var argv = require('minimist')(process.argv.slice(2));
console.log(argv);
Copy the code
$ node example/parse.js -a beep -b boop
{ _: [], a: 'beep', b: 'boop' }
$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ 'foo'.'bar'.'baz' ],
x: 3,
y: 4,
n: 5,
a: true,
b: true,
c: true,
beep: 'boop' }
Copy the code
As you can see in argv, _ is an array of all inputs that are not options, which are stored as argv[‘option’]=value.
A similar bag is the Yargs
chalk
www.npmjs.com/package/cha…
Used to add color to a string.
const chalk = require('chalk');
// Use chalk. Blue to print blue
console.log(chalk.blue('Hello world! '));
Copy the code
semver
www.npmjs.com/package/sem…
The Semantic Versioning helper package has some judgment and processing logic for some version numbers.
const semver = require('semver')
semver.valid("1.2.3") / / "1.2.3"
semver.valid('a.b.c') // null
semver.clean('= v1.2.3') / / "1.2.3"
semver.satisfies("1.2.3".'1. X | | > = 2.5.0 | | 5.0.0-7.2.3') // true
semver.gt("1.2.3".'9.8.7') // false
semver.lt("1.2.3".'9.8.7') // true
semver.minVersion('> = 1.0.0') / / '1.0.0'
// coerce is used to coerce the input into semver objects
semver.valid(semver.coerce('v2')) / / '2.0.0'
semver.valid(semver.coerce('42.6.7.9.3 - alpha')) / / '42.6.7'
Copy the code
enquirer
www.npmjs.com/package/enq…
It is used for interactive command line input.
const { prompt } = require('enquirer');
const response = await prompt({
type: 'input'.name: 'username'.message: 'What is your username? '
});
console.log(response); // { username: 'jonschlinkert' }
Copy the code
execa
www.npmjs.com/package/exe…
Nodejs’s child_process package has similar functionality, but is much easier to use.
const execa = require('execa');
(async() = > {const {stdout} = await execa('echo'['unicorns']);
console.log(stdout);
//=> 'unicorns'}) ();Copy the code
Obtaining Configuration Information
const currentVersion = require('.. /package.json').version
/ / semver. Prerelease (1.2.3 - alpha. '1') - > [' alpha ', 1)
const preId =
args.preid ||
(semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0])
const isDryRun = args.dry
const skipTests = args.skipTests
const skipBuild = args.skipBuild
// Get the names of all subpackages, filter out.ts files and hide files
const packages = fs
.readdirSync(path.resolve(__dirname, '.. /packages'))
.filter(p= >! p.endsWith('.ts') && !p.startsWith('. '))
// Skip the package, there is no empty array, should not be used
const skippedPackages = []
// Upgrade options available during subsequent interactive input
const versionIncrements = [
'patch'.'minor'.'major'. (preId ? ['prepatch'.'preminor'.'premajor'.'prerelease'] : [])]// The following are some auxiliary functions
The inc function returns the new version number given the release type
/ / semver. Inc (' 1.2.3 ', 'prerelease', 'beta') - > '1 - beta. 0'
const inc = i= > semver.inc(currentVersion, i, preId)
const bin = name= > path.resolve(__dirname, '.. /node_modules/.bin/' + name)
// Run any executable file
const run = (bin, args, opts = {}) = >
execa(bin, args, { stdio: 'inherit'. opts })// Empty run: print the running parameters
const dryRun = (bin, args, opts = {}) = >
console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
const runIfNotDry = isDryRun ? dryRun : run
const getPkgRoot = pkg= > path.resolve(__dirname, '.. /packages/' + pkg)
const step = msg= > console.log(chalk.cyan(msg))
Copy the code
The main main process
The main process is divided into the following steps:
- Determine the new version number to release
targetVersion
- Command line specification
- The user to select
versionIncrements
One of them gets the new version number - User input
- Run the test
- call
updateVersions
Function updates the version numbers of all subpackages, as well as their dependencies - Build all package
- run
yarn changelog
Generate the changelog - If the file changes, git commit all changes
- Called separately for each package
publishPackage
Function to publish- Also note that the package corresponding to the project root directory is not published, and its
package.json
None at allname
Field, you can think of it as just a Monorepo configuration
- Also note that the package corresponding to the project root directory is not published, and its
- Push to making
The two functions updateVersions and publishPackage are called, and we’ll see the logic later.
async function main() {
let targetVersion = args._[0]
if(! targetVersion) {// no explicit version, offer suggestions
const { release } = await prompt({
type: 'select'.name: 'release'.message: 'Select release type'.choices: versionIncrements.map(i= > `${i} (${inc(i)}) `).concat(['custom'])})if (release === 'custom') {
targetVersion = (
await prompt({
type: 'input'.name: 'version'.message: 'Input custom version'.initial: currentVersion
})
).version
} else {
targetVersion = release.match(/ / / ((. *) \)) [1]}}if(! semver.valid(targetVersion)) {throw new Error(`invalid target version: ${targetVersion}`)}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'.'--bail'])}else {
console.log(`(skipped)`)}// update all package versions and inter-dependencies
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
await run(`yarn`['changelog'])
const { stdout } = await run('git'['diff'] and {stdio: 'pipe' })
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()
}
main().catch(err= > {
console.error(err)
})
Copy the code
updateVersions
function
UpdateVersions accepts only one version parameter because the version number of all packages is the same from release to release. This simplifies the processing logic, but even if a package has not been updated, it will be published with the updated version number.
For the root directory and all subpackages, the package.json file is updated using the updatePackage function, which updates the Version, dependencies, and peerDependencies fields.
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')}function updateDeps(pkg, depType, version) {
const deps = pkg[depType]
if(! deps)return
Object.keys(deps).forEach(dep= > {
if (
dep === 'vue' ||
(dep.startsWith('@vue') && packages.includes(dep.replace(/^@vue\//.' ')))) {console.log(
chalk.yellow(`${pkg.name} -> ${depType} -> ${dep}@${version}`)
)
deps[dep] = version
}
})
}
Copy the code
publishPackage
function
PublishPackage is used to publish individual packages.
Vue 2 is still installed by default when NPM I vue is installed, as you can see here that Vue 3 is released with the next tag, which requires NPM I vue@next.
If an error is reported that a package with the same version number has been sent, skip it.
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, all 3.x packages except "vue" can be published as
// `latest`, whereas "vue" will be published under the "next" tag.
let releaseTag = null
if (args.tag) {
releaseTag = args.tag
} else if (version.includes('alpha')) {
releaseTag = 'alpha'
} else if (version.includes('beta')) {
releaseTag = 'beta'
} else if (version.includes('rc')) {
releaseTag = 'rc'
} else if (pkgName === 'vue') {
// TODO remove when 3.x becomes default
releaseTag = 'next'
}
// TODO use demographics 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'] and {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
}
}
}
Copy the code
Automatically generate GitHub Release
After git push, according to the GitHub Action configuration of the project, the corresponding release will be automatically generated according to the tag, such as release: v3.2.4. GitHub release is as follows:
conclusion
- Feel very clear code logic, good at extracting tool functions, e.g
runIfNotDry
.getPkgRoot
These things, you can reduce the amount of code - Learned how to use it
semver
The package automatically generates a new version number, so try not to manually pick a version number name in the future. - All subpackages under a monorepo keep the same version number, making it easier to operate