// @ts-checkimport fs from 'node:fs'import path from 'node:path'import pico from 'picocolors'import semver from 'semver'import enquirer from 'enquirer'import { createRequire } from 'node:module'import { fileURLToPath } from 'node:url'import { exec } from './utils.js'import { parseArgs } from 'node:util'/*** @typedef {{* name: string* version: string* dependencies?: { [dependenciesPackageName: string]: string }* peerDependencies?: { [peerDependenciesPackageName: string]: string }* }} Package*/let versionUpdated = falseconst { prompt } = enquirerconst currentVersion = createRequire(import.meta.url)('../package.json').versionconst __dirname = path.dirname(fileURLToPath(import.meta.url))const { values: args, positionals } = parseArgs({allowPositionals: true,options: {preid: {type: 'string',},dry: {type: 'boolean',},tag: {type: 'string',},skipBuild: {type: 'boolean',},skipTests: {type: 'boolean',},skipGit: {type: 'boolean',},skipPrompts: {type: 'boolean',},publish: {type: 'boolean',default: false,},publishOnly: {type: 'boolean',},registry: {type: 'string',},},})const preId = args.preid || semver.prerelease(currentVersion)?.[0]const isDryRun = args.dry/** @type {boolean | undefined} */let skipTests = args.skipTestsconst skipBuild = args.skipBuildconst skipPrompts = args.skipPromptsconst skipGit = args.skipGitconst packages = fs.readdirSync(path.resolve(__dirname, '../packages')).filter(p => {const pkgRoot = path.resolve(__dirname, '../packages', p)if (fs.statSync(pkgRoot).isDirectory()) {const pkg = JSON.parse(fs.readFileSync(path.resolve(pkgRoot, 'package.json'), 'utf-8'),)return !pkg.private}})const isCorePackage = (/** @type {string} */ pkgName) => {if (!pkgName) returnif (pkgName === 'vue' || pkgName === '@vue/compat') {return true}return (pkgName.startsWith('@vue') &&packages.includes(pkgName.replace(/^@vue\//, '')))}const keepThePackageName = (/** @type {string} */ pkgName) => pkgName/** @type {string[]} */const skippedPackages = []/** @type {ReadonlyArray<import('semver').ReleaseType>} */const versionIncrements = ['patch','minor','major',...(preId? /** @type {const} */ (['prepatch', 'preminor', 'premajor', 'prerelease']): []),]const inc = (/** @type {import('semver').ReleaseType} */ i) =>semver.inc(currentVersion, i, typeof preId === 'string' ? preId : undefined)const run = async (/** @type {string} */ bin,/** @type {ReadonlyArray<string>} */ args,/** @type {import('node:child_process').SpawnOptions} */ opts = {},) => exec(bin, args, { stdio: 'inherit', ...opts })const dryRun = async (/** @type {string} */ bin,/** @type {ReadonlyArray<string>} */ args,/** @type {import('node:child_process').SpawnOptions} */ opts = {},) => console.log(pico.blue(`[dryrun] ${bin}${args.join('')}`), opts)const runIfNotDry = isDryRun ? dryRun : runconst getPkgRoot = (/** @type {string} */ pkg) =>path.resolve(__dirname, '../packages/' + pkg)const step = (/** @type {string} */ msg) => console.log(pico.cyan(msg))async function main() {if (!(await isInSyncWithRemote())) {return} else {console.log(`${pico.green(`✓`)} commit is up-to-date with remote.\n`)}let targetVersion = positionals[0]if (!targetVersion) {// no explicit version, offer suggestions/** @type {{ release: string }} */const { release } = await prompt({type: 'select',name: 'release',message: 'Select release type',choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom']),})if (release === 'custom') {/** @type {{ version: string }} */const result = await prompt({type: 'input',name: 'version',message: 'Input custom version',initial: currentVersion,})targetVersion = result.version} else {targetVersion = release.match(/\((.*)\)/)?.[1] ?? ''}}// @ts-expect-errorif (versionIncrements.includes(targetVersion)) {// @ts-expect-errortargetVersion = inc(targetVersion)}if (!semver.valid(targetVersion)) {throw new Error(`invalid target version: ${targetVersion}`)}if (skipPrompts) {step(`Releasing v${targetVersion}...`)} else {/** @type {{ yes: boolean }} */const { yes: confirmRelease } = await prompt({type: 'confirm',name: 'yes',message: `Releasing v${targetVersion}. Confirm?`,})if (!confirmRelease) {return}}await runTestsIfNeeded()// update all package versions and inter-dependenciesstep('\nUpdating cross dependencies...')updateVersions(targetVersion, keepThePackageName)versionUpdated = true// generate changelogstep('\nGenerating changelog...')await run(`pnpm`, ['run', 'changelog'])if (!skipPrompts) {/** @type {{ yes: boolean }} */const { yes: changelogOk } = await prompt({type: 'confirm',name: 'yes',message: `Changelog generated. Does it look good?`,})if (!changelogOk) {return}}// update pnpm-lock.yamlstep('\nUpdating lockfile...')await run(`pnpm`, ['install', '--prefer-offline'])if (!skipGit) {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 packagesif (args.publish) {await buildPackages()await publishPackages(targetVersion)}// push to GitHubif (!skipGit) {step('\nPushing to GitHub...')await runIfNotDry('git', ['tag', `v${targetVersion}`])await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])await runIfNotDry('git', ['push'])}if (!args.publish) {console.log(pico.yellow('\nRelease will be done via GitHub Actions.\n' +'Check status at https://github.com/vuejs/core/actions/workflows/release.yml',),)}if (isDryRun) {console.log(`\nDry run finished - run git diff to see package changes.`)}if (skippedPackages.length) {console.log(pico.yellow(`The following packages are skipped and NOT published:\n- ${skippedPackages.join('\n- ',)}`,),)}console.log()}async function runTestsIfNeeded() {if (!skipTests) {step('Checking CI status for HEAD...')let isCIPassed = await getCIResult()skipTests ||= isCIPassedif (isCIPassed) {if (!skipPrompts) {/** @type {{ yes: boolean }} */const { yes: promptSkipTests } = await prompt({type: 'confirm',name: 'yes',message: `CI for this commit passed. Skip local tests?`,})skipTests = promptSkipTests} else {skipTests = true}} else if (skipPrompts) {throw new Error('CI for the latest commit has not passed yet. ' +'Only run the release workflow after the CI has passed.',)}}if (!skipTests) {step('\nRunning tests...')if (!isDryRun) {await run('pnpm', ['run', 'test', '--run'])} else {console.log(`Skipped (dry run)`)}} else {step('Tests skipped.')}}async function getCIResult() {try {const sha = await getSha()const res = await fetch(`https://api.github.com/repos/vuejs/core/actions/runs?head_sha=${sha}` +`&status=success&exclude_pull_requests=true`,)/** @type {{ workflow_runs: ({ name: string, conclusion: string })[] }} */const data = await res.json()return data.workflow_runs.some(({ name, conclusion }) => {return name === 'ci' && conclusion === 'success'})} catch {console.error('Failed to get CI status for current commit.')return false}}async function isInSyncWithRemote() {try {const branch = await getBranch()const res = await fetch(`https://api.github.com/repos/vuejs/core/commits/${branch}?per_page=1`,)const data = await res.json()if (data.sha === (await getSha())) {return true} else {/** @type {{ yes: boolean }} */const { yes } = await prompt({type: 'confirm',name: 'yes',message: pico.red(`Local HEAD is not up-to-date with remote. Are you sure you want to continue?`,),})return yes}} catch {console.error(pico.red('Failed to check whether local HEAD is up-to-date with remote.'),)return false}}async function getSha() {return (await exec('git', ['rev-parse', 'HEAD'])).stdout}async function getBranch() {return (await exec('git', ['rev-parse', '--abbrev-ref', 'HEAD'])).stdout}/*** @param {string} version* @param {(pkgName: string) => string} getNewPackageName*/function updateVersions(version, getNewPackageName = keepThePackageName) {// 1. update root package.jsonupdatePackage(path.resolve(__dirname, '..'), version, getNewPackageName)// 2. update all packagespackages.forEach(p =>updatePackage(getPkgRoot(p), version, getNewPackageName),)}/*** @param {string} pkgRoot* @param {string} version* @param {(pkgName: string) => string} getNewPackageName*/function updatePackage(pkgRoot, version, getNewPackageName) {const pkgPath = path.resolve(pkgRoot, 'package.json')/** @type {Package} */const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))pkg.name = getNewPackageName(pkg.name)pkg.version = versionfs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')}async function buildPackages() {step('\nBuilding all packages...')if (!skipBuild) {await run('pnpm', ['run', 'build', '--withTypes'])} else {console.log(`(skipped)`)}}/*** @param {string} version*/async function publishPackages(version) {// publish packagesstep('\nPublishing packages...')const additionalPublishFlags = []if (isDryRun) {additionalPublishFlags.push('--dry-run')}if (isDryRun || skipGit || process.env.CI) {additionalPublishFlags.push('--no-git-checks')}// add provenance metadata when releasing from CI// skip provenance if not publishing to actual npmif (process.env.CI && !args.registry) {additionalPublishFlags.push('--provenance')}for (const pkg of packages) {await publishPackage(pkg, version, additionalPublishFlags)}}/*** @param {string} pkgName* @param {string} version* @param {ReadonlyArray<string>} additionalFlags*/async function publishPackage(pkgName, version, additionalFlags) {if (skippedPackages.includes(pkgName)) {return}let releaseTag = nullif (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'}step(`Publishing ${pkgName}...`)try {// Don't change the package manager here as we rely on pnpm to handle// workspace:* depsawait run('pnpm',['publish',...(releaseTag ? ['--tag', releaseTag] : []),'--access','public',...(args.registry ? ['--registry', args.registry] : []),...additionalFlags,],{cwd: getPkgRoot(pkgName),stdio: 'pipe',},)console.log(pico.green(`Successfully published ${pkgName}@${version}`))} catch (/** @type {any} */ e) {if (e.message?.match(/previously published/)) {console.log(pico.red(`Skipping already published: ${pkgName}`))} else {throw e}}}async function publishOnly() {const targetVersion = positionals[0]if (targetVersion) {updateVersions(targetVersion)}await buildPackages()await publishPackages(currentVersion)}const fnToRun = args.publishOnly ? publishOnly : mainfnToRun().catch(err => {if (versionUpdated) {// revert to current version on failed releasesupdateVersions(currentVersion)}console.error(err)process.exit(1)})
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。