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') const preId = args.preid || (semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0]) const isDryRun = args.dry const skipTests = args.skipTests const skipBuild = args.skipBuild const packages = fs .readdirSync(path.resolve(__dirname, '../packages')) .filter(p => !p.endsWith('.ts') && !p.startsWith('.')) .concat('vue') const versionIncrements = [ 'patch', 'minor', 'major', ...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : []) ] const inc = i => semver.inc(currentVersion, i, preId) const run = (bin, args, opts = {}) => execa(bin, args, { stdio: 'inherit', ...opts }) const dryRun = (bin, args, opts = {}) => console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts) const runIfNotDry = isDryRun ? dryRun : run const step = msg => console.log(chalk.cyan(msg)) 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('pnpm', ['test']) } else { console.log(`(skipped)`) } // update all package versions and inter-dependencies step('\nUpdating package versions...') packages.forEach(p => updatePackage(getPkgRoot(p), targetVersion)) // build all packages with types step('\nBuilding all packages...') if (!skipBuild && !isDryRun) { await run('pnpm', ['run', 'build']) if (skipTests) { await run('pnpm', ['run', 'build:types']) } } else { console.log(`(skipped)`) } // generate changelog step('\nGenerating changelog...') await run(`pnpm`, ['run', 'changelog']) // update pnpm-lock.yaml step('\nUpdating lockfile...') await run(`pnpm`, ['install', '--prefer-offline']) const { stdout } = await run('git', ['diff'], { 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.`) } console.log() } function updatePackage(pkgRoot, version) { const pkgPath = path.resolve(pkgRoot, 'package.json') const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) pkg.version = version fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n') } const getPkgRoot = pkg => pkg === 'vue' ? path.resolve(__dirname, '../') : path.resolve(__dirname, '../packages/' + pkg) async function publishPackage(pkgName, version, runIfNotDry) { const pkgRoot = getPkgRoot(pkgName) const pkgPath = path.resolve(pkgRoot, 'package.json') const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) const publishedName = pkg.name if (pkg.private) { return } 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' } // avoid overwriting tags for v3 if (pkgName === 'vue' || pkgName === 'compiler-sfc') { if (releaseTag) { releaseTag = `v2-${releaseTag}` } else { releaseTag = 'v2-latest' } } step(`Publishing ${publishedName}...`) try { await runIfNotDry( 'pnpm', [ 'publish', ...(releaseTag ? ['--tag', releaseTag] : []), '--access', 'public' ], { cwd: pkgRoot, stdio: 'pipe' } ) console.log( chalk.green(`Successfully published ${publishedName}@${version}`) ) } catch (e) { if (e.stderr.match(/previously published/)) { console.log(chalk.red(`Skipping already published: ${publishedName}`)) } else { throw e } } } main()