@node-core/utils 5.12.1 → 5.13.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.
@@ -1,23 +1,13 @@
1
- import os from 'node:os';
2
1
  import path from 'node:path';
3
2
  import { getMetadata } from '../components/metadata.js';
4
3
 
5
4
  import {
6
- runAsync, runSync, forceRunAsync
5
+ runAsync, runSync
7
6
  } from './run.js';
8
- import { writeFile } from './file.js';
9
- import {
10
- shortSha, getEditor
11
- } from './utils.js';
12
7
  import { getNcuDir } from './config.js';
8
+ import LandingSession, { LINT_RESULTS } from './landing_session.js';
13
9
 
14
- const LINT_RESULTS = {
15
- SKIPPED: 'skipped',
16
- FAILED: 'failed',
17
- SUCCESS: 'success'
18
- };
19
-
20
- export default class CheckPick {
10
+ export default class CherryPick {
21
11
  constructor(prid, dir, cli, {
22
12
  owner,
23
13
  repo,
@@ -46,11 +36,6 @@ export default class CheckPick {
46
36
  return this.options.lint;
47
37
  }
48
38
 
49
- getUpstreamHead() {
50
- const { upstream, branch } = this;
51
- return runSync('git', ['rev-parse', `${upstream}/${branch}`]).trim();
52
- }
53
-
54
39
  getCurrentRev() {
55
40
  return runSync('git', ['rev-parse', 'HEAD']).trim();
56
41
  }
@@ -73,16 +58,6 @@ export default class CheckPick {
73
58
  return path.resolve(this.ncuDir, `${this.prid}`);
74
59
  }
75
60
 
76
- getMessagePath(rev) {
77
- return path.resolve(this.pullDir, `${shortSha(rev)}.COMMIT_EDITMSG`);
78
- }
79
-
80
- saveMessage(rev, message) {
81
- const file = this.getMessagePath(rev);
82
- writeFile(file, message);
83
- return file;
84
- }
85
-
86
61
  async start() {
87
62
  const { cli } = this;
88
63
 
@@ -91,7 +66,7 @@ export default class CheckPick {
91
66
  owner: this.owner,
92
67
  repo: this.repo
93
68
  }, false, cli);
94
- const expectedCommitShas =
69
+ this.expectedCommitShas =
95
70
  metadata.data.commits.map(({ commit }) => commit.oid);
96
71
 
97
72
  const amend = await cli.prompt(
@@ -104,7 +79,7 @@ export default class CheckPick {
104
79
  }
105
80
 
106
81
  try {
107
- const commitInfo = await this.downloadAndPatch(expectedCommitShas);
82
+ const commitInfo = await this.downloadAndPatch();
108
83
  const cleanLint = await this.validateLint();
109
84
  if (cleanLint === LINT_RESULTS.FAILED) {
110
85
  cli.error('Patch still contains lint errors. ' +
@@ -120,68 +95,6 @@ export default class CheckPick {
120
95
  }
121
96
  }
122
97
 
123
- async downloadAndPatch(expectedCommitShas) {
124
- const { cli, repo, owner, prid } = this;
125
-
126
- cli.startSpinner(`Downloading patch for ${prid}`);
127
- // fetch via ssh to handle private repo
128
- await runAsync('git', [
129
- 'fetch', `git@github.com:${owner}/${repo}.git`,
130
- `refs/pull/${prid}/merge`]);
131
- // We fetched the commit that would result if we used `git merge`.
132
- // ^1 and ^2 refer to the PR base and the PR head, respectively.
133
- const [base, head] = await runAsync('git',
134
- ['rev-parse', 'FETCH_HEAD^1', 'FETCH_HEAD^2'],
135
- { captureStdout: 'lines' });
136
- const commitShas = await runAsync('git',
137
- ['rev-list', `${base}..${head}`],
138
- { captureStdout: 'lines' });
139
- cli.stopSpinner(`Fetched commits as ${shortSha(base)}..${shortSha(head)}`);
140
- cli.separator();
141
-
142
- const mismatchedCommits = [
143
- ...commitShas.filter((sha) => !expectedCommitShas.includes(sha))
144
- .map((sha) => `Unexpected commit ${sha}`),
145
- ...expectedCommitShas.filter((sha) => !commitShas.includes(sha))
146
- .map((sha) => `Missing commit ${sha}`)
147
- ].join('\n');
148
- if (mismatchedCommits.length > 0) {
149
- throw new Error(`Mismatched commits:\n${mismatchedCommits}`);
150
- }
151
-
152
- const commitInfo = { base, head, shas: commitShas };
153
-
154
- try {
155
- await forceRunAsync('git', ['cherry-pick', `${base}..${head}`], {
156
- ignoreFailure: false
157
- });
158
- } catch (ex) {
159
- await forceRunAsync('git', ['cherry-pick', '--abort']);
160
- throw new Error('Failed to apply patches');
161
- }
162
-
163
- cli.ok('Patches applied');
164
- return commitInfo;
165
- }
166
-
167
- async validateLint() {
168
- // The linter is currently only run on non-Windows platforms.
169
- if (os.platform() === 'win32') {
170
- return LINT_RESULTS.SKIPPED;
171
- }
172
-
173
- if (!this.lint) {
174
- return LINT_RESULTS.SKIPPED;
175
- }
176
-
177
- try {
178
- await runAsync('make', ['lint']);
179
- return LINT_RESULTS.SUCCESS;
180
- } catch {
181
- return LINT_RESULTS.FAILED;
182
- }
183
- }
184
-
185
98
  async amend(metadata, commitInfo) {
186
99
  const { cli } = this;
187
100
  const subjects = await runAsync('git',
@@ -203,102 +116,23 @@ export default class CheckPick {
203
116
  await runAsync('git', ['commit', '--amend', '--no-edit']);
204
117
  }
205
118
 
206
- return this._amend(metadata);
119
+ return LandingSession.prototype.amend.call(this, metadata);
207
120
  }
208
121
 
209
- async _amend(metadataStr) {
210
- const { cli } = this;
211
-
212
- const rev = this.getCurrentRev();
213
- const original = runSync('git', [
214
- 'show', 'HEAD', '-s', '--format=%B'
215
- ]).trim();
216
- // git has very specific rules about what is a trailer and what is not.
217
- // Instead of trying to implement those ourselves, let git parse the
218
- // original commit message and see if it outputs any trailers.
219
- const originalHasTrailers = runSync('git', [
220
- 'interpret-trailers', '--parse', '--no-divider'
221
- ], {
222
- input: `${original}\n`
223
- }).trim().length !== 0;
224
- const metadata = metadataStr.trim().split('\n');
225
- const amended = original.split('\n');
226
-
227
- // If the original commit message already contains trailers (such as
228
- // "Co-authored-by"), we simply add our own metadata after those. Otherwise,
229
- // we have to add an empty line so that git recognizes our own metadata as
230
- // trailers in the amended commit message.
231
- if (!originalHasTrailers) {
232
- amended.push('');
233
- }
234
-
235
- const BACKPORT_RE = /BACKPORT-PR-URL\s*:\s*(\S+)/i;
236
- const PR_RE = /PR-URL\s*:\s*(\S+)/i;
237
- const REVIEW_RE = /Reviewed-By\s*:\s*(\S+)/i;
238
- const CVE_RE = /CVE-ID\s*:\s*(\S+)/i;
239
-
240
- let containCVETrailer = false;
241
- for (const line of metadata) {
242
- if (line.length !== 0 && original.includes(line)) {
243
- if (line.match(CVE_RE)) {
244
- containCVETrailer = true;
245
- }
246
- if (originalHasTrailers) {
247
- cli.warn(`Found ${line}, skipping..`);
248
- } else {
249
- throw new Error(
250
- 'Git found no trailers in the original commit message, ' +
251
- `but '${line}' is present and should be a trailer.`);
252
- }
253
- } else {
254
- if (line.match(BACKPORT_RE)) {
255
- let prIndex = amended.findIndex(datum => datum.match(PR_RE));
256
- if (prIndex === -1) {
257
- prIndex = amended.findIndex(datum => datum.match(REVIEW_RE)) - 1;
258
- }
259
- amended.splice(prIndex + 1, 0, line);
260
- } else {
261
- amended.push(line);
262
- }
263
- }
264
- }
265
-
266
- if (!containCVETrailer && this.includeCVE) {
267
- const cveID = await cli.prompt(
268
- 'Git found no CVE-ID trailer in the original commit message. ' +
269
- 'Please, provide the CVE-ID',
270
- { questionType: 'input', defaultAnswer: 'CVE-2023-XXXXX' }
271
- );
272
- amended.push('CVE-ID: ' + cveID);
273
- }
122
+ readyToAmend() {
123
+ return true;
124
+ }
274
125
 
275
- const message = amended.join('\n');
276
- const messageFile = this.saveMessage(rev, message);
277
- cli.separator('New Message');
278
- cli.log(message.trim());
279
- const takeMessage = await cli.prompt('Use this message?');
280
- if (takeMessage) {
281
- await runAsync('git', ['commit', '--amend', '-F', messageFile]);
282
- return true;
283
- }
126
+ startAmending() {
127
+ // No-op
128
+ }
284
129
 
285
- const editor = await getEditor({ git: true });
286
- if (editor) {
287
- try {
288
- await forceRunAsync(
289
- editor,
290
- [`"${messageFile}"`],
291
- { ignoreFailure: false, spawnArgs: { shell: true } }
292
- );
293
- await runAsync('git', ['commit', '--amend', '-F', messageFile]);
294
- return true;
295
- } catch {
296
- cli.warn(`Please manually edit ${messageFile}, then run\n` +
297
- `\`git commit --amend -F ${messageFile}\` ` +
298
- 'to finish amending the message');
299
- throw new Error(
300
- 'Failed to edit the message using the configured editor');
301
- }
302
- }
130
+ saveCommitInfo() {
131
+ // No-op
303
132
  }
304
133
  }
134
+
135
+ CherryPick.prototype.downloadAndPatch = LandingSession.prototype.downloadAndPatch;
136
+ CherryPick.prototype.validateLint = LandingSession.prototype.validateLint;
137
+ CherryPick.prototype.getMessagePath = LandingSession.prototype.getMessagePath;
138
+ CherryPick.prototype.saveMessage = LandingSession.prototype.saveMessage;
package/lib/ci/run_ci.js CHANGED
@@ -26,7 +26,7 @@ export class RunPRJob {
26
26
  this.prData = new PRData({ prid, owner, repo }, cli, request);
27
27
  this.certifySafe =
28
28
  certifySafe ||
29
- Promise.all([this.prData.getReviews(), this.prData.getPR()]).then(() =>
29
+ Promise.all([this.prData.getReviews(), this.prData.getCommits()]).then(() =>
30
30
  (this.certifySafe = new PRChecker(cli, this.prData, request, {}).getApprovedTipOfHead())
31
31
  );
32
32
  }
@@ -10,7 +10,7 @@ import {
10
10
 
11
11
  const isWindows = process.platform === 'win32';
12
12
 
13
- const LINT_RESULTS = {
13
+ export const LINT_RESULTS = {
14
14
  SKIPPED: 'skipped',
15
15
  FAILED: 'failed',
16
16
  SUCCESS: 'success'
@@ -84,11 +84,11 @@ export default class LandingSession extends Session {
84
84
  }
85
85
 
86
86
  async downloadAndPatch() {
87
- const { cli, repo, owner, prid, expectedCommitShas } = this;
87
+ const { cli, upstream, prid, expectedCommitShas } = this;
88
88
 
89
89
  cli.startSpinner(`Downloading patch for ${prid}`);
90
90
  await runAsync('git', [
91
- 'fetch', `https://github.com/${owner}/${repo}.git`,
91
+ 'fetch', upstream,
92
92
  `refs/pull/${prid}/merge`]);
93
93
  // We fetched the commit that would result if we used `git merge`.
94
94
  // ^1 and ^2 refer to the PR base and the PR head, respectively.
@@ -315,9 +315,14 @@ export default class LandingSession extends Session {
315
315
  const BACKPORT_RE = /BACKPORT-PR-URL\s*:\s*(\S+)/i;
316
316
  const PR_RE = /PR-URL\s*:\s*(\S+)/i;
317
317
  const REVIEW_RE = /Reviewed-By\s*:\s*(\S+)/i;
318
+ const CVE_RE = /CVE-ID\s*:\s*(\S+)/i;
318
319
 
320
+ let containCVETrailer = false;
319
321
  for (const line of metadata) {
320
322
  if (line.length !== 0 && original.includes(line)) {
323
+ if (line.match(CVE_RE)) {
324
+ containCVETrailer = true;
325
+ }
321
326
  if (originalHasTrailers) {
322
327
  cli.warn(`Found ${line}, skipping..`);
323
328
  } else {
@@ -338,6 +343,15 @@ export default class LandingSession extends Session {
338
343
  }
339
344
  }
340
345
 
346
+ if (!containCVETrailer && this.includeCVE) {
347
+ const cveID = await cli.prompt(
348
+ 'Git found no CVE-ID trailer in the original commit message. ' +
349
+ 'Please, provide the CVE-ID',
350
+ { questionType: 'input', defaultAnswer: 'CVE-2023-XXXXX' }
351
+ );
352
+ amended.push('CVE-ID: ' + cveID);
353
+ }
354
+
341
355
  const message = amended.join('\n');
342
356
  const messageFile = this.saveMessage(rev, message);
343
357
  cli.separator('New Message');
package/lib/pr_checker.js CHANGED
@@ -30,15 +30,6 @@ const FAST_TRACK_RE = /^Fast-track has been requested by @(.+?)\. Please 👍 to
30
30
  const FAST_TRACK_MIN_APPROVALS = 2;
31
31
  const GIT_CONFIG_GUIDE_URL = 'https://github.com/nodejs/node/blob/99b1ada/doc/guides/contributing/pull-requests.md#step-1-fork';
32
32
 
33
- // eslint-disable-next-line no-extend-native
34
- Array.prototype.findLastIndex ??= function findLastIndex(fn) {
35
- const reversedIndex = Reflect.apply(
36
- Array.prototype.findIndex,
37
- this.slice().reverse(),
38
- arguments);
39
- return reversedIndex === -1 ? -1 : this.length - reversedIndex - 1;
40
- };
41
-
42
33
  export default class PRChecker {
43
34
  /**
44
35
  * @param {{}} cli
@@ -49,7 +40,7 @@ export default class PRChecker {
49
40
  this.request = request;
50
41
  this.data = data;
51
42
  const {
52
- pr, reviewers, comments, reviews, commits, collaborators
43
+ pr, reviewers, comments, reviews, commits
53
44
  } = data;
54
45
  this.reviewers = reviewers;
55
46
  this.pr = pr;
@@ -61,9 +52,6 @@ export default class PRChecker {
61
52
  this.reviews = reviews;
62
53
  this.commits = commits;
63
54
  this.argv = argv;
64
- this.collaboratorEmails = new Set(
65
- Array.from(collaborators).map((c) => c[1].email)
66
- );
67
55
  }
68
56
 
69
57
  get waitTimeSingleApproval() {
@@ -531,7 +519,7 @@ export default class PRChecker {
531
519
  const { maxCommits } = argv;
532
520
 
533
521
  if (commits.length === 0) {
534
- cli.warn('No commits found');
522
+ cli.warn('No commits detected');
535
523
  return false;
536
524
  }
537
525
 
package/lib/session.js CHANGED
@@ -347,7 +347,8 @@ export default class Session {
347
347
  const { cli, upstream, branch } = this;
348
348
  const branchName = `${upstream}/${branch}`;
349
349
  cli.startSpinner(`Bringing ${branchName} up to date...`);
350
- await runAsync('git', ['fetch', upstream, branch]);
350
+ const maybeUnshallow = fs.existsSync('.git/shallow') ? ['--unshallow'] : [];
351
+ await runAsync('git', ['fetch', ...maybeUnshallow, upstream, branch]);
351
352
  cli.stopSpinner(`${branchName} is now up-to-date`);
352
353
  const stray = this.getStrayCommits(true);
353
354
  if (!stray.length) {
@@ -44,6 +44,9 @@ const fastFloatReplace = `/third_party/fast_float/src/*
44
44
  const highwayIgnore = `/third_party/highway/src/*
45
45
  !/third_party/highway/src/hwy`;
46
46
 
47
+ const dragonboxIgnore = `/third_party/dragonbox/src/*
48
+ !/third_party/dragonbox/src/include`;
49
+
47
50
  export const v8Deps = [
48
51
  {
49
52
  name: 'trace_event',
@@ -133,5 +136,14 @@ export const v8Deps = [
133
136
  repo: 'third_party/simdutf',
134
137
  gitignore: '!/third_party/simdutf',
135
138
  since: 134
136
- }
139
+ },
140
+ {
141
+ name: 'dragonbox',
142
+ repo: 'third_party/dragonbox/src',
143
+ gitignore: {
144
+ match: '/third_party/dragonbox/src',
145
+ replace: dragonboxIgnore
146
+ },
147
+ since: 138
148
+ },
137
149
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-core/utils",
3
- "version": "5.12.1",
3
+ "version": "5.13.0",
4
4
  "description": "Utilities for Node.js core collaborators",
5
5
  "type": "module",
6
6
  "engines": {
@@ -34,40 +34,40 @@
34
34
  ],
35
35
  "license": "MIT",
36
36
  "dependencies": {
37
- "@inquirer/prompts": "^7.2.3",
37
+ "@inquirer/prompts": "^7.4.1",
38
38
  "@listr2/prompt-adapter-enquirer": "^2.0.12",
39
39
  "@node-core/caritat": "^1.6.0",
40
40
  "@pkgjs/nv": "^0.2.2",
41
41
  "branch-diff": "^3.1.1",
42
42
  "chalk": "^5.4.1",
43
- "changelog-maker": "^4.3.2",
43
+ "changelog-maker": "^4.4.1",
44
44
  "cheerio": "^1.0.0",
45
45
  "clipboardy": "^4.0.0",
46
46
  "core-validate-commit": "^4.1.0",
47
47
  "figures": "^6.1.0",
48
- "ghauth": "^6.0.10",
48
+ "ghauth": "^6.0.12",
49
49
  "git-secure-tag": "^2.3.1",
50
50
  "js-yaml": "^4.1.0",
51
51
  "listr2": "^8.2.5",
52
52
  "lodash": "^4.17.21",
53
53
  "log-symbols": "^7.0.0",
54
- "ora": "^8.1.1",
54
+ "ora": "^8.2.0",
55
55
  "replace-in-file": "^8.3.0",
56
- "semver": "^7.6.3",
57
- "undici": "^7.3.0",
56
+ "semver": "^7.7.1",
57
+ "undici": "^7.7.0",
58
58
  "which": "^5.0.0",
59
59
  "yargs": "^17.7.2"
60
60
  },
61
61
  "devDependencies": {
62
- "@eslint/js": "^9.19.0",
62
+ "@eslint/js": "^9.24.0",
63
63
  "@reporters/github": "^1.7.2",
64
64
  "c8": "^10.1.3",
65
- "eslint": "^9.19.0",
65
+ "eslint": "^9.24.0",
66
66
  "eslint-plugin-import": "^2.31.0",
67
- "eslint-plugin-n": "^17.15.1",
67
+ "eslint-plugin-n": "^17.17.0",
68
68
  "eslint-plugin-promise": "^7.2.1",
69
- "globals": "^15.14.0",
70
- "neostandard": "^0.12.0",
71
- "sinon": "^19.0.2"
69
+ "globals": "^16.0.0",
70
+ "neostandard": "^0.12.1",
71
+ "sinon": "^20.0.0"
72
72
  }
73
73
  }