build: convert all release scripts to typescript (#44059)
* build: convert all release scripts to typescript (#44035) * build: convert all release scripts to typescript * fix test imports * build: fix version bumper export * refactor: use as const * spec: fix bad type spec * build: use ts-node to spawn the version-bumper (#44057) Missed this in the tsification, we should probably call this via API instead of spawning a sub-proc? * build: still colors
This commit is contained in:
parent
52a8d133cd
commit
fc9cda72d2
22 changed files with 1164 additions and 772 deletions
264
script/release/prepare-release.ts
Executable file
264
script/release/prepare-release.ts
Executable file
|
@ -0,0 +1,264 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { Octokit } from '@octokit/rest';
|
||||
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 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())
|
||||
});
|
||||
|
||||
require('colors');
|
||||
const pass = '✓'.green;
|
||||
const fail = '✗'.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) {
|
||||
if (dryRunMode === DryRunMode.REAL_RUN) {
|
||||
console.log(`Bumping for new "${bumpType}" version.`);
|
||||
}
|
||||
const bumpScript = join(__dirname, 'version-bumper.ts');
|
||||
const scriptArgs = [
|
||||
'node',
|
||||
'node_modules/.bin/ts-node',
|
||||
bumpScript,
|
||||
`--bump=${bumpType}`
|
||||
];
|
||||
if (dryRunMode === DryRunMode.DRY_RUN) scriptArgs.push('--dryRun');
|
||||
try {
|
||||
let bumpVersion = execSync(scriptArgs.join(' '), { encoding: 'utf-8' });
|
||||
bumpVersion = bumpVersion.substr(bumpVersion.indexOf(':') + 1).trim();
|
||||
const newVersion = `v${bumpVersion}`;
|
||||
if (dryRunMode === DryRunMode.REAL_RUN) {
|
||||
console.log(`${pass} Successfully bumped version to ${newVersion}`);
|
||||
}
|
||||
return newVersion;
|
||||
} catch (err) {
|
||||
console.log(`${fail} Could not bump version, error was:`, err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function getReleaseNotes (currentBranch: string, newVersion: string) {
|
||||
if (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);
|
||||
if (releaseNotes.warning) {
|
||||
console.warn(releaseNotes.warning);
|
||||
}
|
||||
return releaseNotes;
|
||||
}
|
||||
|
||||
async function createRelease (branchToTarget: string, isPreRelease: boolean) {
|
||||
const newVersion = await getNewVersion(DryRunMode.REAL_RUN);
|
||||
const releaseNotes = await getReleaseNotes(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 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.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`${pass} A draft release does not exist; creating one.`);
|
||||
|
||||
let releaseBody;
|
||||
let releaseIsPrelease = false;
|
||||
if (isPreRelease) {
|
||||
if (newVersion.indexOf('nightly') > 0) {
|
||||
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}`;
|
||||
} else if (newVersion.indexOf('alpha') > 0) {
|
||||
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}`;
|
||||
} else {
|
||||
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}`;
|
||||
}
|
||||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
if (pushDetails.exitCode === 0) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
async function runReleaseBuilds (branch: string, newVersion: string) {
|
||||
await ciReleaseBuild(branch, {
|
||||
ci: undefined,
|
||||
ghRelease: true,
|
||||
newVersion
|
||||
});
|
||||
}
|
||||
|
||||
async function tagRelease (version: string) {
|
||||
console.log(`Tagging release ${version}.`);
|
||||
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}`);
|
||||
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 ${version.green} (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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareRelease(!isStableArg, dryRunArg ? DryRunMode.DRY_RUN : DryRunMode.REAL_RUN)
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue