@node-core/utils 6.3.2 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/cli.js CHANGED
@@ -94,6 +94,14 @@ export default class CLI {
94
94
  return answer;
95
95
  }
96
96
 
97
+ async promptCheckbox(message, choices) {
98
+ if (this.assumeYes) {
99
+ return choices.filter((c) => c.checked).map((c) => c.value);
100
+ }
101
+
102
+ return inquirer.checkbox({ message, choices });
103
+ }
104
+
97
105
  setAssumeYes() {
98
106
  this.assumeYes = true;
99
107
  }
@@ -316,40 +316,49 @@ export default class LandingSession extends Session {
316
316
 
317
317
  // git has very specific rules about what is a trailer and what is not.
318
318
  // Instead of trying to implement those ourselves, let git parse the
319
- // original commit message and see if it outputs any trailers.
320
- const originalTrailers = runSync('git', [
319
+ // commit message and see if it outputs any trailers.
320
+ const interpretTrailers = commitMessage => runSync('git', [
321
321
  'interpret-trailers', '--parse', '--no-divider'
322
322
  ], {
323
- input: `${original}\n`
324
- }).split('\n').map(trailer => {
325
- const separatorIndex = trailer.indexOf(':');
326
- return [trailer.slice(0, separatorIndex), trailer.slice(separatorIndex + 2)];
327
- }).slice(0, -1); // Remove the last line return entry
323
+ input: `${commitMessage}\n`
324
+ });
328
325
 
326
+ const originalTrailers = interpretTrailers(original);
329
327
  const containCVETrailer = CVE_RE.test(originalTrailers);
330
328
 
331
- const filterTrailer = (line) => ([key]) =>
332
- line.startsWith(key) &&
333
- new RegExp(`^${RegExp.escape?.(key) ?? key}\\s*:`).test(line);
334
- const amended = original.trim().split('\n')
335
- .filter(line =>
336
- !line.includes(':') ||
337
- !originalTrailers.some(filterTrailer(line)));
338
- for (let i = amended.length - 1; amended[i] === ''; i--) {
339
- amended.pop();
340
- }
329
+ const amended = original.trim().split('\n');
330
+ let keptTrailers = [];
331
+ if (originalTrailers) {
332
+ const stillInTrailers = () => {
333
+ const result = interpretTrailers(amended.join('\n'));
334
+ return result.length && originalTrailers.startsWith(result.trim());
335
+ };
336
+ // Remove all lines until we find an empty string, which should get of all
337
+ // trailers in most cases.
338
+ amended.splice(amended.lastIndexOf(''), Infinity);
339
+ for (let i = amended.length - 1; amended[i] === '' || stillInTrailers(); i--) {
340
+ // Remove last line until git no longer detects any trailers
341
+ amended.pop();
342
+ }
341
343
 
342
- // Only keep existing trailers that we won't add ourselves
343
- const trailersToFilterOut = ['BACKPORT-PR-URL', 'REVIEWED-BY'];
344
- if (!this.backport) trailersToFilterOut.push('PR-URL');
345
- const keptTrailers = originalTrailers.filter(
346
- ([key]) => !trailersToFilterOut.includes(key.toUpperCase())
347
- );
344
+ // Only keep existing trailers that we won't add ourselves
345
+ const trailersToFilterOut = ['BACKPORT-PR-URL', 'REVIEWED-BY'];
346
+ if (!this.backport) trailersToFilterOut.push('PR-URL');
347
+ keptTrailers = originalTrailers
348
+ .split('\n')
349
+ .map(trailer => {
350
+ const separatorIndex = trailer.indexOf(':');
351
+ return [trailer.slice(0, separatorIndex), trailer.slice(separatorIndex + 2)];
352
+ })
353
+ .slice(0, -1) // Remove the last line return entry
354
+ .filter(
355
+ ([key]) => !trailersToFilterOut.includes(key.toUpperCase())
356
+ );
357
+ }
348
358
  amended.push('', ...keptTrailers.map(([key, value]) => `${key}: ${value}`));
349
359
 
350
360
  for (const line of metadata.trim().split('\n')) {
351
- const foundMatchingTrailer = keptTrailers.find(filterTrailer(line));
352
- if (foundMatchingTrailer && line === foundMatchingTrailer.join(': ')) {
361
+ if (keptTrailers.some((trailer) => line.toUpperCase() === trailer.join(': ').toUpperCase())) {
353
362
  cli.warn(`Found ${line}, skipping..`);
354
363
  continue;
355
364
  }
@@ -10,11 +10,19 @@ import {
10
10
  validateDate,
11
11
  promptDependencies,
12
12
  getSupportedVersions,
13
+ getReportSeverity,
13
14
  pickReport,
14
15
  SecurityRelease
15
16
  } from './security-release/security-release.js';
16
17
  import _ from 'lodash';
17
18
 
19
+ function relativeDate(date) {
20
+ const days = Math.floor((Date.now() - date) / (1000 * 60 * 60 * 24));
21
+ if (days < 30) return days === 1 ? '1 day ago' : `${days} days ago`;
22
+ const months = Math.floor(days / 30);
23
+ return months === 1 ? '1 month ago' : `${months} months ago`;
24
+ }
25
+
18
26
  export default class PrepareSecurityRelease extends SecurityRelease {
19
27
  title = 'Next Security Release';
20
28
 
@@ -25,6 +33,13 @@ export default class PrepareSecurityRelease extends SecurityRelease {
25
33
  });
26
34
 
27
35
  this.req = new Request(credentials);
36
+
37
+ let excludedReports = [];
38
+ const showTriaged = await this.promptShowTriagedWithoutPR();
39
+ if (showTriaged) {
40
+ excludedReports = await this.showTriagedReportsWithoutPR();
41
+ }
42
+
28
43
  const releaseDate = await this.promptReleaseDate();
29
44
  if (releaseDate !== 'TBD') {
30
45
  validateDate(releaseDate);
@@ -34,7 +49,8 @@ export default class PrepareSecurityRelease extends SecurityRelease {
34
49
  let securityReleasePRUrl;
35
50
  const content = await this.buildDescription(releaseDate, securityReleasePRUrl);
36
51
  if (createVulnerabilitiesJSON) {
37
- securityReleasePRUrl = await this.startVulnerabilitiesJSONCreation(releaseDate, content);
52
+ securityReleasePRUrl = await this.startVulnerabilitiesJSONCreation(
53
+ releaseDate, content, excludedReports);
38
54
  }
39
55
 
40
56
  this.cli.ok('Done!');
@@ -93,12 +109,12 @@ export default class PrepareSecurityRelease extends SecurityRelease {
93
109
  this.cli.ok('Done!');
94
110
  }
95
111
 
96
- async startVulnerabilitiesJSONCreation(releaseDate, content) {
112
+ async startVulnerabilitiesJSONCreation(releaseDate, content, excludedReports = []) {
97
113
  // checkout on the next-security-release branch
98
114
  checkoutOnSecurityReleaseBranch(this.cli, this.repository);
99
115
 
100
116
  // choose the reports to include in the security release
101
- const reports = await this.chooseReports();
117
+ const reports = await this.chooseReports(excludedReports);
102
118
  const depUpdates = await this.getDependencyUpdates();
103
119
  const deps = _.groupBy(depUpdates, 'name');
104
120
 
@@ -184,17 +200,61 @@ export default class PrepareSecurityRelease extends SecurityRelease {
184
200
  { defaultAnswer: true });
185
201
  }
186
202
 
203
+ async promptShowTriagedWithoutPR() {
204
+ return this.cli.prompt(
205
+ 'Do you want to see which reports are triaged but have no PR URL?',
206
+ { defaultAnswer: true });
207
+ }
208
+
209
+ async showTriagedReportsWithoutPR() {
210
+ this.cli.info('Fetching triaged reports without PR URL...');
211
+ const reports = await this.req.getTriagedReports();
212
+ const reportsWithoutPR = reports.data.filter(
213
+ (report) => !report.relationships.custom_field_values.data.length
214
+ );
215
+ if (!reportsWithoutPR.length) {
216
+ this.cli.ok('All triaged reports have a PR URL.');
217
+ return [];
218
+ }
219
+ const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
220
+ const choices = reportsWithoutPR
221
+ .sort((a, b) => {
222
+ const dateA = new Date(a.attributes.created_at);
223
+ const dateB = new Date(b.attributes.created_at);
224
+ if (dateB - dateA !== 0) return dateB - dateA;
225
+ const rankA = severityRank[getReportSeverity(a).rating] ?? 4;
226
+ const rankB = severityRank[getReportSeverity(b).rating] ?? 4;
227
+ return rankA - rankB;
228
+ })
229
+ .map((report) => {
230
+ const { id, attributes: { title, created_at } } = report;
231
+ const { rating } = getReportSeverity(report);
232
+ const openedDate = relativeDate(new Date(created_at));
233
+ const link = `https://hackerone.com/reports/${id}`;
234
+ return {
235
+ name: `[${openedDate}] (${rating}) ${title} - ${link}`,
236
+ value: id,
237
+ checked: true
238
+ };
239
+ });
240
+ return this.cli.promptCheckbox(
241
+ 'Select reports to exclude from the upcoming security release:',
242
+ choices
243
+ );
244
+ }
245
+
187
246
  async buildDescription() {
188
247
  const template = await this.getSecurityIssueTemplate();
189
248
  return template;
190
249
  }
191
250
 
192
- async chooseReports() {
251
+ async chooseReports(excludedReports = []) {
193
252
  this.cli.info('Getting triaged H1 reports...');
194
253
  const reports = await this.req.getTriagedReports();
195
254
  const selectedReports = [];
196
255
 
197
256
  for (const report of reports.data) {
257
+ if (excludedReports.includes(report.id)) continue;
198
258
  const rep = await pickReport(report, { cli: this.cli, req: this.req });
199
259
  if (!rep) continue;
200
260
  selectedReports.push(rep);
@@ -47,6 +47,14 @@ const highwayIgnore = `/third_party/highway/src/*
47
47
  const dragonboxIgnore = `/third_party/dragonbox/src/*
48
48
  !/third_party/dragonbox/src/include`;
49
49
 
50
+ const llvmLibcIgnore = `/third_party/llvm-libc/src/*
51
+ !/third_party/llvm-libc/src/hdr
52
+ !/third_party/llvm-libc/src/include
53
+ !/third_party/llvm-libc/src/shared
54
+ !/third_party/llvm-libc/src/src
55
+ /third_party/llvm-libc/src/src/*
56
+ !/third_party/llvm-libc/src/src/__support`;
57
+
50
58
  export const v8Deps = [
51
59
  {
52
60
  name: 'trace_event',
@@ -146,4 +154,13 @@ export const v8Deps = [
146
154
  },
147
155
  since: 138
148
156
  },
157
+ {
158
+ name: 'llvm-libc',
159
+ repo: 'third_party/llvm-libc/src',
160
+ gitignore: {
161
+ match: '/third_party/llvm-libc/src',
162
+ replace: llvmLibcIgnore
163
+ },
164
+ since: 150
165
+ },
149
166
  ];