prepare-release.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. #!/usr/bin/env node
  2. if (!process.env.CI) require('dotenv-safe').load()
  3. const args = require('minimist')(process.argv.slice(2), {
  4. boolean: ['automaticRelease', 'notesOnly', 'stable']
  5. })
  6. const ciReleaseBuild = require('./ci-release-build')
  7. const octokit = require('@octokit/rest')({
  8. auth: process.env.ELECTRON_GITHUB_TOKEN
  9. })
  10. const { execSync } = require('child_process')
  11. const { GitProcess } = require('dugite')
  12. const path = require('path')
  13. const readline = require('readline')
  14. const releaseNotesGenerator = require('./release-notes/index.js')
  15. const { getCurrentBranch } = require('./lib/utils.js')
  16. const bumpType = args._[0]
  17. const targetRepo = bumpType === 'nightly' ? 'nightlies' : 'electron'
  18. require('colors')
  19. const pass = '\u2713'.green
  20. const fail = '\u2717'.red
  21. if (!bumpType && !args.notesOnly) {
  22. console.log(`Usage: prepare-release [stable | beta | nightly]` +
  23. ` (--stable) (--notesOnly) (--automaticRelease) (--branch)`)
  24. process.exit(1)
  25. }
  26. const gitDir = path.resolve(__dirname, '..')
  27. async function getNewVersion (dryRun) {
  28. if (!dryRun) {
  29. console.log(`Bumping for new "${bumpType}" version.`)
  30. }
  31. const bumpScript = path.join(__dirname, 'bump-version.js')
  32. const scriptArgs = ['node', bumpScript, `--bump=${bumpType}`]
  33. if (dryRun) scriptArgs.push('--dryRun')
  34. try {
  35. let bumpVersion = execSync(scriptArgs.join(' '), { encoding: 'UTF-8' })
  36. bumpVersion = bumpVersion.substr(bumpVersion.indexOf(':') + 1).trim()
  37. const newVersion = `v${bumpVersion}`
  38. if (!dryRun) {
  39. console.log(`${pass} Successfully bumped version to ${newVersion}`)
  40. }
  41. return newVersion
  42. } catch (err) {
  43. console.log(`${fail} Could not bump version, error was:`, err)
  44. throw err
  45. }
  46. }
  47. async function getReleaseNotes (currentBranch, newVersion) {
  48. if (bumpType === 'nightly') {
  49. return { text: 'Nightlies do not get release notes, please compare tags for info.' }
  50. }
  51. console.log(`Generating release notes for ${currentBranch}.`)
  52. const releaseNotes = await releaseNotesGenerator(currentBranch, newVersion)
  53. if (releaseNotes.warning) {
  54. console.warn(releaseNotes.warning)
  55. }
  56. return releaseNotes
  57. }
  58. async function createRelease (branchToTarget, isBeta) {
  59. const newVersion = await getNewVersion()
  60. const releaseNotes = await getReleaseNotes(branchToTarget, newVersion)
  61. await tagRelease(newVersion)
  62. console.log(`Checking for existing draft release.`)
  63. const releases = await octokit.repos.listReleases({
  64. owner: 'electron',
  65. repo: targetRepo
  66. }).catch(err => {
  67. console.log(`${fail} Could not get releases. Error was: `, err)
  68. })
  69. const drafts = releases.data.filter(release => release.draft &&
  70. release.tag_name === newVersion)
  71. if (drafts.length > 0) {
  72. console.log(`${fail} Aborting because draft release for
  73. ${drafts[0].tag_name} already exists.`)
  74. process.exit(1)
  75. }
  76. console.log(`${pass} A draft release does not exist; creating one.`)
  77. let releaseBody
  78. let releaseIsPrelease = false
  79. if (isBeta) {
  80. if (newVersion.indexOf('nightly') > 0) {
  81. releaseBody = `Note: This is a nightly release. Please file new issues ` +
  82. `for any bugs you find in it.\n \n This release is published to npm ` +
  83. `under the nightly tag and can be installed via npm install electron@nightly, ` +
  84. `or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes.text}`
  85. } else {
  86. releaseBody = `Note: This is a beta release. Please file new issues ` +
  87. `for any bugs you find in it.\n \n This release is published to npm ` +
  88. `under the beta tag and can be installed via npm install electron@beta, ` +
  89. `or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes.text}`
  90. }
  91. releaseIsPrelease = true
  92. } else {
  93. releaseBody = releaseNotes.text
  94. }
  95. const release = await octokit.repos.createRelease({
  96. owner: 'electron',
  97. repo: targetRepo,
  98. tag_name: newVersion,
  99. draft: true,
  100. name: `electron ${newVersion}`,
  101. body: releaseBody,
  102. prerelease: releaseIsPrelease,
  103. target_commitish: newVersion.indexOf('nightly') !== -1 ? 'master' : branchToTarget
  104. }).catch(err => {
  105. console.log(`${fail} Error creating new release: `, err)
  106. process.exit(1)
  107. })
  108. console.log(`Release has been created with id: ${release.data.id}.`)
  109. console.log(`${pass} Draft release for ${newVersion} successful.`)
  110. }
  111. async function pushRelease (branch) {
  112. const pushDetails = await GitProcess.exec(['push', 'origin', `HEAD:${branch}`, '--follow-tags'], gitDir)
  113. if (pushDetails.exitCode === 0) {
  114. console.log(`${pass} Successfully pushed the release. Wait for ` +
  115. `release builds to finish before running "npm run release".`)
  116. } else {
  117. console.log(`${fail} Error pushing the release: ${pushDetails.stderr}`)
  118. process.exit(1)
  119. }
  120. }
  121. async function runReleaseBuilds (branch) {
  122. await ciReleaseBuild(branch, {
  123. ghRelease: true,
  124. automaticRelease: args.automaticRelease
  125. })
  126. }
  127. async function tagRelease (version) {
  128. console.log(`Tagging release ${version}.`)
  129. const checkoutDetails = await GitProcess.exec([ 'tag', '-a', '-m', version, version ], gitDir)
  130. if (checkoutDetails.exitCode === 0) {
  131. console.log(`${pass} Successfully tagged ${version}.`)
  132. } else {
  133. console.log(`${fail} Error tagging ${version}: ` +
  134. `${checkoutDetails.stderr}`)
  135. process.exit(1)
  136. }
  137. }
  138. async function verifyNewVersion () {
  139. const newVersion = await getNewVersion(true)
  140. let response
  141. if (args.automaticRelease) {
  142. response = 'y'
  143. } else {
  144. response = await promptForVersion(newVersion)
  145. }
  146. if (response.match(/^y/i)) {
  147. console.log(`${pass} Starting release of ${newVersion}`)
  148. } else {
  149. console.log(`${fail} Aborting release of ${newVersion}`)
  150. process.exit()
  151. }
  152. }
  153. async function promptForVersion (version) {
  154. return new Promise((resolve, reject) => {
  155. const rl = readline.createInterface({
  156. input: process.stdin,
  157. output: process.stdout
  158. })
  159. rl.question(`Do you want to create the release ${version.green} (y/N)? `, (answer) => {
  160. rl.close()
  161. resolve(answer)
  162. })
  163. })
  164. }
  165. // function to determine if there have been commits to master since the last release
  166. async function changesToRelease () {
  167. const lastCommitWasRelease = new RegExp(`^Bump v[0-9.]*(-beta[0-9.]*)?(-nightly[0-9.]*)?$`, 'g')
  168. const lastCommit = await GitProcess.exec(['log', '-n', '1', `--pretty=format:'%s'`], gitDir)
  169. return !lastCommitWasRelease.test(lastCommit.stdout)
  170. }
  171. async function prepareRelease (isBeta, notesOnly) {
  172. if (args.dryRun) {
  173. const newVersion = await getNewVersion(true)
  174. console.log(newVersion)
  175. } else {
  176. const currentBranch = (args.branch) ? args.branch : await getCurrentBranch(gitDir)
  177. if (notesOnly) {
  178. const newVersion = await getNewVersion(true)
  179. const releaseNotes = await getReleaseNotes(currentBranch, newVersion)
  180. console.log(`Draft release notes are: \n${releaseNotes.text}`)
  181. } else {
  182. const changes = await changesToRelease(currentBranch)
  183. if (changes) {
  184. await verifyNewVersion()
  185. await createRelease(currentBranch, isBeta)
  186. await pushRelease(currentBranch)
  187. await runReleaseBuilds(currentBranch)
  188. } else {
  189. console.log(`There are no new changes to this branch since the last release, aborting release.`)
  190. process.exit(1)
  191. }
  192. }
  193. }
  194. }
  195. prepareRelease(!args.stable, args.notesOnly)