build: cleanup release scripts, separate cli entrypoints from logic (#44082)

* build: cleanup release scripts, separate cli entrypoints from logic

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>

* build: use repo/org constants

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
This commit is contained in:
trop[bot] 2024-10-01 14:08:15 -07:00 committed by GitHub
parent 7b14a305f8
commit 6074f7ed31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 410 additions and 300 deletions

View file

@ -0,0 +1,61 @@
# Release Scripts
> These ancient artifacts date back to the early days of Electron, they have been modified
> over the years but in reality still very much look how they did at the beginning. You
> have been warned.
None of these scripts are called manually, they are each called by Sudowoodo at various points
in the Electron release process. What each script does though is loosely documented below,
however this documentation is a best effort so please be careful when modifying the scripts
as there still may be unknown or undocumented effects / intentions.
## What scripts do we have?
### `cleanup-release`
This script completely reverts a failed or otherwise unreleasable version. It does this by:
* Deleting the draft release if it exists
* Deleting the git tag if it exists
> [!NOTE]
> This is the only script / case where an existing tag will be deleted. Tags are only considered immutable after the release is published.
### `print-next-version`
This script just outputs the theoretical "next" version that a release would use.
### `prepare-for-release`
This script creates all the requisite tags and CI builds that will populate required release assets.
* Creates the git tag
* Kicks off all release builds on AppVeyor and GitHub Actions
### `run-release-build`
This script is used to re-kick specific release builds after they fail. Sudowoodo is responsible for prompting the release team as to whether or not to run this script. It's currently only used for AppVeyor builds.
> [!IMPORTANT]
> This script should be removed and the "rerun" logic for AppVeyor be implemented in Sudowoodo specifically in the same way that GitHub Actions' rerun logic is.
### `validate-before-publish`
This script ensures that a release is in a valid state before publishing it anywhere. Specifically it checks:
* All assets exist
* All checksums match uploaded assets
* Headers have been uploaded to the header CDN
* Symbols have been uploaded to the symbol CDN
### `publish-to-github`
This script finalizes the GitHub release, in the process it:
* Uploads the header SHASUMs to the CDN
* Updates the `index.json` file on the assets CDN with the new version via metadumper
* Publishes the actual GitHub release
### `publish-to-npm`
This script finishes the release process by publishing a new `npm` package.

View file

@ -0,0 +1,30 @@
import { parseArgs } from 'node:util';
import { cleanReleaseArtifacts } from '../release-artifact-cleanup';
const { values: { tag: _tag, releaseID } } = parseArgs({
options: {
tag: {
type: 'string'
},
releaseID: {
type: 'string',
default: ''
}
}
});
if (!_tag) {
console.error('Missing --tag argument');
process.exit(1);
}
const tag = _tag;
cleanReleaseArtifacts({
releaseID,
tag
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View file

@ -0,0 +1,32 @@
import { parseArgs } from 'node:util';
import { prepareRelease } from '../prepare-release';
import { ELECTRON_REPO, isVersionBumpType, NIGHTLY_REPO } from '../types';
const { values: { branch }, positionals } = parseArgs({
options: {
branch: {
type: 'string'
}
},
allowPositionals: true
});
const bumpType = positionals[0];
if (!bumpType || !isVersionBumpType(bumpType)) {
console.log('Usage: prepare-for-release [stable | minor | beta | alpha | nightly]' +
' (--branch=branch)');
process.exit(1);
}
prepareRelease({
isPreRelease: bumpType !== 'stable' && bumpType !== 'minor',
targetRepo: bumpType === 'nightly' ? NIGHTLY_REPO : ELECTRON_REPO,
targetBranch: branch,
bumpType
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View file

@ -0,0 +1,32 @@
import { parseArgs } from 'node:util';
import { printNextVersion } from '../prepare-release';
import { ELECTRON_REPO, isVersionBumpType, NIGHTLY_REPO } from '../types';
const { values: { branch }, positionals } = parseArgs({
options: {
branch: {
type: 'string'
}
},
allowPositionals: true
});
const bumpType = positionals[0];
if (!bumpType || !isVersionBumpType(bumpType)) {
console.log('Usage: print-next-version [stable | minor | beta | alpha | nightly]' +
' (--branch=branch)');
process.exit(1);
}
printNextVersion({
isPreRelease: bumpType !== 'stable' && bumpType !== 'minor',
targetRepo: bumpType === 'nightly' ? NIGHTLY_REPO : ELECTRON_REPO,
targetBranch: branch,
bumpType
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View file

@ -0,0 +1,6 @@
import { makeRelease } from '../release';
makeRelease().catch((err) => {
console.error('Error occurred while making release:', err);
process.exit(1);
});

View file

@ -5,12 +5,12 @@ import * as path from 'node:path';
import * as semver from 'semver';
import * as temp from 'temp';
import { getCurrentBranch, ELECTRON_DIR } from '../lib/utils';
import { getElectronVersion } from '../lib/get-version';
import { getCurrentBranch, ELECTRON_DIR } from '../../lib/utils';
import { getElectronVersion } from '../../lib/get-version';
import { getAssetContents } from './get-asset';
import { createGitHubTokenStrategy } from './github-token';
import { ELECTRON_ORG, ELECTRON_REPO, ElectronReleaseRepo, NIGHTLY_REPO } from './types';
import { getAssetContents } from '../get-asset';
import { createGitHubTokenStrategy } from '../github-token';
import { ELECTRON_ORG, ELECTRON_REPO, ElectronReleaseRepo, NIGHTLY_REPO } from '../types';
const rootPackageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8'));

View file

@ -0,0 +1,53 @@
import { parseArgs } from 'node:util';
import { runReleaseCIJobs } from '../run-release-ci-jobs';
const { values: { ghRelease, job, arch, ci, commit, newVersion }, positionals } = parseArgs({
options: {
ghRelease: {
type: 'boolean'
},
job: {
type: 'string'
},
arch: {
type: 'string'
},
ci: {
type: 'string'
},
commit: {
type: 'string'
},
newVersion: {
type: 'string'
}
},
allowPositionals: true
});
const targetBranch = positionals[0];
if (positionals.length < 1) {
console.log(`Trigger CI to build release builds of electron.
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--arch=INDIVIDUAL_ARCH] [--ci=AppVeyor|GitHubActions]
[--ghRelease] [--commit=sha] [--newVersion=version_tag] TARGET_BRANCH
`);
process.exit(0);
}
if (ci === 'GitHubActions' || !ci) {
if (!newVersion) {
console.error('--newVersion is required for GitHubActions');
process.exit(1);
}
}
runReleaseCIJobs(targetBranch, {
ci: ci as 'GitHubActions' | 'AppVeyor',
ghRelease,
job: job as any,
arch,
newVersion: newVersion!,
commit
});

View file

@ -0,0 +1,6 @@
import { validateRelease } from '../release';
validateRelease().catch((err) => {
console.error('Error occurred while validating release:', err);
process.exit(1);
});

View file

@ -1,7 +1,7 @@
import { Octokit } from '@octokit/rest';
import got from 'got';
import { createGitHubTokenStrategy } from './github-token';
import { ElectronReleaseRepo } from './types';
import { ELECTRON_ORG, ElectronReleaseRepo } from './types';
export async function getAssetContents (repo: ElectronReleaseRepo, assetId: number) {
const octokit = new Octokit({
@ -10,7 +10,7 @@ export async function getAssetContents (repo: ElectronReleaseRepo, assetId: numb
});
const requestOptions = octokit.repos.getReleaseAsset.endpoint({
owner: 'electron',
owner: ELECTRON_ORG,
repo,
asset_id: assetId,
headers: {

View file

@ -10,8 +10,10 @@ import { get, render } from './notes';
import { Octokit } from '@octokit/rest';
import { createGitHubTokenStrategy } from '../github-token';
import { parseArgs } from 'node:util';
import { ELECTRON_ORG, ELECTRON_REPO } from '../types';
const octokit = new Octokit({
authStrategy: createGitHubTokenStrategy('electron')
authStrategy: createGitHubTokenStrategy(ELECTRON_REPO)
});
const semverify = (version: string) => version.replace(/^origin\//, '').replace(/[xy]/g, '0').replace(/-/g, '.');
@ -45,8 +47,8 @@ const getTagsOf = async (point: string) => {
const getTagsOnBranch = async (point: string) => {
const { data: { default_branch: defaultBranch } } = await octokit.repos.get({
owner: 'electron',
repo: 'electron'
owner: ELECTRON_ORG,
repo: ELECTRON_REPO
});
const mainTags = await getTagsOf(defaultBranch);
if (point === defaultBranch) {

View file

@ -8,9 +8,10 @@ import { GitProcess } from 'dugite';
import { ELECTRON_DIR } from '../../lib/utils';
import { createGitHubTokenStrategy } from '../github-token';
import { ELECTRON_ORG, ELECTRON_REPO } from '../types';
const octokit = new Octokit({
authStrategy: createGitHubTokenStrategy('electron')
authStrategy: createGitHubTokenStrategy(ELECTRON_REPO)
});
const MAX_FAIL_COUNT = 3;
@ -520,7 +521,7 @@ const getNotes = async (fromRef: string, toRef: string, newVersion: string) => {
);
// get the electron/electron commits
const electron = { owner: 'electron', repo: 'electron', dir: ELECTRON_DIR };
const electron = { owner: ELECTRON_ORG, repo: ELECTRON_REPO, dir: ELECTRON_DIR };
await addRepoToPool(pool, electron, fromRef, toRef);
// remove any old commits

View file

@ -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);
});
}
}

View file

@ -2,30 +2,10 @@
import { Octokit } from '@octokit/rest';
import * as chalk from 'chalk';
import { parseArgs } from 'node:util';
import { createGitHubTokenStrategy } from './github-token';
import { ELECTRON_ORG, ELECTRON_REPO, ElectronReleaseRepo, NIGHTLY_REPO } from './types';
const { values: { tag: _tag, releaseID } } = parseArgs({
options: {
tag: {
type: 'string'
},
releaseID: {
type: 'string',
default: ''
}
}
});
if (!_tag) {
console.error('Missing --tag argument');
process.exit(1);
}
const tag = _tag;
const pass = chalk.green('✓');
const fail = chalk.red('✗');
@ -75,7 +55,12 @@ async function deleteTag (tag: string, targetRepo: ElectronReleaseRepo) {
}
}
async function cleanReleaseArtifacts () {
type CleanOptions = {
releaseID?: string;
tag: string;
}
export async function cleanReleaseArtifacts ({ releaseID, tag }: CleanOptions) {
const releaseId = releaseID && releaseID.length > 0 ? releaseID : null;
const isNightly = tag.includes('nightly');
@ -102,9 +87,3 @@ async function cleanReleaseArtifacts () {
console.log(`${pass} failed release artifact cleanup complete`);
}
cleanReleaseArtifacts()
.catch((err) => {
console.error(err);
process.exit(1);
});

View file

@ -14,8 +14,7 @@ import { ELECTRON_DIR } from '../lib/utils';
import { getElectronVersion } from '../lib/get-version';
import { getUrlHash } from './get-url-hash';
import { createGitHubTokenStrategy } from './github-token';
import { ELECTRON_REPO, ElectronReleaseRepo, NIGHTLY_REPO } from './types';
import { parseArgs } from 'node:util';
import { ELECTRON_ORG, ELECTRON_REPO, ElectronReleaseRepo, NIGHTLY_REPO } from './types';
const temp = trackTemp();
@ -40,7 +39,7 @@ async function getDraftRelease (
skipValidation: boolean = false
) {
const releaseInfo = await octokit.repos.listReleases({
owner: 'electron',
owner: ELECTRON_ORG,
repo: targetRepo
});
@ -316,7 +315,7 @@ async function createReleaseShasums (release: MinimalRelease) {
);
await octokit.repos
.deleteReleaseAsset({
owner: 'electron',
owner: ELECTRON_ORG,
repo: targetRepo,
asset_id: existingAssets[0].id
})
@ -383,7 +382,7 @@ async function publishRelease (release: MinimalRelease) {
let makeLatest = false;
if (!release.prerelease) {
const currentLatest = await octokit.repos.getLatestRelease({
owner: 'electron',
owner: ELECTRON_ORG,
repo: targetRepo
});
@ -392,7 +391,7 @@ async function publishRelease (release: MinimalRelease) {
return octokit.repos
.updateRelease({
owner: 'electron',
owner: ELECTRON_ORG,
repo: targetRepo,
release_id: release.id,
tag_name: release.tag_name,
@ -405,36 +404,31 @@ async function publishRelease (release: MinimalRelease) {
});
}
async function makeRelease (releaseToValidate: string | boolean) {
if (releaseToValidate) {
if (releaseToValidate === true) {
releaseToValidate = pkgVersion;
} else {
console.log('Release to validate !=== true');
}
console.log(`Validating release ${releaseToValidate}`);
const release = await getDraftRelease(releaseToValidate);
await validateReleaseAssets(release, true);
} else {
let draftRelease = await getDraftRelease();
uploadNodeShasums();
await createReleaseShasums(draftRelease);
export async function validateRelease () {
console.log(`Validating release ${pkgVersion}`);
const release = await getDraftRelease(pkgVersion);
await validateReleaseAssets(release, true);
}
// Fetch latest version of release before verifying
draftRelease = await getDraftRelease(pkgVersion, true);
await validateReleaseAssets(draftRelease);
// index.json goes live once uploaded so do these uploads as
// late as possible to reduce the chances it contains a release
// which fails to publish. It has to be done before the final
// publish to ensure there aren't published releases not contained
// in index.json, which causes other problems in downstream projects
uploadIndexJson();
await publishRelease(draftRelease);
console.log(
`${pass} SUCCESS!!! Release has been published. Please run ` +
'"npm run publish-to-npm" to publish release to npm.'
);
}
export async function makeRelease () {
let draftRelease = await getDraftRelease();
uploadNodeShasums();
await createReleaseShasums(draftRelease);
// Fetch latest version of release before verifying
draftRelease = await getDraftRelease(pkgVersion, true);
await validateReleaseAssets(draftRelease);
// index.json goes live once uploaded so do these uploads as
// late as possible to reduce the chances it contains a release
// which fails to publish. It has to be done before the final
// publish to ensure there aren't published releases not contained
// in index.json, which causes other problems in downstream projects
uploadIndexJson();
await publishRelease(draftRelease);
console.log(
`${pass} SUCCESS!!! Release has been published. Please run ` +
'"npm run publish-to-npm" to publish release to npm.'
);
}
const SHASUM_256_FILENAME = 'SHASUMS256.txt';
@ -446,7 +440,7 @@ async function verifyDraftGitHubReleaseAssets (release: MinimalRelease) {
const remoteFilesToHash = await Promise.all(
release.assets.map(async (asset) => {
const requestOptions = octokit.repos.getReleaseAsset.endpoint({
owner: 'electron',
owner: ELECTRON_ORG,
repo: targetRepo,
asset_id: asset.id,
headers: {
@ -591,18 +585,3 @@ async function verifyShasumsForRemoteFiles (
);
}
}
const {
values: { validateRelease }
} = parseArgs({
options: {
validateRelease: {
type: 'boolean'
}
}
});
makeRelease(!!validateRelease).catch((err) => {
console.error('Error occurred while making release:', err);
process.exit(1);
});

View file

@ -4,10 +4,9 @@ import * as assert from 'node:assert';
import { createGitHubTokenStrategy } from './github-token';
import { ELECTRON_ORG, ELECTRON_REPO } from './types';
import { parseArgs } from 'node:util';
const octokit = new Octokit({
authStrategy: createGitHubTokenStrategy('electron')
authStrategy: createGitHubTokenStrategy(ELECTRON_REPO)
});
const BUILD_APPVEYOR_URL = 'https://ci.appveyor.com/api/builds';
@ -83,8 +82,8 @@ async function githubActionsCall (targetBranch: string, workflowName: string, op
jobRequestedCount++;
try {
const commits = await octokit.repos.listCommits({
owner: 'electron',
repo: 'electron',
owner: ELECTRON_ORG,
repo: ELECTRON_REPO,
sha: targetBranch,
per_page: 5
});
@ -268,7 +267,7 @@ type RunReleaseOptions = ({
ci: undefined,
} & BuildAppVeyorOptions & BuildGHActionsOptions);
async function runRelease (targetBranch: string, options: RunReleaseOptions) {
export async function runReleaseCIJobs (targetBranch: string, options: RunReleaseOptions) {
if (options.ci) {
switch (options.ci) {
case 'GitHubActions': {
@ -292,54 +291,3 @@ async function runRelease (targetBranch: string, options: RunReleaseOptions) {
}
console.log(`${jobRequestedCount} jobs were requested.`);
}
export default runRelease;
if (require.main === module) {
const { values: { ghRelease, job, arch, ci, commit, newVersion }, positionals } = parseArgs({
options: {
ghRelease: {
type: 'boolean'
},
job: {
type: 'string'
},
arch: {
type: 'string'
},
ci: {
type: 'string'
},
commit: {
type: 'string'
},
newVersion: {
type: 'string'
}
},
allowPositionals: true
});
const targetBranch = positionals[0];
if (positionals.length < 1) {
console.log(`Trigger CI to build release builds of electron.
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--arch=INDIVIDUAL_ARCH] [--ci=AppVeyor|GitHubActions]
[--ghRelease] [--commit=sha] [--newVersion=version_tag] TARGET_BRANCH
`);
process.exit(0);
}
if (ci === 'GitHubActions' || !ci) {
if (!newVersion) {
console.error('--newVersion is required for GitHubActions');
process.exit(1);
}
}
runRelease(targetBranch, {
ci: ci as 'GitHubActions' | 'AppVeyor',
ghRelease,
job: job as any,
arch,
newVersion: newVersion!,
commit
});
}

View file

@ -5,3 +5,7 @@ export const NIGHTLY_REPO = 'nightlies';
export type ElectronReleaseRepo = 'electron' | 'nightlies';
export type VersionBumpType = 'nightly' | 'alpha' | 'beta' | 'minor' | 'stable';
export const isVersionBumpType = (s: string): s is VersionBumpType => {
return ['nightly', 'alpha', 'beta', 'minor', 'stable'].includes(s);
};

View file

@ -1,6 +1,7 @@
import { Octokit } from '@octokit/rest';
import * as fs from 'node:fs';
import { createGitHubTokenStrategy } from '../github-token';
import { ELECTRON_ORG, ELECTRON_REPO, ElectronReleaseRepo, NIGHTLY_REPO } from '../types';
if (process.argv.length < 6) {
console.log('Usage: upload-to-github filePath fileName releaseId');
@ -37,8 +38,8 @@ const getHeaders = (filePath: string, fileName: string) => {
};
};
function getRepo () {
return releaseVersion.indexOf('nightly') > 0 ? 'nightlies' : 'electron';
function getRepo (): ElectronReleaseRepo {
return releaseVersion.indexOf('nightly') > 0 ? NIGHTLY_REPO : ELECTRON_REPO;
}
const targetRepo = getRepo();
@ -59,7 +60,7 @@ function uploadToGitHub () {
headers: getHeaders(filePath, fileName),
data: fileData as any,
name: fileName,
owner: 'electron',
owner: ELECTRON_ORG,
repo: targetRepo,
release_id: releaseId
}).then(() => {
@ -71,7 +72,7 @@ function uploadToGitHub () {
retry++;
octokit.repos.listReleaseAssets({
owner: 'electron',
owner: ELECTRON_ORG,
repo: targetRepo,
release_id: releaseId,
per_page: 100
@ -83,7 +84,7 @@ function uploadToGitHub () {
if (existingAssets.length > 0) {
console.log(`${fileName} already exists; will delete before retrying upload.`);
octokit.repos.deleteReleaseAsset({
owner: 'electron',
owner: ELECTRON_ORG,
repo: targetRepo,
asset_id: existingAssets[0].id
}).catch((deleteErr) => {