|
@@ -1,75 +1,43 @@
|
|
|
-#!/usr/bin/env node
|
|
|
-
|
|
|
import { Octokit } from '@octokit/rest';
|
|
|
import * as chalk from 'chalk';
|
|
|
import { GitProcess } from 'dugite';
|
|
|
import { execSync } from 'node:child_process';
|
|
|
import { join } from 'node:path';
|
|
|
-import { createInterface } from 'node:readline';
|
|
|
-import { parseArgs } from 'node:util';
|
|
|
|
|
|
-import ciReleaseBuild from './ci-release-build';
|
|
|
+import { runReleaseCIJobs } from './run-release-ci-jobs';
|
|
|
import releaseNotesGenerator from './notes';
|
|
|
import { getCurrentBranch, ELECTRON_DIR } from '../lib/utils.js';
|
|
|
import { createGitHubTokenStrategy } from './github-token';
|
|
|
-import { ELECTRON_REPO, ElectronReleaseRepo, NIGHTLY_REPO } from './types';
|
|
|
-
|
|
|
-const { values: { notesOnly, dryRun: dryRunArg, stable: isStableArg, branch: branchArg, automaticRelease }, positionals } = parseArgs({
|
|
|
- options: {
|
|
|
- notesOnly: {
|
|
|
- type: 'boolean'
|
|
|
- },
|
|
|
- dryRun: {
|
|
|
- type: 'boolean'
|
|
|
- },
|
|
|
- stable: {
|
|
|
- type: 'boolean'
|
|
|
- },
|
|
|
- branch: {
|
|
|
- type: 'string'
|
|
|
- },
|
|
|
- automaticRelease: {
|
|
|
- type: 'boolean'
|
|
|
- }
|
|
|
- },
|
|
|
- allowPositionals: true
|
|
|
-});
|
|
|
-
|
|
|
-const bumpType = positionals[0];
|
|
|
-const targetRepo = getRepo();
|
|
|
-
|
|
|
-function getRepo (): ElectronReleaseRepo {
|
|
|
- return bumpType === 'nightly' ? NIGHTLY_REPO : ELECTRON_REPO;
|
|
|
-}
|
|
|
-
|
|
|
-const octokit = new Octokit({
|
|
|
- authStrategy: createGitHubTokenStrategy(getRepo())
|
|
|
-});
|
|
|
+import { ELECTRON_ORG, ElectronReleaseRepo, VersionBumpType } from './types';
|
|
|
|
|
|
const pass = chalk.green('✓');
|
|
|
const fail = chalk.red('✗');
|
|
|
|
|
|
-if (!bumpType && !notesOnly) {
|
|
|
- console.log('Usage: prepare-release [stable | minor | beta | alpha | nightly]' +
|
|
|
- ' (--stable) (--notesOnly) (--automaticRelease) (--branch)');
|
|
|
- process.exit(1);
|
|
|
-}
|
|
|
-
|
|
|
enum DryRunMode {
|
|
|
DRY_RUN,
|
|
|
REAL_RUN,
|
|
|
}
|
|
|
|
|
|
-async function getNewVersion (dryRunMode: DryRunMode) {
|
|
|
+type PrepareReleaseOptions = {
|
|
|
+ targetRepo: ElectronReleaseRepo;
|
|
|
+ targetBranch?: string;
|
|
|
+ bumpType: VersionBumpType;
|
|
|
+ isPreRelease: boolean;
|
|
|
+};
|
|
|
+
|
|
|
+async function getNewVersion (
|
|
|
+ options: PrepareReleaseOptions,
|
|
|
+ dryRunMode: DryRunMode
|
|
|
+) {
|
|
|
if (dryRunMode === DryRunMode.REAL_RUN) {
|
|
|
- console.log(`Bumping for new "${bumpType}" version.`);
|
|
|
+ console.log(`Bumping for new "${options.bumpType}" version.`);
|
|
|
}
|
|
|
const bumpScript = join(__dirname, 'version-bumper.ts');
|
|
|
const scriptArgs = [
|
|
|
'node',
|
|
|
'node_modules/.bin/ts-node',
|
|
|
bumpScript,
|
|
|
- `--bump=${bumpType}`
|
|
|
+ `--bump=${options.bumpType}`
|
|
|
];
|
|
|
if (dryRunMode === DryRunMode.DRY_RUN) scriptArgs.push('--dryRun');
|
|
|
try {
|
|
@@ -86,9 +54,15 @@ async function getNewVersion (dryRunMode: DryRunMode) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function getReleaseNotes (currentBranch: string, newVersion: string) {
|
|
|
- if (bumpType === 'nightly') {
|
|
|
- return { text: 'Nightlies do not get release notes, please compare tags for info.' };
|
|
|
+async function getReleaseNotes (
|
|
|
+ options: PrepareReleaseOptions,
|
|
|
+ currentBranch: string,
|
|
|
+ newVersion: string
|
|
|
+) {
|
|
|
+ if (options.bumpType === 'nightly') {
|
|
|
+ return {
|
|
|
+ text: 'Nightlies do not get release notes, please compare tags for info.'
|
|
|
+ };
|
|
|
}
|
|
|
console.log(`Generating release notes for ${currentBranch}.`);
|
|
|
const releaseNotes = await releaseNotesGenerator(currentBranch, newVersion);
|
|
@@ -98,22 +72,36 @@ async function getReleaseNotes (currentBranch: string, newVersion: string) {
|
|
|
return releaseNotes;
|
|
|
}
|
|
|
|
|
|
-async function createRelease (branchToTarget: string, isPreRelease: boolean) {
|
|
|
- const newVersion = await getNewVersion(DryRunMode.REAL_RUN);
|
|
|
- const releaseNotes = await getReleaseNotes(branchToTarget, newVersion);
|
|
|
+async function createRelease (
|
|
|
+ options: PrepareReleaseOptions,
|
|
|
+ branchToTarget: string
|
|
|
+) {
|
|
|
+ const newVersion = await getNewVersion(options, DryRunMode.REAL_RUN);
|
|
|
+ const releaseNotes = await getReleaseNotes(
|
|
|
+ options,
|
|
|
+ branchToTarget,
|
|
|
+ newVersion
|
|
|
+ );
|
|
|
await tagRelease(newVersion);
|
|
|
|
|
|
- console.log('Checking for existing draft release.');
|
|
|
- const releases = await octokit.repos.listReleases({
|
|
|
- owner: 'electron',
|
|
|
- repo: targetRepo
|
|
|
- }).catch(err => {
|
|
|
- console.log(`${fail} Could not get releases. Error was: `, err);
|
|
|
- throw err;
|
|
|
+ const octokit = new Octokit({
|
|
|
+ authStrategy: createGitHubTokenStrategy(options.targetRepo)
|
|
|
});
|
|
|
|
|
|
- const drafts = releases.data.filter(release => release.draft &&
|
|
|
- release.tag_name === newVersion);
|
|
|
+ console.log('Checking for existing draft release.');
|
|
|
+ const releases = await octokit.repos
|
|
|
+ .listReleases({
|
|
|
+ owner: ELECTRON_ORG,
|
|
|
+ repo: options.targetRepo
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.log(`${fail} Could not get releases. Error was: `, err);
|
|
|
+ throw err;
|
|
|
+ });
|
|
|
+
|
|
|
+ const drafts = releases.data.filter(
|
|
|
+ (release) => release.draft && release.tag_name === newVersion
|
|
|
+ );
|
|
|
if (drafts.length > 0) {
|
|
|
console.log(`${fail} Aborting because draft release for
|
|
|
${drafts[0].tag_name} already exists.`);
|
|
@@ -123,51 +111,69 @@ async function createRelease (branchToTarget: string, isPreRelease: boolean) {
|
|
|
|
|
|
let releaseBody;
|
|
|
let releaseIsPrelease = false;
|
|
|
- if (isPreRelease) {
|
|
|
+ if (options.isPreRelease) {
|
|
|
if (newVersion.indexOf('nightly') > 0) {
|
|
|
- releaseBody = 'Note: This is a nightly release. Please file new issues ' +
|
|
|
+ releaseBody =
|
|
|
+ 'Note: This is a nightly release. Please file new issues ' +
|
|
|
'for any bugs you find in it.\n \n This release is published to npm ' +
|
|
|
'under the electron-nightly package and can be installed via `npm install electron-nightly`, ' +
|
|
|
- `or \`npm install electron-nightly@${newVersion.substr(1)}\`.\n \n ${releaseNotes.text}`;
|
|
|
+ `or \`npm install electron-nightly@${newVersion.substr(1)}\`.\n \n ${
|
|
|
+ releaseNotes.text
|
|
|
+ }`;
|
|
|
} else if (newVersion.indexOf('alpha') > 0) {
|
|
|
- releaseBody = 'Note: This is an alpha release. Please file new issues ' +
|
|
|
+ releaseBody =
|
|
|
+ 'Note: This is an alpha release. Please file new issues ' +
|
|
|
'for any bugs you find in it.\n \n This release is published to npm ' +
|
|
|
'under the alpha tag and can be installed via `npm install electron@alpha`, ' +
|
|
|
- `or \`npm install electron@${newVersion.substr(1)}\`.\n \n ${releaseNotes.text}`;
|
|
|
+ `or \`npm install electron@${newVersion.substr(1)}\`.\n \n ${
|
|
|
+ releaseNotes.text
|
|
|
+ }`;
|
|
|
} else {
|
|
|
- releaseBody = 'Note: This is a beta release. Please file new issues ' +
|
|
|
+ releaseBody =
|
|
|
+ 'Note: This is a beta release. Please file new issues ' +
|
|
|
'for any bugs you find in it.\n \n This release is published to npm ' +
|
|
|
'under the beta tag and can be installed via `npm install electron@beta`, ' +
|
|
|
- `or \`npm install electron@${newVersion.substr(1)}\`.\n \n ${releaseNotes.text}`;
|
|
|
+ `or \`npm install electron@${newVersion.substr(1)}\`.\n \n ${
|
|
|
+ releaseNotes.text
|
|
|
+ }`;
|
|
|
}
|
|
|
releaseIsPrelease = true;
|
|
|
} else {
|
|
|
releaseBody = releaseNotes.text;
|
|
|
}
|
|
|
|
|
|
- const release = await octokit.repos.createRelease({
|
|
|
- owner: 'electron',
|
|
|
- repo: targetRepo,
|
|
|
- tag_name: newVersion,
|
|
|
- draft: true,
|
|
|
- name: `electron ${newVersion}`,
|
|
|
- body: releaseBody,
|
|
|
- prerelease: releaseIsPrelease,
|
|
|
- target_commitish: newVersion.includes('nightly') ? 'main' : branchToTarget
|
|
|
- }).catch(err => {
|
|
|
- console.log(`${fail} Error creating new release: `, err);
|
|
|
- process.exit(1);
|
|
|
- });
|
|
|
+ const release = await octokit.repos
|
|
|
+ .createRelease({
|
|
|
+ owner: ELECTRON_ORG,
|
|
|
+ repo: options.targetRepo,
|
|
|
+ tag_name: newVersion,
|
|
|
+ draft: true,
|
|
|
+ name: `electron ${newVersion}`,
|
|
|
+ body: releaseBody,
|
|
|
+ prerelease: releaseIsPrelease,
|
|
|
+ target_commitish: newVersion.includes('nightly')
|
|
|
+ ? 'main'
|
|
|
+ : branchToTarget
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.log(`${fail} Error creating new release: `, err);
|
|
|
+ process.exit(1);
|
|
|
+ });
|
|
|
|
|
|
console.log(`Release has been created with id: ${release.data.id}.`);
|
|
|
console.log(`${pass} Draft release for ${newVersion} successful.`);
|
|
|
}
|
|
|
|
|
|
async function pushRelease (branch: string) {
|
|
|
- const pushDetails = await GitProcess.exec(['push', 'origin', `HEAD:${branch}`, '--follow-tags'], ELECTRON_DIR);
|
|
|
+ const pushDetails = await GitProcess.exec(
|
|
|
+ ['push', 'origin', `HEAD:${branch}`, '--follow-tags'],
|
|
|
+ ELECTRON_DIR
|
|
|
+ );
|
|
|
if (pushDetails.exitCode === 0) {
|
|
|
- console.log(`${pass} Successfully pushed the release. Wait for ` +
|
|
|
- 'release builds to finish before running "npm run release".');
|
|
|
+ console.log(
|
|
|
+ `${pass} Successfully pushed the release. Wait for ` +
|
|
|
+ 'release builds to finish before running "npm run release".'
|
|
|
+ );
|
|
|
} else {
|
|
|
console.log(`${fail} Error pushing the release: ${pushDetails.stderr}`);
|
|
|
process.exit(1);
|
|
@@ -175,7 +181,7 @@ async function pushRelease (branch: string) {
|
|
|
}
|
|
|
|
|
|
async function runReleaseBuilds (branch: string, newVersion: string) {
|
|
|
- await ciReleaseBuild(branch, {
|
|
|
+ await runReleaseCIJobs(branch, {
|
|
|
ci: undefined,
|
|
|
ghRelease: true,
|
|
|
newVersion
|
|
@@ -184,81 +190,51 @@ async function runReleaseBuilds (branch: string, newVersion: string) {
|
|
|
|
|
|
async function tagRelease (version: string) {
|
|
|
console.log(`Tagging release ${version}.`);
|
|
|
- const checkoutDetails = await GitProcess.exec(['tag', '-a', '-m', version, version], ELECTRON_DIR);
|
|
|
+ const checkoutDetails = await GitProcess.exec(
|
|
|
+ ['tag', '-a', '-m', version, version],
|
|
|
+ ELECTRON_DIR
|
|
|
+ );
|
|
|
if (checkoutDetails.exitCode === 0) {
|
|
|
console.log(`${pass} Successfully tagged ${version}.`);
|
|
|
} else {
|
|
|
- console.log(`${fail} Error tagging ${version}: ` +
|
|
|
- `${checkoutDetails.stderr}`);
|
|
|
+ console.log(
|
|
|
+ `${fail} Error tagging ${version}: ` + `${checkoutDetails.stderr}`
|
|
|
+ );
|
|
|
process.exit(1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function verifyNewVersion () {
|
|
|
- const newVersion = await getNewVersion(DryRunMode.DRY_RUN);
|
|
|
- let response;
|
|
|
- if (automaticRelease) {
|
|
|
- response = 'y';
|
|
|
- } else {
|
|
|
- response = await promptForVersion(newVersion);
|
|
|
- }
|
|
|
- if (response.match(/^y/i)) {
|
|
|
- console.log(`${pass} Starting release of ${newVersion}`);
|
|
|
- } else {
|
|
|
- console.log(`${fail} Aborting release of ${newVersion}`);
|
|
|
- process.exit();
|
|
|
- }
|
|
|
-
|
|
|
- return newVersion;
|
|
|
-}
|
|
|
-
|
|
|
-async function promptForVersion (version: string) {
|
|
|
- return new Promise<string>(resolve => {
|
|
|
- const rl = createInterface({
|
|
|
- input: process.stdin,
|
|
|
- output: process.stdout
|
|
|
- });
|
|
|
- rl.question(`Do you want to create the release ${chalk.green(version)} (y/N)? `, (answer) => {
|
|
|
- rl.close();
|
|
|
- resolve(answer);
|
|
|
- });
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
// function to determine if there have been commits to main since the last release
|
|
|
async function changesToRelease () {
|
|
|
- const lastCommitWasRelease = /^Bump v[0-9]+.[0-9]+.[0-9]+(-beta.[0-9]+)?(-alpha.[0-9]+)?(-nightly.[0-9]+)?$/g;
|
|
|
- const lastCommit = await GitProcess.exec(['log', '-n', '1', '--pretty=format:\'%s\''], ELECTRON_DIR);
|
|
|
+ const lastCommitWasRelease =
|
|
|
+ /^Bump v[0-9]+.[0-9]+.[0-9]+(-beta.[0-9]+)?(-alpha.[0-9]+)?(-nightly.[0-9]+)?$/g;
|
|
|
+ const lastCommit = await GitProcess.exec(
|
|
|
+ ['log', '-n', '1', "--pretty=format:'%s'"],
|
|
|
+ ELECTRON_DIR
|
|
|
+ );
|
|
|
return !lastCommitWasRelease.test(lastCommit.stdout);
|
|
|
}
|
|
|
|
|
|
-async function prepareRelease (isPreRelease: boolean, dryRunMode: DryRunMode) {
|
|
|
- if (dryRunMode === DryRunMode.DRY_RUN) {
|
|
|
- const newVersion = await getNewVersion(DryRunMode.DRY_RUN);
|
|
|
- console.log(newVersion);
|
|
|
- } else {
|
|
|
- const currentBranch = branchArg || await getCurrentBranch(ELECTRON_DIR);
|
|
|
- if (notesOnly) {
|
|
|
- const newVersion = await getNewVersion(DryRunMode.DRY_RUN);
|
|
|
- const releaseNotes = await getReleaseNotes(currentBranch, newVersion);
|
|
|
- console.log(`Draft release notes are: \n${releaseNotes.text}`);
|
|
|
- } else {
|
|
|
- const changes = await changesToRelease();
|
|
|
- if (changes) {
|
|
|
- const newVersion = await verifyNewVersion();
|
|
|
- await createRelease(currentBranch, isPreRelease);
|
|
|
- await pushRelease(currentBranch);
|
|
|
- await runReleaseBuilds(currentBranch, newVersion);
|
|
|
- } else {
|
|
|
- console.log('There are no new changes to this branch since the last release, aborting release.');
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+export async function printNextVersion (options: PrepareReleaseOptions) {
|
|
|
+ const newVersion = await getNewVersion(options, DryRunMode.DRY_RUN);
|
|
|
+ console.log(newVersion);
|
|
|
}
|
|
|
|
|
|
-prepareRelease(!isStableArg, dryRunArg ? DryRunMode.DRY_RUN : DryRunMode.REAL_RUN)
|
|
|
- .catch((err) => {
|
|
|
- console.error(err);
|
|
|
+export async function prepareRelease (options: PrepareReleaseOptions) {
|
|
|
+ const currentBranch =
|
|
|
+ options.targetBranch || (await getCurrentBranch(ELECTRON_DIR));
|
|
|
+
|
|
|
+ const changes = await changesToRelease();
|
|
|
+ if (changes) {
|
|
|
+ const newVersion = await getNewVersion(options, DryRunMode.DRY_RUN);
|
|
|
+ console.log(`${pass} Starting release of ${newVersion}`);
|
|
|
+ await createRelease(options, currentBranch);
|
|
|
+ await pushRelease(currentBranch);
|
|
|
+ await runReleaseBuilds(currentBranch, newVersion);
|
|
|
+ } else {
|
|
|
+ console.log(
|
|
|
+ 'There are no new changes to this branch since the last release, aborting release.'
|
|
|
+ );
|
|
|
process.exit(1);
|
|
|
- });
|
|
|
+ }
|
|
|
+}
|