@@ -8,179 +8,105 @@ import * as process from 'node:process';
88import * as fs from 'fs' ;
99import * as path from 'path' ;
1010import minimist from 'minimist' ;
11- import { spawnSync } from 'child_process' ;
12- import { getPackageJSON } from './packageJson' ;
13- import { Octokit } from '@octokit/rest' ;
11+ import {
12+ configureGitUser ,
13+ createCommit ,
14+ pushBranch ,
15+ createPullRequest ,
16+ doesBranchExist ,
17+ findPRByTitle ,
18+ } from './gitTasks' ;
19+ import { updatePackageDependencies } from '../src/tools/updatePackageDependencies' ;
1420
1521type Options = {
1622 userName ?: string ;
1723 email ?: string ;
1824} ;
1925
20- async function git ( args : string [ ] , printCommand = true ) : Promise < string > {
21- if ( printCommand ) {
22- console . log ( `git ${ args . join ( ' ' ) } ` ) ;
23- }
24-
25- const git = spawnSync ( 'git' , args ) ;
26- if ( git . status != 0 ) {
27- const err = git . stderr ? git . stderr . toString ( ) : '' ;
28- if ( printCommand ) {
29- console . error ( `Failed to execute git ${ args . join ( ' ' ) } .` ) ;
30- }
31- throw new Error ( err || `git ${ args . join ( ' ' ) } failed with code ${ git . status } ` ) ;
32- }
33-
34- const stdout = git . stdout ? git . stdout . toString ( ) : '' ;
35- if ( printCommand ) {
36- console . log ( stdout ) ;
37- }
38- return Promise . resolve ( stdout ) ;
26+ /**
27+ * Extract version from file name using a provided regex pattern
28+ * @param fileName - The file name to extract version from
29+ * @param pattern - The regex pattern to match and extract version (should have a capture group)
30+ * @returns The extracted version string or null if not found
31+ */
32+ function extractVersion ( fileName : string , pattern : RegExp ) : string | null {
33+ const match = fileName . match ( pattern ) ;
34+ return match && match [ 1 ] ? match [ 1 ] : null ;
3935}
4036
4137gulp . task ( 'publish roslyn copilot' , async ( ) => {
4238 const parsedArgs = minimist < Options > ( process . argv . slice ( 2 ) ) ;
4339
44- // Get staging directory from environment variable passed from pipeline
45- const stagingDir = process . env [ 'STAGING_DIRECTORY' ] ;
46- if ( ! stagingDir ) {
47- console . log ( 'STAGING_DIRECTORY environment variable not set; skipping package.json update.' ) ;
48- return ;
49- }
50-
51- if ( ! fs . existsSync ( stagingDir ) ) {
52- console . log ( `Staging directory not found at ${ stagingDir } ; skipping package.json update.` ) ;
53- return ;
40+ if ( ! parsedArgs . stagingDirectory || ! fs . existsSync ( parsedArgs . stagingDirectory ) ) {
41+ throw new Error ( `Staging directory not found at ${ parsedArgs . stagingDirectory } ; skipping package.json update.` ) ;
5442 }
5543
5644 // Find the Roslyn zip file in the staging directory (we know it was copied here)
57- const files = fs . readdirSync ( stagingDir ) ;
45+ const files = fs . readdirSync ( parsedArgs . stagingDirectory ) ;
5846 const zipFile = files . find ( ( file ) => / R o s l y n \. L a n g u a g e S e r v e r .* \. z i p $ / i. test ( file ) ) ;
5947
6048 if ( ! zipFile ) {
61- console . log ( `No Roslyn LanguageServer zip file found in ${ stagingDir } ; skipping package.json update.` ) ;
62- return ;
49+ throw new Error ( `
50+ No Roslyn LanguageServer zip file found in ${ parsedArgs . stagingDirectory } ; skipping package.json update.` ) ;
6351 }
6452
65- const zipPath = path . join ( stagingDir , zipFile ) ;
53+ const zipPath = path . join ( parsedArgs . stagingDirectory , zipFile ) ;
6654 console . log ( `Using zip file: ${ zipPath } ` ) ;
6755 const zipName = zipFile ;
6856
6957 // Extract version from file name
70- // Example: "Microsoft.VisualStudio.Copilot.Roslyn.LanguageServer-18.0.671-alpha.zip"
71- let version : string | null = null ;
72- const m = zipName . match ( / M i c r o s o f t \. V i s u a l S t u d i o \. C o p i l o t \. R o s l y n \. L a n g u a g e S e r v e r - ( .+ ) \. z i p $ / i) ;
73- if ( m && m [ 1 ] ) {
74- version = m [ 1 ] ;
75- }
58+ const version = extractVersion ( zipName , / M i c r o s o f t \. V i s u a l S t u d i o \. C o p i l o t \. R o s l y n \. L a n g u a g e S e r v e r - ( .+ ) \. z i p $ / i) ;
7659
7760 if ( ! version ) {
78- console . log ( `Could not extract version from file name ${ zipName } ; skipping.` ) ;
79- return ;
61+ throw new Error ( `Could not extract version from file name ${ zipName } ; skipping.` ) ;
8062 }
8163
8264 console . log ( `Extracted version: ${ version } ` ) ;
8365
84- const pkg = getPackageJSON ( ) ;
85- let updated = false ;
86-
87- if ( pkg . runtimeDependencies && Array . isArray ( pkg . runtimeDependencies ) ) {
88- for ( let i = 0 ; i < pkg . runtimeDependencies . length ; i ++ ) {
89- const dep = pkg . runtimeDependencies [ i ] ;
90- if ( dep && dep . id === 'RoslynCopilot' ) {
91- const oldUrl = dep . url as string ;
92- const newUrl = oldUrl . replace (
93- / M i c r o s o f t \. V i s u a l S t u d i o \. C o p i l o t \. R o s l y n \. L a n g u a g e S e r v e r - [ ^ / ] + ?\. z i p / ,
94- `Microsoft.VisualStudio.Copilot.Roslyn.LanguageServer-${ version } .zip`
95- ) ;
96- if ( oldUrl !== newUrl ) {
97- pkg . runtimeDependencies [ i ] . url = newUrl ;
98- updated = true ;
99- console . log ( `Updated RoslynCopilot url:\n ${ oldUrl } \n-> ${ newUrl } ` ) ;
100- } else {
101- console . log ( 'RoslynCopilot url already up to date.' ) ;
102- }
103- break ;
104- }
105- }
106- }
107-
108- if ( ! updated ) {
109- console . log ( 'No changes required to package.json; aborting PR creation.' ) ;
110- return ;
111- }
112-
113- // Persist package.json
114- fs . writeFileSync ( 'package.json' , JSON . stringify ( pkg , null , 2 ) + '\n' , { encoding : 'utf8' } ) ;
115-
116- // Prepare git
11766 const safeVersion = version . replace ( / [ ^ A - Z a - z 0 - 9 _ . - ] / g, '-' ) ;
11867 const branch = `update/roslyn-copilot-${ safeVersion } ` ;
11968
120- // Make this optional so it can be tested locally by using dev's information. In real CI user name and email are always supplied.
121- if ( parsedArgs . userName ) {
122- await git ( [ 'config' , '--local' , 'user.name' , parsedArgs . userName ] ) ;
123- }
124- if ( parsedArgs . email ) {
125- await git ( [ 'config' , '--local' , 'user.email' , parsedArgs . email ] ) ;
126- }
127-
128- await git ( [ 'checkout' , '-b' , branch ] ) ;
129- await git ( [ 'add' , 'package.json' ] ) ;
130- await git ( [ 'commit' , '-m' , `chore: update RoslynCopilot url to ${ version } ` ] ) ;
131-
13269 const pat = process . env [ 'GitHubPAT' ] ;
13370 if ( ! pat ) {
13471 throw 'No GitHub PAT found.' ;
13572 }
13673
137- const remoteRepoAlias = 'targetRepo' ;
138- await git (
139- [ 'remote' , 'add' , remoteRepoAlias , `https://${ parsedArgs . userName } :${ pat } @github.com/dotnet/vscode-csharp.git` ] ,
140- // Note: don't print PAT to console
141- false
142- ) ;
143- await git ( [ 'fetch' , remoteRepoAlias ] ) ;
144-
145- const lsRemote = await git ( [ 'ls-remote' , remoteRepoAlias , 'refs/head/' + branch ] ) ;
146- if ( lsRemote . trim ( ) !== '' ) {
147- // If the localization branch of this commit already exists, don't try to create another one.
148- console . log ( `##vso[task.logissue type=error]${ branch } already exists in dotnet/vscode-csharp. Skip pushing.` ) ;
149- } else {
150- await git ( [ 'push' , '-u' , remoteRepoAlias ] ) ;
151- }
74+ const owner = 'dotnet' ;
75+ const repo = 'vscode-csharp' ;
76+ const title = `Update RoslynCopilot url to ${ version } ` ;
77+ const body = `Automated update of RoslynCopilot url to ${ version } ` ;
15278
153- // Create PR via Octokit
154- try {
155- const octokit = new Octokit ( { auth : pat } ) ;
156- const listPullRequest = await octokit . rest . pulls . list ( {
157- owner : 'dotnet' ,
158- repo : 'vscode-csharp' ,
159- } ) ;
160-
161- if ( listPullRequest . status != 200 ) {
162- throw `Failed get response from GitHub, http status code: ${ listPullRequest . status } ` ;
163- }
164-
165- const title = `Update RoslynCopilot url to ${ version } ` ;
166- if ( listPullRequest . data . some ( ( pr ) => pr . title === title ) ) {
167- console . log ( 'Pull request with the same name already exists. Skip creation.' ) ;
168- return ;
169- }
170-
171- const body = `Automated update of RoslynCopilot url to ${ version } ` ;
172-
173- console . log ( `Creating PR against dotnet/vscode-csharp...` ) ;
174- const pullRequest = await octokit . rest . pulls . create ( {
175- owner : 'dotnet' ,
176- repo : 'vscode-csharp' ,
177- title : title ,
178- head : branch ,
179- base : 'main' ,
180- body : body ,
181- } ) ;
182- console . log ( `Created pull request: ${ pullRequest . data . html_url } ` ) ;
183- } catch ( e ) {
184- console . warn ( 'Failed to create PR via Octokit:' , e ) ;
79+ // Bail out if a branch with the same name already exists or PR already exists for the insertion.
80+ if ( await doesBranchExist ( 'origin' , branch ) ) {
81+ console . log ( `##vso[task.logissue type=warning]${ branch } already exists in origin. Skip pushing.` ) ;
82+ return ;
18583 }
84+ const existingPRUrl = await findPRByTitle ( pat , owner , repo , title ) ;
85+ if ( existingPRUrl ) {
86+ console . log (
87+ `##vso[task.logissue type=warning] Pull request with the same name already exists: ${ existingPRUrl } `
88+ ) ;
89+ return ;
90+ }
91+
92+ // Set environment variables for updatePackageDependencies
93+ process . env [ 'NEW_DEPS_ID' ] = 'RoslynCopilot' ;
94+ process . env [ 'NEW_DEPS_VERSION' ] = version ;
95+ process . env [
96+ 'NEW_DEPS_URLS'
97+ ] = `https://roslyn.blob.core.windows.net/releases/Microsoft.VisualStudio.Copilot.Roslyn.LanguageServer-${ version } .zip` ;
98+
99+ // Update package dependencies using the extracted utility
100+ await updatePackageDependencies ( ) ;
101+ console . log ( `Updated RoslynCopilot dependency to version ${ version } ` ) ;
102+
103+ // Configure git user if provided
104+ await configureGitUser ( parsedArgs . userName , parsedArgs . email ) ;
105+
106+ // Create commit with changes
107+ await createCommit ( branch , [ 'package.json' ] , `Update RoslynCopilot version to ${ version } ` ) ;
108+
109+ // Push branch and create PR
110+ await pushBranch ( branch , pat , owner , repo ) ;
111+ await createPullRequest ( pat , owner , repo , branch , title , body ) ;
186112} ) ;
0 commit comments