Skip to content

Commit 227acdd

Browse files
atscottthePunderWoman
authored andcommitted
build: Update vscode release script
- Ensure there is a GITHUB_TOKEN environment variable at the start so we can push the release - More robust handling for finding releaser's fork if it's not 'origin'
1 parent cb5879f commit 227acdd

1 file changed

Lines changed: 82 additions & 13 deletions

File tree

vscode-ng-language-service/tools/release.mts

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
*/
1414

1515
// tslint:disable:no-console
16-
import {input} from '@inquirer/prompts';
16+
import {input, select} from '@inquirer/prompts';
1717
import chalk from 'chalk';
1818
import semver from 'semver';
1919
import {writeFile, readFile} from 'node:fs/promises';
@@ -68,6 +68,8 @@ async function main(): Promise<void> {
6868

6969
// Ensure the user has a clean working directory before starting the release process.
7070
await checkCleanWorkingDirectory();
71+
// Ensure there is a github token before starting the release process.
72+
ensureGithubToken();
7173

7274
let branchToReleaseFrom: string | undefined = process.env['BRANCH_TO_RELEASE'];
7375
if (!branchToReleaseFrom) {
@@ -91,18 +93,21 @@ async function main(): Promise<void> {
9193
await installDependencies();
9294
await buildExtension();
9395

94-
await prepareReleasePullRequest(releaseBranch, `${releaseCommitPrefix}${newVersion}`, [
95-
packageJsonPath,
96-
changelogPath,
97-
]);
96+
const forkRemote = await getForkRemoteName();
97+
await prepareReleasePullRequest(
98+
releaseBranch,
99+
`${releaseCommitPrefix}${newVersion}`,
100+
[packageJsonPath, changelogPath],
101+
forkRemote,
102+
);
98103
await waitForPRToBeMergedAndTag(newVersion, branchToReleaseFrom);
99104

100105
await publishExtension();
101106

102107
await createGithubRelease(newVersion, changelog);
103108

104109
if (branchToReleaseFrom !== 'main') {
105-
await cherryPickChangelog(changelog, newVersion);
110+
await cherryPickChangelog(changelog, newVersion, forkRemote);
106111
}
107112

108113
console.log(chalk.green('VSCode extension release process complete!'));
@@ -216,10 +221,11 @@ async function prepareReleasePullRequest(
216221
branch: string,
217222
commitMessage: string,
218223
files: string[],
224+
forkRemote: string,
219225
): Promise<void> {
220226
await exec(`git commit -m "${commitMessage}" "${files.join('" "')}"`);
221-
await exec(`git push origin ${branch} --force-with-lease`);
222-
const {stdout: remoteUrl} = await exec('git remote get-url origin');
227+
await exec(`git push ${forkRemote} ${branch} --force-with-lease`);
228+
const {stdout: remoteUrl} = await exec(`git remote get-url ${forkRemote}`);
223229
const {owner, repo} = getRepoDetails(remoteUrl);
224230

225231
console.log(
@@ -382,17 +388,25 @@ async function buildExtension(): Promise<void> {
382388
console.log(chalk.green(`VSCode extension packaged at ${extensionPath}`));
383389
}
384390

391+
function ensureGithubToken(): string {
392+
// https://github.com/angular/dev-infra/blob/8ce8257f740613a7291256173e2706fb2ed8aefa/ng-dev/utils/git/github-yargs.ts#L45
393+
const token = process.env['GITHUB_TOKEN'] ?? process.env['TOKEN'];
394+
if (!token) {
395+
throw new Error(
396+
'GITHUB_TOKEN nor TOKEN environment variable is not set. Cannot create GitHub release.',
397+
);
398+
}
399+
return token;
400+
}
401+
385402
/**
386403
* Creates a GitHub release and uploads the extension asset.
387404
*
388405
* @param version The version of the release.
389406
* @param changelog The changelog content for the release.
390407
*/
391408
async function createGithubRelease(version: string, changelog: string): Promise<void> {
392-
const token = process.env['GITHUB_TOKEN'];
393-
if (!token) {
394-
throw new Error('GITHUB_TOKEN environment variable is not set. Cannot create GitHub release.');
395-
}
409+
const token = ensureGithubToken();
396410

397411
console.log(chalk.blue('Creating GitHub release...'));
398412

@@ -478,7 +492,11 @@ async function publishExtension(): Promise<void> {
478492
* @param changelog The changelog content to add.
479493
* @param newVersion The new version number.
480494
*/
481-
async function cherryPickChangelog(changelog: string, newVersion: string): Promise<void> {
495+
async function cherryPickChangelog(
496+
changelog: string,
497+
newVersion: string,
498+
forkRemote: string,
499+
): Promise<void> {
482500
console.log(chalk.blue('Cherry-picking changelog to main...'));
483501

484502
await exec(`git stash`);
@@ -497,6 +515,7 @@ async function cherryPickChangelog(changelog: string, newVersion: string): Promi
497515
cherryPickBranch,
498516
`docs: release notes for the vscode extension ${newVersion} release`,
499517
[changelogPath],
518+
forkRemote,
500519
);
501520
}
502521

@@ -591,6 +610,56 @@ function getTagName(version: string): string {
591610
return `${tagPrefix}${version}`;
592611
}
593612

613+
/**
614+
* Gets the name of the remote to use as the user's fork.
615+
*
616+
* This function lists all configured remotes and attempts to identify the user's fork.
617+
* - If 'origin' exists and is not the upstream angular repo, it is used.
618+
* - If there are other candidates (remotes that are not the upstream angular repo),
619+
* it asks the user to select one if there are multiple.
620+
*
621+
* @returns The name of the remote to use.
622+
*/
623+
async function getForkRemoteName(): Promise<string> {
624+
const {stdout} = await exec('git remote -v');
625+
const remotes = new Map<string, string>();
626+
for (const line of stdout.split('\n')) {
627+
const parts = line.split(/\s+/);
628+
if (parts.length >= 2) {
629+
const [name, url] = parts;
630+
remotes.set(name, url);
631+
}
632+
}
633+
634+
const candidates: string[] = [];
635+
for (const [name, url] of remotes) {
636+
if (getRepoDetails(url).owner !== 'angular') {
637+
candidates.push(name);
638+
}
639+
}
640+
641+
// If origin is a candidate, we prefer it appropriately IF it's likely the user's fork.
642+
// The check `getRepoDetails(url).owner !== 'angular'` already filters out upstream.
643+
// So if `origin` is in candidates, it's safe to use?
644+
// User wanted: "If origin exists and is a candidate ... return 'origin'".
645+
if (candidates.includes('origin')) {
646+
return 'origin';
647+
}
648+
649+
if (candidates.length === 0) {
650+
throw new Error('No suitable fork remote found. Please add a remote for your fork.');
651+
}
652+
653+
if (candidates.length === 1) {
654+
return candidates[0];
655+
}
656+
657+
return await select({
658+
message: 'Which remote should be used as your fork (to push the release commit to for the PR)?',
659+
choices: candidates.map((c) => ({value: c})),
660+
});
661+
}
662+
594663
// Start the release process.
595664
main().catch((err) => {
596665
console.error(chalk.red(err));

0 commit comments

Comments
 (0)