prepare-release.js 7.3 KB

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