@@ -11,68 +11,78 @@ import { allNugetPackages, NugetPackageInfo, platformSpecificPackages } from './
1111import { PlatformInformation } from '../src/shared/platform' ;
1212import path from 'path' ;
1313
14- interface Options {
14+ interface CreateTagsOptions {
1515 releaseVersion : string ;
1616 releaseCommit : string ;
1717 // Even it is specified as boolean, it would still be parsed as string in compiled js.
1818 dryRun : string ;
1919 githubPAT : string | null ;
20+ prerelease : string | null ;
2021}
2122
2223gulp . task ( 'createTags:roslyn' , async ( ) : Promise < void > => {
23- const options = minimist < Options > ( process . argv . slice ( 2 ) ) ;
24+ const options = minimist < CreateTagsOptions > ( process . argv . slice ( 2 ) ) ;
2425
2526 return createTagsAsync (
2627 options ,
2728 'dotnet' ,
2829 'roslyn' ,
2930 async ( ) => getCommitFromNugetAsync ( allNugetPackages . roslyn ) ,
30- ( releaseVersion : string ) : [ string , string ] => [
31- `VSCode-CSharp-${ releaseVersion } ` ,
32- `${ releaseVersion } VSCode C# extension release` ,
33- ]
31+ ( releaseVersion : string , isPrerelease : boolean ) : [ string , string ] => {
32+ const prereleaseText = isPrerelease ? '-prerelease' : '' ;
33+ return [
34+ `VSCode-CSharp-${ releaseVersion } ${ prereleaseText } ` ,
35+ `${ releaseVersion } VSCode C# extension ${ prereleaseText } ` ,
36+ ] ;
37+ }
3438 ) ;
3539} ) ;
3640
3741gulp . task ( 'createTags:razor' , async ( ) : Promise < void > => {
38- const options = minimist < Options > ( process . argv . slice ( 2 ) ) ;
42+ const options = minimist < CreateTagsOptions > ( process . argv . slice ( 2 ) ) ;
3943
4044 return createTagsAsync (
4145 options ,
4246 'dotnet' ,
4347 'razor' ,
4448 async ( ) => getCommitFromNugetAsync ( allNugetPackages . razor ) ,
45- ( releaseVersion : string ) : [ string , string ] => [
46- `VSCode-CSharp-${ releaseVersion } ` ,
47- `${ releaseVersion } VSCode C# extension release` ,
48- ]
49+ ( releaseVersion : string , isPrerelease : boolean ) : [ string , string ] => {
50+ const prereleaseText = isPrerelease ? '-prerelease' : '' ;
51+ return [
52+ `VSCode-CSharp-${ releaseVersion } ${ prereleaseText } ` ,
53+ `${ releaseVersion } VSCode C# extension ${ prereleaseText } ` ,
54+ ] ;
55+ }
4956 ) ;
5057} ) ;
5158
5259gulp . task ( 'createTags:vscode-csharp' , async ( ) : Promise < void > => {
53- const options = minimist < Options > ( process . argv . slice ( 2 ) ) ;
60+ const options = minimist < CreateTagsOptions > ( process . argv . slice ( 2 ) ) ;
5461
5562 return createTagsAsync (
5663 options ,
5764 'dotnet' ,
5865 'vscode-csharp' ,
5966 async ( ) => options . releaseCommit ,
60- ( releaseVersion : string ) : [ string , string ] => [ `v${ releaseVersion } ` , releaseVersion ]
67+ ( releaseVersion : string , isPrerelease : boolean ) : [ string , string ] => {
68+ const prereleaseText = isPrerelease ? '-prerelease' : '' ;
69+ return [ `v${ releaseVersion } ${ prereleaseText } ` , releaseVersion ] ;
70+ }
6171 ) ;
6272} ) ;
6373
6474gulp . task ( 'createTags' , gulp . series ( 'createTags:roslyn' , 'createTags:razor' , 'createTags:vscode-csharp' ) ) ;
6575
6676async function createTagsAsync (
67- options : Options ,
77+ options : CreateTagsOptions ,
6878 owner : string ,
6979 repo : string ,
7080 getCommit : ( ) => Promise < string | null > ,
71- getTagAndMessage : ( releaseVersion : string ) => [ string , string ]
81+ getTagAndMessage : ( releaseVersion : string , isPrerelease : boolean ) => [ string , string ]
7282) : Promise < void > {
7383 console . log ( `releaseVersion: ${ options . releaseVersion } ` ) ;
7484 console . log ( `releaseCommit: ${ options . releaseCommit } ` ) ;
75- const dryRun = options . dryRun ? options . dryRun . toLocaleLowerCase ( ) === 'true' : false ;
85+ const dryRun = getFlag ( 'dryRun' , options ) ;
7686 console . log ( `dry run: ${ dryRun } ` ) ;
7787
7888 const commit = await getCommit ( ) ;
@@ -81,7 +91,8 @@ async function createTagsAsync(
8191 return ;
8292 }
8393
84- const [ tag , message ] = getTagAndMessage ( options . releaseVersion ) ;
94+ const prerelease = getFlag ( 'prerelease' , options ) ;
95+ const [ tag , message ] = getTagAndMessage ( options . releaseVersion , prerelease ) ;
8596 console . log ( `tag: ${ tag } ` ) ;
8697 console . log ( `message: ${ message } ` ) ;
8798
@@ -90,7 +101,8 @@ async function createTagsAsync(
90101 console . log ( 'Tagging is skipped in dry run mode.' ) ;
91102 return ;
92103 } else {
93- const tagCreated = await tagRepoAsync ( owner , repo , commit , tag , message , options . githubPAT ) ;
104+ const githubPAT = getGitHubPAT ( options ) ;
105+ const tagCreated = await tagRepoAsync ( owner , repo , commit , tag , message , githubPAT ) ;
94106
95107 if ( ! tagCreated ) {
96108 logError ( `Failed to tag '${ owner } /${ repo } '` ) ;
@@ -107,15 +119,10 @@ async function tagRepoAsync(
107119 commit : string ,
108120 releaseTag : string ,
109121 tagMessage : string ,
110- githubPAT : string | null
122+ githubPAT : string
111123) : Promise < boolean > {
112- const pat = githubPAT ?? process . env [ 'GitHubPAT' ] ;
113- if ( ! pat ) {
114- throw 'No GitHub Pat found. Specify with --githubPAT or set GitHubPAT environment variable.' ;
115- }
116-
117124 console . log ( `Start to tag ${ owner } /${ repo } . Commit: ${ commit } , tag: ${ releaseTag } , message: ${ tagMessage } ` ) ;
118- const octokit = new Octokit ( { auth : pat } ) ;
125+ const octokit = new Octokit ( { auth : githubPAT } ) ;
119126 await octokit . auth ( ) ;
120127 const createTagResponse = await octokit . request ( `POST /repos/${ owner } /${ repo } /git/tags` , {
121128 owner : owner ,
@@ -130,25 +137,62 @@ async function tagRepoAsync(
130137 logError ( `Failed to create tag for ${ commit } in ${ owner } /${ repo } .` ) ;
131138 return false ;
132139 }
133- const refCreationResponse = await octokit . request ( `Post /repos/${ owner } /${ repo } /git/refs` , {
134- owner : owner ,
135- repo : repo ,
136- ref : `refs/tags/${ releaseTag } ` ,
137- sha : commit ,
138- } ) ;
139-
140- if ( refCreationResponse . status !== 201 ) {
141- logError ( `Failed to create reference for ${ commit } in ${ owner } /${ repo } .` ) ;
142- return false ;
140+ try {
141+ const refCreationResponse = await octokit . request ( `Post /repos/${ owner } /${ repo } /git/refs` , {
142+ owner : owner ,
143+ repo : repo ,
144+ ref : `refs/tags/${ releaseTag } ` ,
145+ sha : commit ,
146+ } ) ;
147+
148+ if ( refCreationResponse . status !== 201 ) {
149+ logError ( `Failed to create reference for ${ commit } in ${ owner } /${ repo } .` ) ;
150+ return false ;
151+ }
152+ } catch ( err : any ) {
153+ if ( err . status === 422 && err . message && err . message . includes ( 'Reference already exists' ) ) {
154+ logWarning ( `Reference for tag '${ releaseTag } ' already exists in ${ owner } /${ repo } .` ) ;
155+ return true ;
156+ } else {
157+ logError ( `Failed to create reference for ${ commit } in ${ owner } /${ repo } : ${ err } ` ) ;
158+ return false ;
159+ }
143160 }
144161
145162 console . log ( `Tag is created.` ) ;
146163 return true ;
147164}
148165
166+ // --- Helper functions ---
167+
168+ function getGitHubPAT ( options : { githubPAT ?: string | null } ) : string {
169+ const pat = options . githubPAT ?? process . env [ 'GitHubPAT' ] ;
170+ if ( ! pat ) {
171+ throw 'No GitHub Pat found. Specify with --githubPAT or set GitHubPAT environment variable.' ;
172+ }
173+ return pat ;
174+ }
175+
176+ function getFlag < T extends CreateTagsOptions > ( option : keyof T , options : T ) : boolean {
177+ const value = options [ option ] ;
178+ if ( ! value ) {
179+ logError ( `Missing required argument: --${ option . toString ( ) } ` ) ;
180+ }
181+ if ( typeof value === 'string' ) {
182+ return value . toLocaleLowerCase ( ) === 'true' ;
183+ } else {
184+ throw new Error ( `Expected boolean value for --${ option . toString ( ) } , but got ${ typeof value } ` ) ;
185+ }
186+ }
187+
188+ function logWarning ( message : string ) : void {
189+ console . log ( `##vso[task.logissue type=warning]${ message } ` ) ;
190+ }
191+
149192function logError ( message : string ) : void {
150193 console . log ( `##vso[task.logissue type=error]${ message } ` ) ;
151194}
195+
152196async function getCommitFromNugetAsync ( packageInfo : NugetPackageInfo ) : Promise < string | null > {
153197 const packageJsonString = fs . readFileSync ( './package.json' ) . toString ( ) ;
154198 const packageJson = JSON . parse ( packageJsonString ) ;
0 commit comments