1- import nv from '@pkgjs/nv' ;
21import fs from 'node:fs' ;
32import path from 'node:path' ;
43import auth from './auth.js' ;
@@ -10,100 +9,89 @@ import {
109 PLACEHOLDERS ,
1110 checkoutOnSecurityReleaseBranch ,
1211 commitAndPushVulnerabilitiesJSON ,
13- getSummary ,
1412 validateDate ,
1513 promptDependencies ,
16- getSupportedVersions
14+ getSupportedVersions ,
15+ pickReport
1716} from './security-release/security-release.js' ;
1817import _ from 'lodash' ;
1918
20- export default class SecurityReleaseSteward {
19+ export default class PrepareSecurityRelease {
2120 repository = NEXT_SECURITY_RELEASE_REPOSITORY ;
21+ title = 'Next Security Release' ;
2222 constructor ( cli ) {
2323 this . cli = cli ;
2424 }
2525
2626 async start ( ) {
27- const { cli } = this ;
2827 const credentials = await auth ( {
2928 github : true ,
3029 h1 : true
3130 } ) ;
3231
33- const req = new Request ( credentials ) ;
34- const release = new PrepareSecurityRelease ( req ) ;
35- const releaseDate = await release . promptReleaseDate ( cli ) ;
32+ this . req = new Request ( credentials ) ;
33+ const releaseDate = await this . promptReleaseDate ( ) ;
3634 if ( releaseDate !== 'TBD' ) {
3735 validateDate ( releaseDate ) ;
3836 }
39-
40- const createVulnerabilitiesJSON = await release . promptVulnerabilitiesJSON ( cli ) ;
37+ const createVulnerabilitiesJSON = await this . promptVulnerabilitiesJSON ( ) ;
4138
4239 let securityReleasePRUrl ;
4340 if ( createVulnerabilitiesJSON ) {
44- securityReleasePRUrl = await this . createVulnerabilitiesJSON (
45- req , release , releaseDate , { cli } ) ;
41+ securityReleasePRUrl = await this . startVulnerabilitiesJSONCreation ( releaseDate ) ;
4642 }
4743
48- const createIssue = await release . promptCreateRelaseIssue ( cli ) ;
44+ const createIssue = await this . promptCreateRelaseIssue ( ) ;
4945
5046 if ( createIssue ) {
51- const content = await release . buildIssue ( releaseDate , securityReleasePRUrl ) ;
52- await release . createIssue ( content , { cli } ) ;
47+ const content = await this . buildIssue ( releaseDate , securityReleasePRUrl ) ;
48+ await createIssue (
49+ this . title , content , this . repository , { cli : this . cli , repository : this . repository } ) ;
5350 } ;
5451
55- cli . ok ( 'Done!' ) ;
52+ this . cli . ok ( 'Done!' ) ;
5653 }
5754
58- async createVulnerabilitiesJSON ( req , release , releaseDate , { cli } ) {
55+ async startVulnerabilitiesJSONCreation ( releaseDate ) {
5956 // checkout on the next-security-release branch
60- checkoutOnSecurityReleaseBranch ( cli , this . repository ) ;
57+ checkoutOnSecurityReleaseBranch ( this . cli , this . repository ) ;
6158
6259 // choose the reports to include in the security release
63- const reports = await release . chooseReports ( cli ) ;
64- const depUpdates = await release . getDependencyUpdates ( { cli } ) ;
60+ const reports = await this . chooseReports ( ) ;
61+ const depUpdates = await this . getDependencyUpdates ( ) ;
6562 const deps = _ . groupBy ( depUpdates , 'name' ) ;
6663
6764 // create the vulnerabilities.json file in the security-release repo
68- const filePath = await release . createVulnerabilitiesJSON ( reports , deps , releaseDate , { cli } ) ;
65+ const filePath = await this . createVulnerabilitiesJSON ( reports , deps , releaseDate ) ;
66+
6967 // review the vulnerabilities.json file
70- const review = await release . promptReviewVulnerabilitiesJSON ( cli ) ;
68+ const review = await this . promptReviewVulnerabilitiesJSON ( ) ;
7169
7270 if ( ! review ) {
73- cli . info ( `To push the vulnerabilities.json file run:
71+ this . cli . info ( `To push the vulnerabilities.json file run:
7472 - git add ${ filePath }
7573 - git commit -m "chore: create vulnerabilities.json for next security release"
7674 - git push -u origin ${ NEXT_SECURITY_RELEASE_BRANCH }
77- - open a PR on ${ release . repository . owner } /${ release . repository . repo } ` ) ;
75+ - open a PR on ${ this . repository . owner } /${ this . repository . repo } ` ) ;
7876 return ;
7977 } ;
8078
8179 // commit and push the vulnerabilities.json file
8280 const commitMessage = 'chore: create vulnerabilities.json for next security release' ;
83- commitAndPushVulnerabilitiesJSON ( filePath , commitMessage , { cli, repository : this . repository } ) ;
81+ commitAndPushVulnerabilitiesJSON ( filePath ,
82+ commitMessage ,
83+ { cli : this . cli , repository : this . repository } ) ;
8484
85- const createPr = await release . promptCreatePR ( cli ) ;
85+ const createPr = await this . promptCreatePR ( ) ;
8686
8787 if ( ! createPr ) return ;
8888
8989 // create pr on the security-release repo
90- return release . createPullRequest ( req , { cli } ) ;
90+ return this . createPullRequest ( ) ;
9191 }
92- }
9392
94- class PrepareSecurityRelease {
95- repository = NEXT_SECURITY_RELEASE_REPOSITORY ;
96- title = 'Next Security Release' ;
97-
98- constructor ( req , repository ) {
99- this . req = req ;
100- if ( repository ) {
101- this . repository = repository ;
102- }
103- }
104-
105- promptCreatePR ( cli ) {
106- return cli . prompt (
93+ promptCreatePR ( ) {
94+ return this . cli . prompt (
10795 'Create the Next Security Release PR?' ,
10896 { defaultAnswer : true } ) ;
10997 }
@@ -125,31 +113,32 @@ class PrepareSecurityRelease {
125113 }
126114 }
127115
128- async promptReleaseDate ( cli ) {
116+ async promptReleaseDate ( ) {
129117 const nextWeekDate = new Date ( ) ;
130118 nextWeekDate . setDate ( nextWeekDate . getDate ( ) + 7 ) ;
131119 // Format the date as YYYY/MM/DD
132120 const formattedDate = nextWeekDate . toISOString ( ) . slice ( 0 , 10 ) . replace ( / - / g, '/' ) ;
133- return cli . prompt ( 'Enter target release date in YYYY/MM/DD format (TBD if not defined yet):' , {
134- questionType : 'input' ,
135- defaultAnswer : formattedDate
136- } ) ;
121+ return this . cli . prompt (
122+ 'Enter target release date in YYYY/MM/DD format (TBD if not defined yet):' , {
123+ questionType : 'input' ,
124+ defaultAnswer : formattedDate
125+ } ) ;
137126 }
138127
139- async promptVulnerabilitiesJSON ( cli ) {
140- return cli . prompt (
128+ async promptVulnerabilitiesJSON ( ) {
129+ return this . cli . prompt (
141130 'Create the vulnerabilities.json?' ,
142131 { defaultAnswer : true } ) ;
143132 }
144133
145- async promptCreateRelaseIssue ( cli ) {
146- return cli . prompt (
134+ async promptCreateRelaseIssue ( ) {
135+ return this . cli . prompt (
147136 'Create the Next Security Release issue?' ,
148137 { defaultAnswer : true } ) ;
149138 }
150139
151- async promptReviewVulnerabilitiesJSON ( cli ) {
152- return cli . prompt (
140+ async promptReviewVulnerabilitiesJSON ( ) {
141+ return this . cli . prompt (
153142 'Please review vulnerabilities.json and press enter to proceed.' ,
154143 { defaultAnswer : true } ) ;
155144 }
@@ -161,67 +150,21 @@ class PrepareSecurityRelease {
161150 return content ;
162151 }
163152
164- async createIssue ( content , { cli } ) {
165- const data = await this . req . createIssue ( this . title , content , this . repository ) ;
166- if ( data . html_url ) {
167- cli . ok ( `Created: ${ data . html_url } ` ) ;
168- } else {
169- cli . error ( data ) ;
170- process . exit ( 1 ) ;
171- }
172- }
173-
174- async chooseReports ( cli ) {
175- cli . info ( 'Getting triaged H1 reports...' ) ;
153+ async chooseReports ( ) {
154+ this . cli . info ( 'Getting triaged H1 reports...' ) ;
176155 const reports = await this . req . getTriagedReports ( ) ;
177- const supportedVersions = ( await nv ( 'supported' ) )
178- . map ( ( v ) => `${ v . versionName } .x` )
179- . join ( ',' ) ;
180156 const selectedReports = [ ] ;
181157
182158 for ( const report of reports . data ) {
183- const {
184- id, attributes : { title, cve_ids } ,
185- relationships : { severity, weakness, reporter }
186- } = report ;
187- const link = `https://hackerone.com/reports/${ id } ` ;
188- const reportSeverity = {
189- rating : severity ?. data ?. attributes ?. rating || '' ,
190- cvss_vector_string : severity ?. data ?. attributes ?. cvss_vector_string || '' ,
191- weakness_id : weakness ?. data ?. id || ''
192- } ;
193-
194- cli . separator ( ) ;
195- cli . info ( `Report: ${ link } - ${ title } (${ reportSeverity ?. rating } )` ) ;
196- const include = await cli . prompt (
197- 'Would you like to include this report to the next security release?' ,
198- { defaultAnswer : true } ) ;
199- if ( ! include ) {
200- continue ;
201- }
202-
203- const versions = await cli . prompt ( 'Which active release lines this report affects?' , {
204- questionType : 'input' ,
205- defaultAnswer : supportedVersions
206- } ) ;
207- const summaryContent = await getSummary ( id , this . req ) ;
208-
209- selectedReports . push ( {
210- id,
211- title,
212- cveIds : cve_ids ,
213- severity : reportSeverity ,
214- summary : summaryContent ?? '' ,
215- affectedVersions : versions . split ( ',' ) . map ( ( v ) => v . replace ( 'v' , '' ) . trim ( ) ) ,
216- link,
217- reporter : reporter . data . attributes . username
218- } ) ;
159+ const rep = await pickReport ( report , { cli : this . cli , req : this . req } ) ;
160+ if ( ! rep ) continue ;
161+ selectedReports . push ( rep ) ;
219162 }
220163 return selectedReports ;
221164 }
222165
223- async createVulnerabilitiesJSON ( reports , dependencies , releaseDate , { cli } ) {
224- cli . separator ( 'Creating vulnerabilities.json...' ) ;
166+ async createVulnerabilitiesJSON ( reports , dependencies , releaseDate ) {
167+ this . cli . separator ( 'Creating vulnerabilities.json...' ) ;
225168 const file = JSON . stringify ( {
226169 releaseDate,
227170 reports,
@@ -237,14 +180,14 @@ class PrepareSecurityRelease {
237180
238181 const fullPath = path . join ( folderPath , 'vulnerabilities.json' ) ;
239182 fs . writeFileSync ( fullPath , file ) ;
240- cli . ok ( `Created ${ fullPath } ` ) ;
183+ this . cli . ok ( `Created ${ fullPath } ` ) ;
241184
242185 return fullPath ;
243186 }
244187
245- async createPullRequest ( req , { cli } ) {
188+ async createPullRequest ( ) {
246189 const { owner, repo } = this . repository ;
247- const response = await req . createPullRequest (
190+ const response = await this . req . createPullRequest (
248191 this . title ,
249192 'List of vulnerabilities to be included in the next security release' ,
250193 {
@@ -257,49 +200,52 @@ class PrepareSecurityRelease {
257200 ) ;
258201 const url = response ?. html_url ;
259202 if ( url ) {
260- cli . ok ( `Created: ${ url } ` ) ;
203+ this . cli . ok ( `Created: ${ url } ` ) ;
261204 return url ;
262205 }
263206 if ( response ?. errors ) {
264207 for ( const error of response . errors ) {
265- cli . error ( error . message ) ;
208+ this . cli . error ( error . message ) ;
266209 }
267210 } else {
268- cli . error ( response ) ;
211+ this . cli . error ( response ) ;
269212 }
270213 process . exit ( 1 ) ;
271214 }
272215
273- async getDependencyUpdates ( { cli } ) {
216+ async getDependencyUpdates ( ) {
274217 const deps = [ ] ;
275- cli . log ( '\n' ) ;
276- cli . separator ( 'Dependency Updates' ) ;
277- const updates = await cli . prompt ( 'Are there dependency updates in this security release?' , {
278- defaultAnswer : true ,
279- questionType : 'confirm'
280- } ) ;
218+ this . cli . log ( '\n' ) ;
219+ this . cli . separator ( 'Dependency Updates' ) ;
220+ const updates = await this . cli . prompt ( 'Are there dependency updates in this security release?' ,
221+ {
222+ defaultAnswer : true ,
223+ questionType : 'confirm'
224+ } ) ;
281225
282226 if ( ! updates ) return deps ;
283227
284228 const supportedVersions = await getSupportedVersions ( ) ;
285229
286230 let asking = true ;
287231 while ( asking ) {
288- const dep = await promptDependencies ( cli ) ;
232+ const dep = await promptDependencies ( this . cli ) ;
289233 if ( ! dep ) {
290234 asking = false ;
291235 break ;
292236 }
293237
294- const name = await cli . prompt ( 'What is the name of the dependency that has been updated?' , {
295- defaultAnswer : '' ,
296- questionType : 'input'
297- } ) ;
238+ const name = await this . cli . prompt (
239+ 'What is the name of the dependency that has been updated?' , {
240+ defaultAnswer : '' ,
241+ questionType : 'input'
242+ } ) ;
298243
299- const versions = await cli . prompt ( 'Which release line does this dependency update affect?' , {
300- defaultAnswer : supportedVersions ,
301- questionType : 'input'
302- } ) ;
244+ const versions = await this . cli . prompt (
245+ 'Which release line does this dependency update affect?' , {
246+ defaultAnswer : supportedVersions ,
247+ questionType : 'input'
248+ } ) ;
303249
304250 try {
305251 const prUrl = dep . replace ( 'https://github.com/' , 'https://api.github.com/repos/' ) . replace ( 'pull' , 'pulls' ) ;
@@ -311,7 +257,7 @@ class PrepareSecurityRelease {
311257 title,
312258 affectedVersions : versions . split ( ',' ) . map ( ( v ) => v . replace ( 'v' , '' ) . trim ( ) )
313259 } ) ;
314- cli . separator ( ) ;
260+ this . cli . separator ( ) ;
315261 } catch ( error ) {
316262 this . cli . error ( 'Invalid PR url. Please provide a valid PR url.' ) ;
317263 this . cli . error ( error ) ;
0 commit comments