release.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. const args = require('minimist')(process.argv.slice(2))
  2. const fs = require('fs')
  3. const path = require('path')
  4. const chalk = require('chalk')
  5. const semver = require('semver')
  6. const currentVersion = require('../package.json').version
  7. const { prompt } = require('enquirer')
  8. const execa = require('execa')
  9. const preId =
  10. args.preid ||
  11. (semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0])
  12. const isDryRun = args.dry
  13. const skipTests = args.skipTests
  14. const skipBuild = args.skipBuild
  15. const packages = fs
  16. .readdirSync(path.resolve(__dirname, '../packages'))
  17. .filter(p => !p.endsWith('.ts') && !p.startsWith('.'))
  18. .concat('vue')
  19. const versionIncrements = [
  20. 'patch',
  21. 'minor',
  22. 'major',
  23. ...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : [])
  24. ]
  25. const inc = i => semver.inc(currentVersion, i, preId)
  26. const run = (bin, args, opts = {}) =>
  27. execa(bin, args, { stdio: 'inherit', ...opts })
  28. const dryRun = (bin, args, opts = {}) =>
  29. console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
  30. const runIfNotDry = isDryRun ? dryRun : run
  31. const step = msg => console.log(chalk.cyan(msg))
  32. async function main() {
  33. let targetVersion = args._[0]
  34. if (!targetVersion) {
  35. // no explicit version, offer suggestions
  36. const { release } = await prompt({
  37. type: 'select',
  38. name: 'release',
  39. message: 'Select release type',
  40. choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])
  41. })
  42. if (release === 'custom') {
  43. targetVersion = (
  44. await prompt({
  45. type: 'input',
  46. name: 'version',
  47. message: 'Input custom version',
  48. initial: currentVersion
  49. })
  50. ).version
  51. } else {
  52. targetVersion = release.match(/\((.*)\)/)[1]
  53. }
  54. }
  55. if (!semver.valid(targetVersion)) {
  56. throw new Error(`invalid target version: ${targetVersion}`)
  57. }
  58. const { yes } = await prompt({
  59. type: 'confirm',
  60. name: 'yes',
  61. message: `Releasing v${targetVersion}. Confirm?`
  62. })
  63. if (!yes) {
  64. return
  65. }
  66. // run tests before release
  67. step('\nRunning tests...')
  68. if (!skipTests && !isDryRun) {
  69. await run('pnpm', ['test'])
  70. } else {
  71. console.log(`(skipped)`)
  72. }
  73. // update all package versions and inter-dependencies
  74. step('\nUpdating package versions...')
  75. packages.forEach(p => updatePackage(getPkgRoot(p), targetVersion))
  76. // build all packages with types
  77. step('\nBuilding all packages...')
  78. if (!skipBuild && !isDryRun) {
  79. await run('pnpm', ['run', 'build'])
  80. if (skipTests) {
  81. await run('pnpm', ['run', 'build:types'])
  82. }
  83. } else {
  84. console.log(`(skipped)`)
  85. }
  86. // generate changelog
  87. step('\nGenerating changelog...')
  88. await run(`pnpm`, ['run', 'changelog'])
  89. // update pnpm-lock.yaml
  90. step('\nUpdating lockfile...')
  91. await run(`pnpm`, ['install', '--prefer-offline'])
  92. const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })
  93. if (stdout) {
  94. step('\nCommitting changes...')
  95. await runIfNotDry('git', ['add', '-A'])
  96. await runIfNotDry('git', ['commit', '-m', `release: v${targetVersion}`])
  97. } else {
  98. console.log('No changes to commit.')
  99. }
  100. // publish packages
  101. step('\nPublishing packages...')
  102. for (const pkg of packages) {
  103. await publishPackage(pkg, targetVersion, runIfNotDry)
  104. }
  105. // push to GitHub
  106. step('\nPushing to GitHub...')
  107. await runIfNotDry('git', ['tag', `v${targetVersion}`])
  108. await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
  109. await runIfNotDry('git', ['push'])
  110. if (isDryRun) {
  111. console.log(`\nDry run finished - run git diff to see package changes.`)
  112. }
  113. console.log()
  114. }
  115. function updatePackage(pkgRoot, version) {
  116. const pkgPath = path.resolve(pkgRoot, 'package.json')
  117. const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
  118. pkg.version = version
  119. fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
  120. }
  121. const getPkgRoot = pkg =>
  122. pkg === 'vue'
  123. ? path.resolve(__dirname, '../')
  124. : path.resolve(__dirname, '../packages/' + pkg)
  125. async function publishPackage(pkgName, version, runIfNotDry) {
  126. const pkgRoot = getPkgRoot(pkgName)
  127. const pkgPath = path.resolve(pkgRoot, 'package.json')
  128. const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
  129. const publishedName = pkg.name
  130. if (pkg.private) {
  131. return
  132. }
  133. let releaseTag = null
  134. if (args.tag) {
  135. releaseTag = args.tag
  136. } else if (version.includes('alpha')) {
  137. releaseTag = 'alpha'
  138. } else if (version.includes('beta')) {
  139. releaseTag = 'beta'
  140. } else if (version.includes('rc')) {
  141. releaseTag = 'rc'
  142. }
  143. // avoid overwriting tags for v3
  144. if (pkgName === 'vue' || pkgName === 'compiler-sfc') {
  145. if (releaseTag) {
  146. releaseTag = `v2-${releaseTag}`
  147. } else {
  148. releaseTag = 'v2-latest'
  149. }
  150. }
  151. step(`Publishing ${publishedName}...`)
  152. try {
  153. await runIfNotDry(
  154. 'pnpm',
  155. [
  156. 'publish',
  157. ...(releaseTag ? ['--tag', releaseTag] : []),
  158. '--access',
  159. 'public'
  160. ],
  161. {
  162. cwd: pkgRoot,
  163. stdio: 'pipe'
  164. }
  165. )
  166. console.log(
  167. chalk.green(`Successfully published ${publishedName}@${version}`)
  168. )
  169. } catch (e) {
  170. if (e.stderr.match(/previously published/)) {
  171. console.log(chalk.red(`Skipping already published: ${publishedName}`))
  172. } else {
  173. throw e
  174. }
  175. }
  176. }
  177. main()