1313 */
1414
1515// tslint:disable:no-console
16- import { input } from '@inquirer/prompts' ;
16+ import { input , select } from '@inquirer/prompts' ;
1717import chalk from 'chalk' ;
1818import semver from 'semver' ;
1919import { 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 */
391408async 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.
595664main ( ) . catch ( ( err ) => {
596665 console . error ( chalk . red ( err ) ) ;
0 commit comments