1
- import nv from '@pkgjs/nv' ;
2
1
import fs from 'node:fs' ;
3
2
import path from 'node:path' ;
4
3
import auth from './auth.js' ;
@@ -10,100 +9,89 @@ import {
10
9
PLACEHOLDERS ,
11
10
checkoutOnSecurityReleaseBranch ,
12
11
commitAndPushVulnerabilitiesJSON ,
13
- getSummary ,
14
12
validateDate ,
15
13
promptDependencies ,
16
- getSupportedVersions
14
+ getSupportedVersions ,
15
+ pickReport
17
16
} from './security-release/security-release.js' ;
18
17
import _ from 'lodash' ;
19
18
20
- export default class SecurityReleaseSteward {
19
+ export default class PrepareSecurityRelease {
21
20
repository = NEXT_SECURITY_RELEASE_REPOSITORY ;
21
+ title = 'Next Security Release' ;
22
22
constructor ( cli ) {
23
23
this . cli = cli ;
24
24
}
25
25
26
26
async start ( ) {
27
- const { cli } = this ;
28
27
const credentials = await auth ( {
29
28
github : true ,
30
29
h1 : true
31
30
} ) ;
32
31
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 ( ) ;
36
34
if ( releaseDate !== 'TBD' ) {
37
35
validateDate ( releaseDate ) ;
38
36
}
39
-
40
- const createVulnerabilitiesJSON = await release . promptVulnerabilitiesJSON ( cli ) ;
37
+ const createVulnerabilitiesJSON = await this . promptVulnerabilitiesJSON ( ) ;
41
38
42
39
let securityReleasePRUrl ;
43
40
if ( createVulnerabilitiesJSON ) {
44
- securityReleasePRUrl = await this . createVulnerabilitiesJSON (
45
- req , release , releaseDate , { cli } ) ;
41
+ securityReleasePRUrl = await this . startVulnerabilitiesJSONCreation ( releaseDate ) ;
46
42
}
47
43
48
- const createIssue = await release . promptCreateRelaseIssue ( cli ) ;
44
+ const createIssue = await this . promptCreateRelaseIssue ( ) ;
49
45
50
46
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 } ) ;
53
50
} ;
54
51
55
- cli . ok ( 'Done!' ) ;
52
+ this . cli . ok ( 'Done!' ) ;
56
53
}
57
54
58
- async createVulnerabilitiesJSON ( req , release , releaseDate , { cli } ) {
55
+ async startVulnerabilitiesJSONCreation ( releaseDate ) {
59
56
// checkout on the next-security-release branch
60
- checkoutOnSecurityReleaseBranch ( cli , this . repository ) ;
57
+ checkoutOnSecurityReleaseBranch ( this . cli , this . repository ) ;
61
58
62
59
// 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 ( ) ;
65
62
const deps = _ . groupBy ( depUpdates , 'name' ) ;
66
63
67
64
// 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
+
69
67
// review the vulnerabilities.json file
70
- const review = await release . promptReviewVulnerabilitiesJSON ( cli ) ;
68
+ const review = await this . promptReviewVulnerabilitiesJSON ( ) ;
71
69
72
70
if ( ! review ) {
73
- cli . info ( `To push the vulnerabilities.json file run:
71
+ this . cli . info ( `To push the vulnerabilities.json file run:
74
72
- git add ${ filePath }
75
73
- git commit -m "chore: create vulnerabilities.json for next security release"
76
74
- 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 } ` ) ;
78
76
return ;
79
77
} ;
80
78
81
79
// commit and push the vulnerabilities.json file
82
80
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 } ) ;
84
84
85
- const createPr = await release . promptCreatePR ( cli ) ;
85
+ const createPr = await this . promptCreatePR ( ) ;
86
86
87
87
if ( ! createPr ) return ;
88
88
89
89
// create pr on the security-release repo
90
- return release . createPullRequest ( req , { cli } ) ;
90
+ return this . createPullRequest ( ) ;
91
91
}
92
- }
93
92
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 (
107
95
'Create the Next Security Release PR?' ,
108
96
{ defaultAnswer : true } ) ;
109
97
}
@@ -125,31 +113,32 @@ class PrepareSecurityRelease {
125
113
}
126
114
}
127
115
128
- async promptReleaseDate ( cli ) {
116
+ async promptReleaseDate ( ) {
129
117
const nextWeekDate = new Date ( ) ;
130
118
nextWeekDate . setDate ( nextWeekDate . getDate ( ) + 7 ) ;
131
119
// Format the date as YYYY/MM/DD
132
120
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
+ } ) ;
137
126
}
138
127
139
- async promptVulnerabilitiesJSON ( cli ) {
140
- return cli . prompt (
128
+ async promptVulnerabilitiesJSON ( ) {
129
+ return this . cli . prompt (
141
130
'Create the vulnerabilities.json?' ,
142
131
{ defaultAnswer : true } ) ;
143
132
}
144
133
145
- async promptCreateRelaseIssue ( cli ) {
146
- return cli . prompt (
134
+ async promptCreateRelaseIssue ( ) {
135
+ return this . cli . prompt (
147
136
'Create the Next Security Release issue?' ,
148
137
{ defaultAnswer : true } ) ;
149
138
}
150
139
151
- async promptReviewVulnerabilitiesJSON ( cli ) {
152
- return cli . prompt (
140
+ async promptReviewVulnerabilitiesJSON ( ) {
141
+ return this . cli . prompt (
153
142
'Please review vulnerabilities.json and press enter to proceed.' ,
154
143
{ defaultAnswer : true } ) ;
155
144
}
@@ -161,67 +150,21 @@ class PrepareSecurityRelease {
161
150
return content ;
162
151
}
163
152
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...' ) ;
176
155
const reports = await this . req . getTriagedReports ( ) ;
177
- const supportedVersions = ( await nv ( 'supported' ) )
178
- . map ( ( v ) => `${ v . versionName } .x` )
179
- . join ( ',' ) ;
180
156
const selectedReports = [ ] ;
181
157
182
158
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 ) ;
219
162
}
220
163
return selectedReports ;
221
164
}
222
165
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...' ) ;
225
168
const file = JSON . stringify ( {
226
169
releaseDate,
227
170
reports,
@@ -237,14 +180,14 @@ class PrepareSecurityRelease {
237
180
238
181
const fullPath = path . join ( folderPath , 'vulnerabilities.json' ) ;
239
182
fs . writeFileSync ( fullPath , file ) ;
240
- cli . ok ( `Created ${ fullPath } ` ) ;
183
+ this . cli . ok ( `Created ${ fullPath } ` ) ;
241
184
242
185
return fullPath ;
243
186
}
244
187
245
- async createPullRequest ( req , { cli } ) {
188
+ async createPullRequest ( ) {
246
189
const { owner, repo } = this . repository ;
247
- const response = await req . createPullRequest (
190
+ const response = await this . req . createPullRequest (
248
191
this . title ,
249
192
'List of vulnerabilities to be included in the next security release' ,
250
193
{
@@ -257,49 +200,52 @@ class PrepareSecurityRelease {
257
200
) ;
258
201
const url = response ?. html_url ;
259
202
if ( url ) {
260
- cli . ok ( `Created: ${ url } ` ) ;
203
+ this . cli . ok ( `Created: ${ url } ` ) ;
261
204
return url ;
262
205
}
263
206
if ( response ?. errors ) {
264
207
for ( const error of response . errors ) {
265
- cli . error ( error . message ) ;
208
+ this . cli . error ( error . message ) ;
266
209
}
267
210
} else {
268
- cli . error ( response ) ;
211
+ this . cli . error ( response ) ;
269
212
}
270
213
process . exit ( 1 ) ;
271
214
}
272
215
273
- async getDependencyUpdates ( { cli } ) {
216
+ async getDependencyUpdates ( ) {
274
217
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
+ } ) ;
281
225
282
226
if ( ! updates ) return deps ;
283
227
284
228
const supportedVersions = await getSupportedVersions ( ) ;
285
229
286
230
let asking = true ;
287
231
while ( asking ) {
288
- const dep = await promptDependencies ( cli ) ;
232
+ const dep = await promptDependencies ( this . cli ) ;
289
233
if ( ! dep ) {
290
234
asking = false ;
291
235
break ;
292
236
}
293
237
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
+ } ) ;
298
243
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
+ } ) ;
303
249
304
250
try {
305
251
const prUrl = dep . replace ( 'https://github.com/' , 'https://api.github.com/repos/' ) . replace ( 'pull' , 'pulls' ) ;
@@ -311,7 +257,7 @@ class PrepareSecurityRelease {
311
257
title,
312
258
affectedVersions : versions . split ( ',' ) . map ( ( v ) => v . replace ( 'v' , '' ) . trim ( ) )
313
259
} ) ;
314
- cli . separator ( ) ;
260
+ this . cli . separator ( ) ;
315
261
} catch ( error ) {
316
262
this . cli . error ( 'Invalid PR url. Please provide a valid PR url.' ) ;
317
263
this . cli . error ( error ) ;
0 commit comments