@node-core/utils 6.4.0 → 7.0.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.
Files changed (56) hide show
  1. package/README.md +2 -5
  2. package/bin/ncu-ci.js +3 -19
  3. package/components/git/benchmark.js +112 -0
  4. package/components/git/land.js +3 -3
  5. package/components/git/metadata.js +34 -1
  6. package/components/git/release.js +3 -5
  7. package/components/git/security.js +258 -27
  8. package/components/git/v8.js +6 -3
  9. package/components/metadata.js +75 -7
  10. package/lib/auth.js +3 -4
  11. package/lib/backport_session.js +3 -3
  12. package/lib/benchmark.js +243 -0
  13. package/lib/cherry_pick.js +32 -11
  14. package/lib/ci/failure_aggregator.js +16 -20
  15. package/lib/ci/run_ci.js +1 -1
  16. package/lib/cli.js +25 -1
  17. package/lib/config.js +7 -7
  18. package/lib/file.js +5 -5
  19. package/lib/landing_session.js +65 -24
  20. package/lib/links.js +4 -4
  21. package/lib/pr_checker.js +163 -31
  22. package/lib/pr_data.js +1 -1
  23. package/lib/pr_summary.js +1 -1
  24. package/lib/prepare_release.js +270 -85
  25. package/lib/prepare_security.js +312 -46
  26. package/lib/promote_release.js +19 -16
  27. package/lib/queries/ListSecurityReleasePRs.gql +19 -0
  28. package/lib/queries/PR.gql +4 -1
  29. package/lib/request.js +41 -0
  30. package/lib/run.js +5 -5
  31. package/lib/security-announcement.js +10 -5
  32. package/lib/security-release/security-release.js +160 -20
  33. package/lib/security_blog.js +130 -65
  34. package/lib/session.js +4 -3
  35. package/lib/team_info.js +1 -1
  36. package/lib/update-v8/applyNodeChanges.js +1 -3
  37. package/lib/update-v8/backport.js +6 -6
  38. package/lib/update-v8/commitUpdate.js +3 -6
  39. package/lib/update-v8/common.js +30 -6
  40. package/lib/update-v8/constants.js +5 -0
  41. package/lib/update-v8/deps.js +3 -5
  42. package/lib/update-v8/index.js +14 -5
  43. package/lib/update-v8/majorUpdate.js +5 -4
  44. package/lib/update-v8/minorUpdate.js +4 -4
  45. package/lib/update-v8/updateCrates.js +192 -0
  46. package/lib/update-v8/updateV8Clone.js +1 -1
  47. package/lib/update-v8/updateVersionNumbers.js +3 -6
  48. package/lib/update-v8/util.js +3 -3
  49. package/lib/update_security_release.js +347 -105
  50. package/lib/utils.js +5 -5
  51. package/lib/verbosity.js +3 -3
  52. package/lib/wpt/index.js +1 -23
  53. package/package.json +18 -22
  54. package/bin/get-metadata.js +0 -11
  55. package/lib/wpt/templates/README.md +0 -16
  56. package/npm-shrinkwrap.json +0 -20380
package/lib/run.js CHANGED
@@ -77,7 +77,7 @@ export function forceRunAsync(cmd, args, options) {
77
77
  throw error;
78
78
  }
79
79
  });
80
- };
80
+ }
81
81
 
82
82
  export function runPromise(promise) {
83
83
  return promise.catch((error) => {
@@ -86,11 +86,11 @@ export function runPromise(promise) {
86
86
  }
87
87
  exit();
88
88
  });
89
- };
89
+ }
90
90
 
91
91
  export function runAsync(cmd, args, options) {
92
92
  return runPromise(runAsyncBase(cmd, args, options));
93
- };
93
+ }
94
94
 
95
95
  export function runSync(cmd, args, options) {
96
96
  if (cmd instanceof URL) {
@@ -111,8 +111,8 @@ export function runSync(cmd, args, options) {
111
111
  } else {
112
112
  return child.stdout.toString();
113
113
  }
114
- };
114
+ }
115
115
 
116
116
  export function exit() {
117
117
  process.exit(1);
118
- };
118
+ }
@@ -1,4 +1,3 @@
1
- import fs from 'node:fs';
2
1
  import {
3
2
  NEXT_SECURITY_RELEASE_REPOSITORY,
4
3
  checkoutOnSecurityReleaseBranch,
@@ -7,7 +6,8 @@ import {
7
6
  validateDate,
8
7
  formatDateToYYYYMMDD,
9
8
  commitAndPushVulnerabilitiesJSON,
10
- createIssue
9
+ createIssue,
10
+ writeSecurityFile
11
11
  } from './security-release/security-release.js';
12
12
  import auth from './auth.js';
13
13
  import Request from './request.js';
@@ -30,7 +30,7 @@ export default class SecurityAnnouncement {
30
30
  this.req = new Request(credentials);
31
31
 
32
32
  // checkout on security release branch
33
- checkoutOnSecurityReleaseBranch(cli, this.repository);
33
+ await checkoutOnSecurityReleaseBranch(cli, this.repository);
34
34
  // read vulnerabilities JSON file
35
35
  const content = getVulnerabilitiesJSON(cli);
36
36
  // validate the release date read from vulnerabilities JSON
@@ -52,9 +52,14 @@ export default class SecurityAnnouncement {
52
52
  content.dockerIssue = dockerIssue;
53
53
 
54
54
  const vulnerabilitiesJSONPath = getVulnerabilitiesJSONPath();
55
- fs.writeFileSync(vulnerabilitiesJSONPath, JSON.stringify(content, null, 2));
55
+ await writeSecurityFile(
56
+ cli,
57
+ vulnerabilitiesJSONPath,
58
+ JSON.stringify(content, null, 2),
59
+ 'This records the build and docker tracking issue links in vulnerabilities.json.'
60
+ );
56
61
  const commitMessage = 'chore: add build and docker issue link';
57
- commitAndPushVulnerabilitiesJSON([vulnerabilitiesJSONPath],
62
+ await commitAndPushVulnerabilitiesJSON([vulnerabilitiesJSONPath],
58
63
  commitMessage, { cli: this.cli, repository: this.repository });
59
64
 
60
65
  this.cli.ok('Added docker and build issue in vulnerabilities.json');
@@ -11,6 +11,10 @@ export const NEXT_SECURITY_RELEASE_REPOSITORY = {
11
11
  repo: 'security-release'
12
12
  };
13
13
 
14
+ export const SEVERITY_RANKS = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];
15
+ const RELEASE_LINE_RE = /^v?(\d+)(?:\.x)?$/;
16
+ const SEMVER_RE = /^v?(\d+)\.\d+\.\d+/;
17
+
14
18
  export const PLACEHOLDERS = {
15
19
  releaseDate: '%RELEASE_DATE%',
16
20
  vulnerabilitiesPRURL: '%VULNERABILITIES_PR_URL%',
@@ -28,7 +32,34 @@ export const PLACEHOLDERS = {
28
32
  downloads: '%DOWNLOADS%'
29
33
  };
30
34
 
31
- export function checkRemote(cli, repository) {
35
+ function formatCommand(command, args) {
36
+ return [command, ...args].join(' ');
37
+ }
38
+
39
+ export async function confirmSecurityStep(cli, action, detail) {
40
+ let message = `Allow action: ${action}?`;
41
+ if (detail) {
42
+ message += `\n\n${detail}`;
43
+ }
44
+
45
+ const allowed = await cli.prompt(message, { defaultAnswer: false });
46
+ if (!allowed) {
47
+ throw new Error(`Aborted: ${action}.`);
48
+ }
49
+ }
50
+
51
+ export async function runSecurityGitCommand(cli, args, detail) {
52
+ const command = formatCommand('git', args);
53
+ await confirmSecurityStep(cli, `run \`${command}\``, detail);
54
+ return runSync('git', args);
55
+ }
56
+
57
+ export async function writeSecurityFile(cli, filePath, content, detail) {
58
+ await confirmSecurityStep(cli, `write \`${filePath}\``, detail);
59
+ return fs.writeFileSync(filePath, content);
60
+ }
61
+
62
+ export async function checkRemote(cli, repository) {
32
63
  const remote = runSync('git', ['ls-remote', '--get-url', 'origin']).trim();
33
64
  const { owner, repo } = repository;
34
65
  const securityReleaseOrigin = [
@@ -42,23 +73,31 @@ export function checkRemote(cli, repository) {
42
73
  }
43
74
  }
44
75
 
45
- export function checkoutOnSecurityReleaseBranch(cli, repository) {
46
- checkRemote(cli, repository);
76
+ export async function checkoutOnSecurityReleaseBranch(cli, repository) {
77
+ await checkRemote(cli, repository);
47
78
  const currentBranch = runSync('git', ['branch', '--show-current']).trim();
48
79
  cli.info(`Current branch: ${currentBranch} `);
49
80
 
50
81
  if (currentBranch !== NEXT_SECURITY_RELEASE_BRANCH) {
51
- runSync('git', ['checkout', '-B', NEXT_SECURITY_RELEASE_BRANCH]);
82
+ await runSecurityGitCommand(
83
+ cli,
84
+ ['checkout', '-B', NEXT_SECURITY_RELEASE_BRANCH],
85
+ `This checks out or recreates the ${NEXT_SECURITY_RELEASE_BRANCH} branch locally.`
86
+ );
52
87
  cli.ok(`Checkout on branch: ${NEXT_SECURITY_RELEASE_BRANCH} `);
53
- };
88
+ }
54
89
  }
55
90
 
56
- export function commitAndPushVulnerabilitiesJSON(filePath, commitMessage, { cli, repository }) {
57
- checkRemote(cli, repository);
91
+ export async function commitAndPushVulnerabilitiesJSON(
92
+ filePath,
93
+ commitMessage,
94
+ { cli, repository }
95
+ ) {
96
+ await checkRemote(cli, repository);
58
97
 
59
98
  if (Array.isArray(filePath)) {
60
- for (const path of filePath) {
61
- runSync('git', ['add', path]);
99
+ for (const currentPath of filePath) {
100
+ runSync('git', ['add', currentPath]);
62
101
  }
63
102
  } else {
64
103
  runSync('git', ['add', filePath]);
@@ -70,15 +109,31 @@ export function commitAndPushVulnerabilitiesJSON(filePath, commitMessage, { cli,
70
109
  return;
71
110
  }
72
111
 
73
- runSync('git', ['commit', '-m', commitMessage]);
112
+ await runSecurityGitCommand(
113
+ cli,
114
+ ['commit', '-m', commitMessage],
115
+ `This creates a local commit with message: ${commitMessage}`
116
+ );
74
117
 
75
118
  try {
76
- runSync('git', ['push', '-u', 'origin', NEXT_SECURITY_RELEASE_BRANCH]);
77
- } catch (error) {
119
+ await runSecurityGitCommand(
120
+ cli,
121
+ ['push', '-u', 'origin', NEXT_SECURITY_RELEASE_BRANCH],
122
+ `This pushes the security release branch to origin/${NEXT_SECURITY_RELEASE_BRANCH}.`
123
+ );
124
+ } catch {
78
125
  cli.warn('Rebasing...');
79
126
  // try to pull rebase and push again
80
- runSync('git', ['pull', 'origin', NEXT_SECURITY_RELEASE_BRANCH, '--rebase']);
81
- runSync('git', ['push', '-u', 'origin', NEXT_SECURITY_RELEASE_BRANCH]);
127
+ await runSecurityGitCommand(
128
+ cli,
129
+ ['pull', 'origin', NEXT_SECURITY_RELEASE_BRANCH, '--rebase'],
130
+ `This rebases local changes on origin/${NEXT_SECURITY_RELEASE_BRANCH}.`
131
+ );
132
+ await runSecurityGitCommand(
133
+ cli,
134
+ ['push', '-u', 'origin', NEXT_SECURITY_RELEASE_BRANCH],
135
+ `This retries pushing the security release branch to origin/${NEXT_SECURITY_RELEASE_BRANCH}.`
136
+ );
82
137
  }
83
138
  cli.ok(`Pushed commit: ${commitMessage} to ${NEXT_SECURITY_RELEASE_BRANCH}`);
84
139
  }
@@ -130,6 +185,74 @@ export function formatDateToYYYYMMDD(date) {
130
185
  return `${year}/${month}/${day}`;
131
186
  }
132
187
 
188
+ export function getHighestSeverityAnnouncement(reports, releaseLine = 'this release') {
189
+ const highestSeverityIndex = Math.max(...reports.map(
190
+ r => SEVERITY_RANKS.indexOf(r.severity.rating.toUpperCase())
191
+ ));
192
+
193
+ return `The highest severity issue fixed in ${releaseLine} is ${
194
+ SEVERITY_RANKS[highestSeverityIndex] ?? 'NONE'
195
+ }.`;
196
+ }
197
+
198
+ function normalizeReleaseLine(version) {
199
+ if (typeof version !== 'string') return;
200
+
201
+ const trimmed = version.trim();
202
+ let match = RELEASE_LINE_RE.exec(trimmed);
203
+ if (match) return `${match[1]}.x`;
204
+
205
+ match = SEMVER_RE.exec(trimmed);
206
+ if (match) return `${match[1]}.x`;
207
+ }
208
+
209
+ function uniqueReleaseLines(releaseLines) {
210
+ return [...new Set(releaseLines.filter(Boolean))];
211
+ }
212
+
213
+ export function getAffectedVersionLines(affectedVersions) {
214
+ if (!affectedVersions) return [];
215
+
216
+ if (typeof affectedVersions === 'string') {
217
+ return uniqueReleaseLines(affectedVersions.split(',').map(normalizeReleaseLine));
218
+ }
219
+
220
+ if (Array.isArray(affectedVersions)) {
221
+ return uniqueReleaseLines(affectedVersions.flatMap((version) => {
222
+ if (typeof version === 'string') {
223
+ return normalizeReleaseLine(version);
224
+ }
225
+
226
+ if (version && typeof version === 'object') {
227
+ return normalizeReleaseLine(version.version);
228
+ }
229
+
230
+ return [];
231
+ }));
232
+ }
233
+
234
+ if (typeof affectedVersions === 'object') {
235
+ const releaseLines = Object.keys(affectedVersions).map(normalizeReleaseLine);
236
+ if (releaseLines.some(Boolean)) {
237
+ return uniqueReleaseLines(releaseLines);
238
+ }
239
+
240
+ return uniqueReleaseLines(
241
+ Object.values(affectedVersions).flatMap(getAffectedVersionLines)
242
+ );
243
+ }
244
+
245
+ return [];
246
+ }
247
+
248
+ // `dependencies` maps a dependency name to its update, or to a list of updates
249
+ // when the release lines got different ones. Flatten it into a list of updates
250
+ // that each know their name.
251
+ export function getDependencyUpdates(dependencies) {
252
+ return Object.entries(dependencies ?? {}).flatMap(([name, update]) =>
253
+ (Array.isArray(update) ? update : [update]).map((entry) => ({ ...entry, name })));
254
+ }
255
+
133
256
  export function promptDependencies(cli) {
134
257
  return cli.prompt('Enter the link to the dependency update PR (leave empty to exit): ', {
135
258
  defaultAnswer: '',
@@ -138,6 +261,11 @@ export function promptDependencies(cli) {
138
261
  }
139
262
 
140
263
  export async function createIssue(title, content, repository, { cli, req }) {
264
+ await confirmSecurityStep(
265
+ cli,
266
+ `create GitHub issue \`${repository.owner}/${repository.repo}: ${title}\``,
267
+ `This creates an issue in ${repository.owner}/${repository.repo}.`
268
+ );
141
269
  const data = await req.createIssue(title, content, repository);
142
270
  if (data.html_url) {
143
271
  cli.ok(`Created: ${data.html_url}`);
@@ -183,7 +311,7 @@ export async function pickReport(report, { cli, req }) {
183
311
  });
184
312
 
185
313
  let prURL = '';
186
- let patchAuthors = [];
314
+ let patchAuthors;
187
315
  if (custom_field_values.data.length) {
188
316
  prURL = custom_field_values.data[0].attributes.value;
189
317
  const { user } = await req.getPullRequest(prURL);
@@ -240,33 +368,45 @@ export class SecurityRelease {
240
368
  NEXT_SECURITY_RELEASE_FOLDER, 'vulnerabilities.json');
241
369
  }
242
370
 
243
- updateReleaseFolder(releaseDate) {
371
+ async updateReleaseFolder(releaseDate) {
244
372
  const folder = path.join(process.cwd(),
245
373
  NEXT_SECURITY_RELEASE_FOLDER);
246
374
  const newFolder = path.join(process.cwd(), 'security-release', releaseDate);
375
+ await confirmSecurityStep(
376
+ this.cli,
377
+ `rename \`${folder}\` to \`${newFolder}\``,
378
+ 'This moves the next-security-release folder to the dated release folder.'
379
+ );
247
380
  fs.renameSync(folder, newFolder);
248
381
  return newFolder;
249
382
  }
250
383
 
251
- updateVulnerabilitiesJSON(content) {
384
+ async updateVulnerabilitiesJSON(content) {
252
385
  try {
253
386
  const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath();
254
387
  this.cli.startSpinner(`Updating vulnerabilities.json from ${vulnerabilitiesJSONPath}...`);
255
- fs.writeFileSync(vulnerabilitiesJSONPath, JSON.stringify(content, null, 2));
256
- commitAndPushVulnerabilitiesJSON(vulnerabilitiesJSONPath,
388
+ await writeSecurityFile(
389
+ this.cli,
390
+ vulnerabilitiesJSONPath,
391
+ JSON.stringify(content, null, 2),
392
+ 'This updates vulnerabilities.json with the latest security release data.'
393
+ );
394
+ await commitAndPushVulnerabilitiesJSON(vulnerabilitiesJSONPath,
257
395
  'chore: updated vulnerabilities.json',
258
396
  { cli: this.cli, repository: this.repository });
259
397
  this.cli.stopSpinner(`Done updating vulnerabilities.json from ${vulnerabilitiesJSONPath}`);
398
+ return true;
260
399
  } catch (error) {
261
400
  this.cli.error('Error updating vulnerabilities.json');
262
401
  this.cli.error(error);
402
+ return false;
263
403
  }
264
404
  }
265
405
 
266
406
  getAffectedVersions(content) {
267
407
  const affectedVersions = new Set();
268
408
  for (const report of Object.values(content.reports)) {
269
- for (const affectedVersion of report.affectedVersions) {
409
+ for (const affectedVersion of getAffectedVersionLines(report.affectedVersions)) {
270
410
  affectedVersions.add(affectedVersion);
271
411
  }
272
412
  }