@@ -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,10 @@ async function createTagsAsync(
8191 return ;
8292 }
8393
84- const [ tag , message ] = getTagAndMessage ( options . releaseVersion ) ;
94+ const prerelease = getFlag ( 'prerelease' , options ) ;
95+ console . log ( `prerelease: ${ prerelease } ` ) ;
96+
97+ const [ tag , message ] = getTagAndMessage ( options . releaseVersion , prerelease ) ;
8598 console . log ( `tag: ${ tag } ` ) ;
8699 console . log ( `message: ${ message } ` ) ;
87100
@@ -90,7 +103,8 @@ async function createTagsAsync(
90103 console . log ( 'Tagging is skipped in dry run mode.' ) ;
91104 return ;
92105 } else {
93- const tagCreated = await tagRepoAsync ( owner , repo , commit , tag , message , options . githubPAT ) ;
106+ const githubPAT = getGitHubPAT ( options ) ;
107+ const tagCreated = await tagRepoAsync ( owner , repo , commit , tag , message , githubPAT ) ;
94108
95109 if ( ! tagCreated ) {
96110 logError ( `Failed to tag '${ owner } /${ repo } '` ) ;
@@ -107,15 +121,10 @@ async function tagRepoAsync(
107121 commit : string ,
108122 releaseTag : string ,
109123 tagMessage : string ,
110- githubPAT : string | null
124+ githubPAT : string
111125) : 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-
117126 console . log ( `Start to tag ${ owner } /${ repo } . Commit: ${ commit } , tag: ${ releaseTag } , message: ${ tagMessage } ` ) ;
118- const octokit = new Octokit ( { auth : pat } ) ;
127+ const octokit = new Octokit ( { auth : githubPAT } ) ;
119128 await octokit . auth ( ) ;
120129 const createTagResponse = await octokit . request ( `POST /repos/${ owner } /${ repo } /git/tags` , {
121130 owner : owner ,
@@ -130,25 +139,62 @@ async function tagRepoAsync(
130139 logError ( `Failed to create tag for ${ commit } in ${ owner } /${ repo } .` ) ;
131140 return false ;
132141 }
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 ;
142+ try {
143+ const refCreationResponse = await octokit . request ( `Post /repos/${ owner } /${ repo } /git/refs` , {
144+ owner : owner ,
145+ repo : repo ,
146+ ref : `refs/tags/${ releaseTag } ` ,
147+ sha : commit ,
148+ } ) ;
149+
150+ if ( refCreationResponse . status !== 201 ) {
151+ logError ( `Failed to create reference for ${ commit } in ${ owner } /${ repo } .` ) ;
152+ return false ;
153+ }
154+ } catch ( err : any ) {
155+ if ( err . status === 422 && err . message && err . message . includes ( 'Reference already exists' ) ) {
156+ logWarning ( `Reference for tag '${ releaseTag } ' already exists in ${ owner } /${ repo } .` ) ;
157+ return true ;
158+ } else {
159+ logError ( `Failed to create reference for ${ commit } in ${ owner } /${ repo } : ${ err } ` ) ;
160+ return false ;
161+ }
143162 }
144163
145164 console . log ( `Tag is created.` ) ;
146165 return true ;
147166}
148167
168+ // --- Helper functions ---
169+
170+ function getGitHubPAT ( options : { githubPAT ?: string | null } ) : string {
171+ const pat = options . githubPAT ?? process . env [ 'GitHubPAT' ] ;
172+ if ( ! pat ) {
173+ throw 'No GitHub Pat found. Specify with --githubPAT or set GitHubPAT environment variable.' ;
174+ }
175+ return pat ;
176+ }
177+
178+ function getFlag < T extends CreateTagsOptions > ( option : keyof T , options : T ) : boolean {
179+ const value = options [ option ] ;
180+ if ( ! value ) {
181+ logError ( `Missing required argument: --${ option . toString ( ) } ` ) ;
182+ }
183+ if ( typeof value === 'string' ) {
184+ return value . toLocaleLowerCase ( ) === 'true' ;
185+ } else {
186+ throw new Error ( `Expected boolean value for --${ option . toString ( ) } , but got ${ typeof value } ` ) ;
187+ }
188+ }
189+
190+ function logWarning ( message : string ) : void {
191+ console . log ( `##vso[task.logissue type=warning]${ message } ` ) ;
192+ }
193+
149194function logError ( message : string ) : void {
150195 console . log ( `##vso[task.logissue type=error]${ message } ` ) ;
151196}
197+
152198async function getCommitFromNugetAsync ( packageInfo : NugetPackageInfo ) : Promise < string | null > {
153199 const packageJsonString = fs . readFileSync ( './package.json' ) . toString ( ) ;
154200 const packageJson = JSON . parse ( packageJsonString ) ;
0 commit comments