@node-core/utils 6.0.0 → 6.1.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/components/git/release.js +8 -2
- package/lib/cherry_pick.js +12 -2
- package/lib/landing_session.js +33 -6
- package/lib/prepare_release.js +52 -3
- package/lib/update_security_release.js +14 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -50,8 +50,14 @@ const releaseOptions = {
|
|
|
50
50
|
type: 'boolean'
|
|
51
51
|
},
|
|
52
52
|
security: {
|
|
53
|
-
describe: 'Demarcate the new security release as a security release'
|
|
54
|
-
|
|
53
|
+
describe: 'Demarcate the new security release as a security release. ' +
|
|
54
|
+
'Optionally provide path to security-release repository for CVE auto-population',
|
|
55
|
+
type: 'string',
|
|
56
|
+
coerce: (arg) => {
|
|
57
|
+
// If --security=path is used, return the path
|
|
58
|
+
if (arg === '' || arg === true) return true;
|
|
59
|
+
return arg;
|
|
60
|
+
}
|
|
55
61
|
},
|
|
56
62
|
skipBranchDiff: {
|
|
57
63
|
describe: 'Skips the initial branch-diff check when preparing releases',
|
package/lib/cherry_pick.js
CHANGED
|
@@ -14,20 +14,30 @@ export default class CherryPick {
|
|
|
14
14
|
upstream,
|
|
15
15
|
gpgSign,
|
|
16
16
|
lint,
|
|
17
|
-
includeCVE
|
|
17
|
+
includeCVE,
|
|
18
|
+
cveIds,
|
|
19
|
+
vulnCveMap
|
|
18
20
|
} = {}) {
|
|
19
21
|
this.prid = prid;
|
|
20
22
|
this.cli = cli;
|
|
21
23
|
this.dir = dir;
|
|
22
24
|
this.upstream = upstream;
|
|
23
25
|
this.gpgSign = gpgSign;
|
|
24
|
-
this.options = { owner, repo, lint, includeCVE };
|
|
26
|
+
this.options = { owner, repo, lint, includeCVE, cveIds, vulnCveMap };
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
get includeCVE() {
|
|
28
30
|
return this.options.includeCVE ?? false;
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
get cveIds() {
|
|
34
|
+
return this.options.cveIds ?? null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get vulnCveMap() {
|
|
38
|
+
return this.options.vulnCveMap ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
get owner() {
|
|
32
42
|
return this.options.owner || 'nodejs';
|
|
33
43
|
}
|
package/lib/landing_session.js
CHANGED
|
@@ -345,12 +345,39 @@ export default class LandingSession extends Session {
|
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
if (!containCVETrailer && this.includeCVE) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
'
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
348
|
+
let cveID;
|
|
349
|
+
if (this.cveIds && this.cveIds.length > 0) {
|
|
350
|
+
cveID = this.cveIds.join(', ');
|
|
351
|
+
cli.ok(`Using CVE-ID from vulnerabilities.json: ${cveID}`);
|
|
352
|
+
} else {
|
|
353
|
+
// Fallback: check if the original commit has a PR-URL trailer
|
|
354
|
+
// and use it to look up CVE-IDs from the vulnerabilities map
|
|
355
|
+
if (this.vulnCveMap) {
|
|
356
|
+
const prUrlMatch = original.match(PR_RE);
|
|
357
|
+
if (prUrlMatch) {
|
|
358
|
+
const prUrl = prUrlMatch[1];
|
|
359
|
+
const cveIds = this.vulnCveMap.get(prUrl);
|
|
360
|
+
if (cveIds && cveIds.length > 0) {
|
|
361
|
+
cveID = cveIds.join(', ');
|
|
362
|
+
cli.ok(`Using CVE-ID from backport PR-URL (${prUrl}): ${cveID}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Fall back to prompt if still not found
|
|
368
|
+
if (!cveID) {
|
|
369
|
+
cveID = await cli.prompt(
|
|
370
|
+
'Git found no CVE-ID trailer in the original commit message. ' +
|
|
371
|
+
'Please, provide the CVE-ID or leave it empty',
|
|
372
|
+
{ questionType: 'input', defaultAnswer: 'CVE-2026-XXXXX' }
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Some commits might not address a vulnerability, but it is necessary
|
|
377
|
+
// for the security release to happen.
|
|
378
|
+
if (cveID !== '') {
|
|
379
|
+
amended.push('CVE-ID: ' + cveID);
|
|
380
|
+
}
|
|
354
381
|
}
|
|
355
382
|
|
|
356
383
|
const message = amended.join('\n');
|
package/lib/prepare_release.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { promises as fs, existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
|
|
4
4
|
import semver from 'semver';
|
|
5
5
|
import { replaceInFile } from 'replace-in-file';
|
|
@@ -21,7 +21,11 @@ const isWindows = process.platform === 'win32';
|
|
|
21
21
|
export default class ReleasePreparation extends Session {
|
|
22
22
|
constructor(argv, cli, dir) {
|
|
23
23
|
super(cli, dir);
|
|
24
|
-
|
|
24
|
+
// argv.security can be either:
|
|
25
|
+
// - true (boolean) if --security was used without parameter
|
|
26
|
+
// - string if --security=path was used
|
|
27
|
+
this.isSecurityRelease = !!argv.security;
|
|
28
|
+
this.securityReleaseRepo = typeof argv.security === 'string' ? argv.security : null;
|
|
25
29
|
this.isLTS = false;
|
|
26
30
|
this.isLTSTransition = argv.startLTS;
|
|
27
31
|
this.runBranchDiff = !argv.skipBranchDiff;
|
|
@@ -63,17 +67,62 @@ export default class ReleasePreparation extends Session {
|
|
|
63
67
|
return false;
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
const vulnCveMap = new Map();
|
|
71
|
+
if (this.isSecurityRelease && this.securityReleaseRepo) {
|
|
72
|
+
const vulnPath = path.join(
|
|
73
|
+
this.securityReleaseRepo,
|
|
74
|
+
'security-release',
|
|
75
|
+
'next-security-release',
|
|
76
|
+
'vulnerabilities.json'
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (!existsSync(vulnPath)) {
|
|
80
|
+
cli.error(`vulnerabilities.json not found at ${vulnPath}. ` +
|
|
81
|
+
'Skipping CVE auto-population.');
|
|
82
|
+
cli.warn('PRs will require manual CVE-ID entry.');
|
|
83
|
+
} else {
|
|
84
|
+
try {
|
|
85
|
+
cli.startSpinner(`Reading vulnerabilities.json from ${vulnPath}..`);
|
|
86
|
+
const vulnData = JSON.parse(readFileSync(vulnPath, 'utf-8'));
|
|
87
|
+
cli.stopSpinner(`Done reading vulnerabilities.json from ${vulnPath}`);
|
|
88
|
+
|
|
89
|
+
if (vulnData.reports && Array.isArray(vulnData.reports)) {
|
|
90
|
+
vulnData.reports.forEach(report => {
|
|
91
|
+
if (report.prURL && report.cveIds && report.cveIds.length > 0) {
|
|
92
|
+
vulnCveMap.set(report.prURL, report.cveIds);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
cli.ok(`Loaded ${vulnCveMap.size} CVE mappings from vulnerabilities.json`);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
cli.error(`Failed to read vulnerabilities.json: ${err.message}`);
|
|
99
|
+
cli.warn('Continuing without CVE auto-population.');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
66
104
|
for (const pr of prs) {
|
|
67
105
|
if (pr.mergeable !== 'MERGEABLE') {
|
|
68
106
|
this.warnForNonMergeablePR(pr);
|
|
69
107
|
}
|
|
108
|
+
|
|
109
|
+
// Look up CVE-IDs from vulnerabilities.json
|
|
110
|
+
const prUrl = `https://github.com/${this.owner}/${this.repo}/pull/${pr.number}`;
|
|
111
|
+
const cveIds = vulnCveMap.get(prUrl);
|
|
112
|
+
|
|
113
|
+
if (!cveIds || cveIds.length === 0) {
|
|
114
|
+
cli.warn(`No CVE-IDs found in vulnerabilities.json for ${prUrl}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
70
117
|
const cp = new CherryPick(pr.number, this.dir, cli, {
|
|
71
118
|
owner: this.owner,
|
|
72
119
|
repo: this.repo,
|
|
73
120
|
gpgSign: this.gpgSign,
|
|
74
121
|
upstream: this.isSecurityRelease ? `https://${this.username}:${this.config.token}@github.com/${this.owner}/${this.repo}.git` : this.upstream,
|
|
75
122
|
lint: false,
|
|
76
|
-
includeCVE: true
|
|
123
|
+
includeCVE: true,
|
|
124
|
+
cveIds: cveIds || null,
|
|
125
|
+
vulnCveMap
|
|
77
126
|
});
|
|
78
127
|
const success = await cp.start();
|
|
79
128
|
if (!success) {
|
|
@@ -12,6 +12,7 @@ import fs from 'node:fs';
|
|
|
12
12
|
import auth from './auth.js';
|
|
13
13
|
import Request from './request.js';
|
|
14
14
|
import nv from '@pkgjs/nv';
|
|
15
|
+
import semver from 'semver';
|
|
15
16
|
|
|
16
17
|
export default class UpdateSecurityRelease extends SecurityRelease {
|
|
17
18
|
async sync() {
|
|
@@ -268,17 +269,26 @@ Summary: ${summary}\n`,
|
|
|
268
269
|
async calculateVersions(affectedVersions, supportedVersions) {
|
|
269
270
|
const h1AffectedVersions = [];
|
|
270
271
|
const patchedVersions = [];
|
|
272
|
+
let isPatchRelease = true;
|
|
271
273
|
for (const affectedVersion of affectedVersions) {
|
|
272
|
-
const
|
|
273
|
-
const latest = supportedVersions.find((v) => v.major === Number(
|
|
274
|
+
const affectedMajor = affectedVersion.split('.')[0];
|
|
275
|
+
const latest = supportedVersions.find((v) => v.major === Number(affectedMajor)).version;
|
|
274
276
|
const version = await this.cli.prompt(
|
|
275
277
|
`What is the affected version (<=) for release line ${affectedVersion}?`,
|
|
276
278
|
{ questionType: 'input', defaultAnswer: latest });
|
|
277
279
|
|
|
278
|
-
const nextPatchVersion =
|
|
280
|
+
const nextPatchVersion = semver.inc(version, 'patch');
|
|
281
|
+
const nextMinorVersion = semver.inc(version, 'minor');
|
|
279
282
|
const patchedVersion = await this.cli.prompt(
|
|
280
283
|
`What is the patched version (>=) for release line ${affectedVersion}?`,
|
|
281
|
-
{
|
|
284
|
+
{
|
|
285
|
+
questionType: 'input',
|
|
286
|
+
defaultAnswer: isPatchRelease ? nextPatchVersion : nextMinorVersion
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (patchedVersion !== nextPatchVersion) {
|
|
290
|
+
isPatchRelease = false; // is a minor release
|
|
291
|
+
}
|
|
282
292
|
|
|
283
293
|
patchedVersions.push(patchedVersion);
|
|
284
294
|
h1AffectedVersions.push({
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node-core/utils",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@node-core/utils",
|
|
9
|
-
"version": "6.
|
|
9
|
+
"version": "6.1.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@inquirer/prompts": "^7.4.1",
|