prepare-release.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import { Octokit } from '@octokit/rest';
  2. import * as chalk from 'chalk';
  3. import { GitProcess } from 'dugite';
  4. import { execSync } from 'node:child_process';
  5. import { join } from 'node:path';
  6. import { createGitHubTokenStrategy } from './github-token';
  7. import releaseNotesGenerator from './notes';
  8. import { runReleaseCIJobs } from './run-release-ci-jobs';
  9. import { ELECTRON_ORG, ElectronReleaseRepo, VersionBumpType } from './types';
  10. import { getCurrentBranch, ELECTRON_DIR } from '../lib/utils.js';
  11. const pass = chalk.green('✓');
  12. const fail = chalk.red('✗');
  13. enum DryRunMode {
  14. DRY_RUN,
  15. REAL_RUN,
  16. }
  17. type PrepareReleaseOptions = {
  18. targetRepo: ElectronReleaseRepo;
  19. targetBranch?: string;
  20. bumpType: VersionBumpType;
  21. isPreRelease: boolean;
  22. };
  23. async function getNewVersion (
  24. options: PrepareReleaseOptions,
  25. dryRunMode: DryRunMode
  26. ) {
  27. if (dryRunMode === DryRunMode.REAL_RUN) {
  28. console.log(`Bumping for new "${options.bumpType}" version.`);
  29. }
  30. const bumpScript = join(__dirname, 'version-bumper.ts');
  31. const scriptArgs = [
  32. 'node',
  33. 'node_modules/.bin/ts-node',
  34. bumpScript,
  35. `--bump=${options.bumpType}`
  36. ];
  37. if (dryRunMode === DryRunMode.DRY_RUN) scriptArgs.push('--dryRun');
  38. try {
  39. let bumpVersion = execSync(scriptArgs.join(' '), { encoding: 'utf-8' });
  40. bumpVersion = bumpVersion.substr(bumpVersion.indexOf(':') + 1).trim();
  41. const newVersion = `v${bumpVersion}`;
  42. if (dryRunMode === DryRunMode.REAL_RUN) {
  43. console.log(`${pass} Successfully bumped version to ${newVersion}`);
  44. }
  45. return newVersion;
  46. } catch (err) {
  47. console.log(`${fail} Could not bump version, error was:`, err);
  48. throw err;
  49. }
  50. }
  51. async function getReleaseNotes (
  52. options: PrepareReleaseOptions,
  53. currentBranch: string,
  54. newVersion: string
  55. ) {
  56. if (options.bumpType === 'nightly') {
  57. return {
  58. text: 'Nightlies do not get release notes, please compare tags for info.'
  59. };
  60. }
  61. console.log(`Generating release notes for ${currentBranch}.`);
  62. const releaseNotes = await releaseNotesGenerator(currentBranch, newVersion);
  63. if (releaseNotes.warning) {
  64. console.warn(releaseNotes.warning);
  65. }
  66. return releaseNotes;
  67. }
  68. async function createRelease (
  69. options: PrepareReleaseOptions,
  70. branchToTarget: string
  71. ) {
  72. const newVersion = await getNewVersion(options, DryRunMode.REAL_RUN);
  73. const releaseNotes = await getReleaseNotes(
  74. options,
  75. branchToTarget,
  76. newVersion
  77. );
  78. await tagRelease(newVersion);
  79. const octokit = new Octokit({
  80. authStrategy: createGitHubTokenStrategy(options.targetRepo)
  81. });
  82. console.log('Checking for existing draft release.');
  83. const releases = await octokit.repos
  84. .listReleases({
  85. owner: ELECTRON_ORG,
  86. repo: options.targetRepo
  87. })
  88. .catch((err) => {
  89. console.log(`${fail} Could not get releases. Error was: `, err);
  90. throw err;
  91. });
  92. const drafts = releases.data.filter(
  93. (release) => release.draft && release.tag_name === newVersion
  94. );
  95. if (drafts.length > 0) {
  96. console.log(`${fail} Aborting because draft release for
  97. ${drafts[0].tag_name} already exists.`);
  98. process.exit(1);
  99. }
  100. console.log(`${pass} A draft release does not exist; creating one.`);
  101. let releaseBody;
  102. let releaseIsPrelease = false;
  103. if (options.isPreRelease) {
  104. if (newVersion.indexOf('nightly') > 0) {
  105. releaseBody =
  106. 'Note: This is a nightly release. Please file new issues ' +
  107. 'for any bugs you find in it.\n \n This release is published to npm ' +
  108. 'under the electron-nightly package and can be installed via `npm install electron-nightly`, ' +
  109. `or \`npm install electron-nightly@${newVersion.substr(1)}\`.\n \n ${
  110. releaseNotes.text
  111. }`;
  112. } else if (newVersion.indexOf('alpha') > 0) {
  113. releaseBody =
  114. 'Note: This is an alpha release. Please file new issues ' +
  115. 'for any bugs you find in it.\n \n This release is published to npm ' +
  116. 'under the alpha tag and can be installed via `npm install electron@alpha`, ' +
  117. `or \`npm install electron@${newVersion.substr(1)}\`.\n \n ${
  118. releaseNotes.text
  119. }`;
  120. } else {
  121. releaseBody =
  122. 'Note: This is a beta release. Please file new issues ' +
  123. 'for any bugs you find in it.\n \n This release is published to npm ' +
  124. 'under the beta tag and can be installed via `npm install electron@beta`, ' +
  125. `or \`npm install electron@${newVersion.substr(1)}\`.\n \n ${
  126. releaseNotes.text
  127. }`;
  128. }
  129. releaseIsPrelease = true;
  130. } else {
  131. releaseBody = releaseNotes.text;
  132. }
  133. const release = await octokit.repos
  134. .createRelease({
  135. owner: ELECTRON_ORG,
  136. repo: options.targetRepo,
  137. tag_name: newVersion,
  138. draft: true,
  139. name: `electron ${newVersion}`,
  140. body: releaseBody,
  141. prerelease: releaseIsPrelease,
  142. target_commitish: newVersion.includes('nightly')
  143. ? 'main'
  144. : branchToTarget
  145. })
  146. .catch((err) => {
  147. console.log(`${fail} Error creating new release: `, err);
  148. process.exit(1);
  149. });
  150. console.log(`Release has been created with id: ${release.data.id}.`);
  151. console.log(`${pass} Draft release for ${newVersion} successful.`);
  152. }
  153. async function pushRelease (branch: string) {
  154. const pushDetails = await GitProcess.exec(
  155. ['push', 'origin', `HEAD:${branch}`, '--follow-tags'],
  156. ELECTRON_DIR
  157. );
  158. if (pushDetails.exitCode === 0) {
  159. console.log(
  160. `${pass} Successfully pushed the release. Wait for ` +
  161. 'release builds to finish before running "npm run release".'
  162. );
  163. } else {
  164. console.log(`${fail} Error pushing the release: ${pushDetails.stderr}`);
  165. process.exit(1);
  166. }
  167. }
  168. async function runReleaseBuilds (branch: string, newVersion: string) {
  169. await runReleaseCIJobs(branch, {
  170. ci: undefined,
  171. ghRelease: true,
  172. newVersion
  173. });
  174. }
  175. async function tagRelease (version: string) {
  176. console.log(`Tagging release ${version}.`);
  177. const checkoutDetails = await GitProcess.exec(
  178. ['tag', '-a', '-m', version, version],
  179. ELECTRON_DIR
  180. );
  181. if (checkoutDetails.exitCode === 0) {
  182. console.log(`${pass} Successfully tagged ${version}.`);
  183. } else {
  184. console.log(
  185. `${fail} Error tagging ${version}: ` + `${checkoutDetails.stderr}`
  186. );
  187. process.exit(1);
  188. }
  189. }
  190. export async function printNextVersion (options: PrepareReleaseOptions) {
  191. const newVersion = await getNewVersion(options, DryRunMode.DRY_RUN);
  192. console.log(newVersion);
  193. }
  194. export async function prepareRelease (options: PrepareReleaseOptions) {
  195. const currentBranch =
  196. options.targetBranch || (await getCurrentBranch(ELECTRON_DIR));
  197. const newVersion = await getNewVersion(options, DryRunMode.DRY_RUN);
  198. console.log(`${pass} Starting release of ${newVersion}`);
  199. await createRelease(options, currentBranch);
  200. await pushRelease(currentBranch);
  201. await runReleaseBuilds(currentBranch, newVersion);
  202. }