@node-core/utils 6.3.3 → 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.
- package/README.md +2 -5
- package/bin/ncu-ci.js +3 -19
- package/components/git/benchmark.js +112 -0
- package/components/git/land.js +3 -3
- package/components/git/metadata.js +34 -1
- package/components/git/release.js +3 -5
- package/components/git/security.js +258 -27
- package/components/git/v8.js +6 -3
- package/components/metadata.js +75 -7
- package/lib/auth.js +3 -4
- package/lib/backport_session.js +3 -3
- package/lib/benchmark.js +243 -0
- package/lib/cherry_pick.js +32 -11
- package/lib/ci/failure_aggregator.js +16 -20
- package/lib/ci/run_ci.js +1 -1
- package/lib/cli.js +33 -1
- package/lib/config.js +7 -7
- package/lib/file.js +5 -5
- package/lib/landing_session.js +65 -24
- package/lib/links.js +4 -4
- package/lib/pr_checker.js +163 -31
- package/lib/pr_data.js +1 -1
- package/lib/pr_summary.js +1 -1
- package/lib/prepare_release.js +270 -85
- package/lib/prepare_security.js +367 -41
- package/lib/promote_release.js +19 -16
- package/lib/queries/ListSecurityReleasePRs.gql +19 -0
- package/lib/queries/PR.gql +4 -1
- package/lib/request.js +41 -0
- package/lib/run.js +5 -5
- package/lib/security-announcement.js +10 -5
- package/lib/security-release/security-release.js +160 -20
- package/lib/security_blog.js +130 -65
- package/lib/session.js +4 -3
- package/lib/team_info.js +1 -1
- package/lib/update-v8/applyNodeChanges.js +1 -3
- package/lib/update-v8/backport.js +6 -6
- package/lib/update-v8/commitUpdate.js +3 -6
- package/lib/update-v8/common.js +30 -6
- package/lib/update-v8/constants.js +22 -0
- package/lib/update-v8/deps.js +3 -5
- package/lib/update-v8/index.js +14 -5
- package/lib/update-v8/majorUpdate.js +5 -4
- package/lib/update-v8/minorUpdate.js +4 -4
- package/lib/update-v8/updateCrates.js +192 -0
- package/lib/update-v8/updateV8Clone.js +1 -1
- package/lib/update-v8/updateVersionNumbers.js +3 -6
- package/lib/update-v8/util.js +3 -3
- package/lib/update_security_release.js +347 -105
- package/lib/utils.js +5 -5
- package/lib/verbosity.js +3 -3
- package/lib/wpt/index.js +1 -23
- package/package.json +18 -22
- package/bin/get-metadata.js +0 -11
- package/lib/wpt/templates/README.md +0 -16
- package/npm-shrinkwrap.json +0 -20380
package/README.md
CHANGED
|
@@ -29,9 +29,6 @@ CLI tools for Node.js Core collaborators.
|
|
|
29
29
|
to use.
|
|
30
30
|
- [`ncu-team`](./docs/ncu-team.md): Listing members of a team, synchronizing
|
|
31
31
|
special blocks in files with the list of members.
|
|
32
|
-
- [`get-metadata`](./docs/get-metadata.md): Retrieving metadata for a Pull Request.
|
|
33
|
-
**DEPRECATED**: use [`git node metadata`](./docs/git-node.md#git-node-metadata)
|
|
34
|
-
instead.
|
|
35
32
|
- [`ncu-ci`](./docs/ncu-ci.md): Parse the results of a Jenkins CI run and display a summary for all the failures.
|
|
36
33
|
|
|
37
34
|
## Usage
|
|
@@ -66,8 +63,8 @@ to create the token.
|
|
|
66
63
|
|
|
67
64
|
When creating the token, the following boxes need to be checked:
|
|
68
65
|
|
|
69
|
-
- `user:email`: Used by `git-node`
|
|
70
|
-
|
|
66
|
+
- `user:email`: Used by `git-node` to read the email of the PR author in order
|
|
67
|
+
to check if it matches the email of the commit author.
|
|
71
68
|
- `read:org`: Used by `ncu-team` to read the list of team members.
|
|
72
69
|
|
|
73
70
|
Optionally, if you want to grant write access so `git-node` can write comments:
|
package/bin/ncu-ci.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import yargs from 'yargs';
|
|
4
|
-
import clipboardy from 'clipboardy';
|
|
5
4
|
|
|
6
5
|
import {
|
|
7
6
|
JobParser,
|
|
@@ -222,11 +221,6 @@ const args = yargs(hideBin(process.argv))
|
|
|
222
221
|
handler
|
|
223
222
|
})
|
|
224
223
|
.demandCommand(1, 'must provide a valid command')
|
|
225
|
-
.option('copy', {
|
|
226
|
-
describe: 'Write the results as markdown to clipboard',
|
|
227
|
-
default: false,
|
|
228
|
-
type: 'boolean'
|
|
229
|
-
})
|
|
230
224
|
.option('skip-more-than', {
|
|
231
225
|
describe: 'Skip jobs that fail more than <limit> builds, when --stat is true, default to 10.',
|
|
232
226
|
type: 'number',
|
|
@@ -397,7 +391,7 @@ class CICommand {
|
|
|
397
391
|
this.json = this.json.concat(json);
|
|
398
392
|
}
|
|
399
393
|
|
|
400
|
-
if (
|
|
394
|
+
if (argv.markdown && !argv.stats) {
|
|
401
395
|
this.markdown += build.formatAsMarkdown();
|
|
402
396
|
}
|
|
403
397
|
}
|
|
@@ -408,16 +402,6 @@ class CICommand {
|
|
|
408
402
|
async serialize() {
|
|
409
403
|
const { argv, cli } = this;
|
|
410
404
|
|
|
411
|
-
if (argv.copy) {
|
|
412
|
-
if (this.markdown) {
|
|
413
|
-
clipboardy.writeSync(this.markdown);
|
|
414
|
-
cli.separator('');
|
|
415
|
-
cli.log('Written markdown to clipboard');
|
|
416
|
-
} else {
|
|
417
|
-
cli.error('No markdown generated');
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
405
|
if (argv.markdown) {
|
|
422
406
|
if (this.markdown) {
|
|
423
407
|
writeFile(argv.markdown, this.markdown);
|
|
@@ -483,7 +467,7 @@ class WalkCommand extends CICommand {
|
|
|
483
467
|
cli.log('');
|
|
484
468
|
aggregator.display();
|
|
485
469
|
|
|
486
|
-
if (argv.markdown
|
|
470
|
+
if (argv.markdown) {
|
|
487
471
|
this.markdown = aggregator.formatAsMarkdown();
|
|
488
472
|
}
|
|
489
473
|
}
|
|
@@ -564,7 +548,7 @@ class DailyCommand extends CICommand {
|
|
|
564
548
|
cli.log('');
|
|
565
549
|
aggregator.display();
|
|
566
550
|
|
|
567
|
-
if (argv.markdown
|
|
551
|
+
if (argv.markdown) {
|
|
568
552
|
this.markdown = aggregator.formatAsMarkdown();
|
|
569
553
|
}
|
|
570
554
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import auth from '../../lib/auth.js';
|
|
2
|
+
import { parsePRFromURL } from '../../lib/links.js';
|
|
3
|
+
import CLI from '../../lib/cli.js';
|
|
4
|
+
import Request from '../../lib/request.js';
|
|
5
|
+
import { runPromise } from '../../lib/run.js';
|
|
6
|
+
import BenchmarkSession from '../../lib/benchmark.js';
|
|
7
|
+
|
|
8
|
+
export const command = 'benchmark <identifier>';
|
|
9
|
+
export const describe =
|
|
10
|
+
'Trigger the benchmark GitHub Actions workflow for a pull request';
|
|
11
|
+
|
|
12
|
+
const options = {
|
|
13
|
+
owner: {
|
|
14
|
+
alias: 'o',
|
|
15
|
+
describe: 'GitHub owner of the PR repository',
|
|
16
|
+
default: 'nodejs',
|
|
17
|
+
type: 'string'
|
|
18
|
+
},
|
|
19
|
+
repo: {
|
|
20
|
+
alias: 'r',
|
|
21
|
+
describe: 'GitHub repository of the PR',
|
|
22
|
+
default: 'node',
|
|
23
|
+
type: 'string'
|
|
24
|
+
},
|
|
25
|
+
'workflow-owner': {
|
|
26
|
+
describe: 'GitHub owner of the repository hosting the benchmark workflow ' +
|
|
27
|
+
'(defaults to the PR owner)',
|
|
28
|
+
type: 'string'
|
|
29
|
+
},
|
|
30
|
+
'workflow-repo': {
|
|
31
|
+
describe: 'GitHub repository hosting the benchmark workflow ' +
|
|
32
|
+
'(defaults to the PR repository)',
|
|
33
|
+
type: 'string'
|
|
34
|
+
},
|
|
35
|
+
workflow: {
|
|
36
|
+
describe: 'The workflow file to dispatch',
|
|
37
|
+
default: 'benchmark.yml',
|
|
38
|
+
type: 'string'
|
|
39
|
+
},
|
|
40
|
+
ref: {
|
|
41
|
+
describe: 'The git ref of the workflow repository to run the workflow from',
|
|
42
|
+
default: 'main',
|
|
43
|
+
type: 'string'
|
|
44
|
+
},
|
|
45
|
+
runs: {
|
|
46
|
+
describe: 'How many times to repeat each benchmark',
|
|
47
|
+
type: 'number'
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
let yargsInstance;
|
|
52
|
+
|
|
53
|
+
export function builder(yargs) {
|
|
54
|
+
yargsInstance = yargs;
|
|
55
|
+
return yargs
|
|
56
|
+
.options(options)
|
|
57
|
+
.positional('identifier', {
|
|
58
|
+
type: 'string',
|
|
59
|
+
describe: 'ID or URL of the pull request. A commit URL ' +
|
|
60
|
+
'(…/pull/<id>/commits/<sha>) benchmarks that commit instead of the ' +
|
|
61
|
+
'PR head.'
|
|
62
|
+
})
|
|
63
|
+
.example('git node benchmark 12344',
|
|
64
|
+
'Pick benchmark categories to run on ' +
|
|
65
|
+
'https://github.com/nodejs/node/pull/12344')
|
|
66
|
+
.example('git node benchmark https://github.com/nodejs/node/pull/12344',
|
|
67
|
+
'Pick benchmark categories to run on ' +
|
|
68
|
+
'https://github.com/nodejs/node/pull/12344')
|
|
69
|
+
.example(
|
|
70
|
+
'git node benchmark ' +
|
|
71
|
+
'https://github.com/nodejs/node/pull/12344/commits/abc1234',
|
|
72
|
+
'Benchmark commit abc1234 rather than the current PR head')
|
|
73
|
+
.wrap(90);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const COMMIT_URL_RE = /\/pull\/\d+\/commits\/([0-9a-f]{7,40})/i;
|
|
77
|
+
|
|
78
|
+
// Parse the positional identifier into { prid, owner?, repo?, commit? }.
|
|
79
|
+
// A PR commit URL (…/pull/<id>/commits/<sha>) also yields the commit SHA.
|
|
80
|
+
export function parseIdentifier(identifier) {
|
|
81
|
+
const prid = Number.parseInt(identifier);
|
|
82
|
+
if (!Number.isNaN(prid)) {
|
|
83
|
+
return { prid };
|
|
84
|
+
}
|
|
85
|
+
const parsed = parsePRFromURL(identifier);
|
|
86
|
+
if (!parsed) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const commit = COMMIT_URL_RE.exec(identifier);
|
|
90
|
+
if (commit) {
|
|
91
|
+
parsed.commit = commit[1];
|
|
92
|
+
}
|
|
93
|
+
return parsed;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function handler(argv) {
|
|
97
|
+
const parsed = parseIdentifier(argv.identifier);
|
|
98
|
+
if (!parsed) {
|
|
99
|
+
return yargsInstance.showHelp();
|
|
100
|
+
}
|
|
101
|
+
Object.assign(argv, parsed);
|
|
102
|
+
|
|
103
|
+
return runPromise(main(argv));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function main(argv) {
|
|
107
|
+
const cli = new CLI(process.stderr);
|
|
108
|
+
const credentials = await auth({ github: true });
|
|
109
|
+
const request = new Request(credentials);
|
|
110
|
+
const session = new BenchmarkSession(cli, request, argv);
|
|
111
|
+
return session.start();
|
|
112
|
+
}
|
package/components/git/land.js
CHANGED
|
@@ -48,8 +48,8 @@ const landActions = {
|
|
|
48
48
|
type: 'boolean'
|
|
49
49
|
},
|
|
50
50
|
fixupAll: {
|
|
51
|
-
describe: 'Automatically fixup all commits to the first one
|
|
52
|
-
'other commit messages',
|
|
51
|
+
describe: 'Automatically fixup ("squash") all commits to the first one ' +
|
|
52
|
+
'dismissing other commit messages',
|
|
53
53
|
default: false,
|
|
54
54
|
type: 'boolean'
|
|
55
55
|
},
|
|
@@ -181,7 +181,7 @@ async function main(state, argv, cli, dir) {
|
|
|
181
181
|
}
|
|
182
182
|
try {
|
|
183
183
|
session.restore();
|
|
184
|
-
} catch
|
|
184
|
+
} catch { // JSON error?
|
|
185
185
|
if (state === ABORT) {
|
|
186
186
|
await session.abort();
|
|
187
187
|
return;
|
|
@@ -36,6 +36,10 @@ const options = {
|
|
|
36
36
|
describe: 'Check for \'LGTM\' in comments',
|
|
37
37
|
type: 'boolean'
|
|
38
38
|
},
|
|
39
|
+
json: {
|
|
40
|
+
describe: 'Print metadata and PR readiness result as JSON',
|
|
41
|
+
type: 'boolean'
|
|
42
|
+
},
|
|
39
43
|
'max-commits': {
|
|
40
44
|
describe: 'Number of commits to warn',
|
|
41
45
|
type: 'number',
|
|
@@ -45,6 +49,29 @@ const options = {
|
|
|
45
49
|
|
|
46
50
|
let yargsInstance;
|
|
47
51
|
|
|
52
|
+
function writeStdout(text) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
process.stdout.write(text, (error) => {
|
|
55
|
+
if (error) {
|
|
56
|
+
reject(error);
|
|
57
|
+
} else {
|
|
58
|
+
resolve();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function writeMetadataJsonResult(metadataPromise) {
|
|
65
|
+
try {
|
|
66
|
+
const { json } = await metadataPromise;
|
|
67
|
+
await writeStdout(`${JSON.stringify(json, null, 2)}\n`);
|
|
68
|
+
process.exitCode = json.exitCode;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(error);
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
48
75
|
export function builder(yargs) {
|
|
49
76
|
yargsInstance = yargs;
|
|
50
77
|
return yargs
|
|
@@ -81,10 +108,16 @@ export function handler(argv) {
|
|
|
81
108
|
return yargsInstance.showHelp();
|
|
82
109
|
}
|
|
83
110
|
|
|
84
|
-
const logStream =
|
|
111
|
+
const logStream = argv.json || !process.stdout.isTTY
|
|
112
|
+
? process.stderr
|
|
113
|
+
: process.stdout;
|
|
85
114
|
const cli = new CLI(logStream);
|
|
86
115
|
|
|
87
116
|
const merged = Object.assign({}, argv, parsed, config);
|
|
117
|
+
if (argv.json) {
|
|
118
|
+
return writeMetadataJsonResult(getMetadata(merged, false, cli));
|
|
119
|
+
}
|
|
120
|
+
|
|
88
121
|
return runPromise(getMetadata(merged, false, cli)
|
|
89
122
|
.then(({ status }) => {
|
|
90
123
|
if (status === false) {
|
|
@@ -14,10 +14,6 @@ const PROMOTE = 'promote';
|
|
|
14
14
|
const RELEASERS = 'releasers';
|
|
15
15
|
|
|
16
16
|
const releaseOptions = {
|
|
17
|
-
filterLabel: {
|
|
18
|
-
describe: 'Labels separated by "," to filter security PRs',
|
|
19
|
-
type: 'string'
|
|
20
|
-
},
|
|
21
17
|
'gpg-sign': {
|
|
22
18
|
describe: 'GPG-sign commits, will be passed to the git process',
|
|
23
19
|
alias: 'S'
|
|
@@ -51,7 +47,9 @@ const releaseOptions = {
|
|
|
51
47
|
},
|
|
52
48
|
security: {
|
|
53
49
|
describe: 'Demarcate the new security release as a security release. ' +
|
|
54
|
-
'Optionally provide path to
|
|
50
|
+
'Optionally provide the path to vulnerabilities.json (or the ' +
|
|
51
|
+
'security-release repository) to cherry-pick the reports and ' +
|
|
52
|
+
'dependency updates for this release line and auto-populate CVEs',
|
|
55
53
|
type: 'string',
|
|
56
54
|
coerce: (arg) => {
|
|
57
55
|
// If --security=path is used, return the path
|
|
@@ -1,17 +1,37 @@
|
|
|
1
|
+
import auth from '../../lib/auth.js';
|
|
2
|
+
import Request from '../../lib/request.js';
|
|
3
|
+
import LandingSession from '../../lib/landing_session.js';
|
|
4
|
+
import Session from '../../lib/session.js';
|
|
1
5
|
import CLI from '../../lib/cli.js';
|
|
6
|
+
import { getMetadata } from '../metadata.js';
|
|
7
|
+
import { checkCwd } from '../../lib/update-v8/common.js';
|
|
8
|
+
import { parsePRFromURL } from '../../lib/links.js';
|
|
2
9
|
import PrepareSecurityRelease from '../../lib/prepare_security.js';
|
|
3
10
|
import UpdateSecurityRelease from '../../lib/update_security_release.js';
|
|
4
11
|
import SecurityBlog from '../../lib/security_blog.js';
|
|
5
12
|
import SecurityAnnouncement from '../../lib/security-announcement.js';
|
|
13
|
+
import { forceRunAsync } from '../../lib/run.js';
|
|
14
|
+
import { getAffectedVersionLines } from '../../lib/security-release/security-release.js';
|
|
15
|
+
import { getPullRequestURLForLine } from '../../lib/prepare_release.js';
|
|
6
16
|
|
|
7
17
|
export const command = 'security [options]';
|
|
8
18
|
export const describe = 'Manage an in-progress security release or start a new one.';
|
|
9
19
|
|
|
20
|
+
const SECURITY_REPO = {
|
|
21
|
+
owner: 'nodejs-private',
|
|
22
|
+
repo: 'node-private',
|
|
23
|
+
};
|
|
24
|
+
|
|
10
25
|
const securityOptions = {
|
|
11
26
|
start: {
|
|
12
27
|
describe: 'Start security release process',
|
|
13
28
|
type: 'boolean'
|
|
14
29
|
},
|
|
30
|
+
'apply-patches': {
|
|
31
|
+
describe: 'Start an interactive session to make local HEAD ready to create ' +
|
|
32
|
+
'a security release proposal',
|
|
33
|
+
type: 'boolean'
|
|
34
|
+
},
|
|
15
35
|
sync: {
|
|
16
36
|
describe: 'Synchronize an ongoing security release with HackerOne',
|
|
17
37
|
type: 'boolean'
|
|
@@ -52,6 +72,26 @@ const securityOptions = {
|
|
|
52
72
|
|
|
53
73
|
let yargsInstance;
|
|
54
74
|
|
|
75
|
+
function isPromptInterrupted(error) {
|
|
76
|
+
return error?.name === 'ExitPromptError' ||
|
|
77
|
+
error?.name === 'AbortPromptError' ||
|
|
78
|
+
error?.message?.includes('User force closed');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function runSecurityAction(cli, action) {
|
|
82
|
+
return Promise.resolve(action).catch((error) => {
|
|
83
|
+
if (!isPromptInterrupted(error)) {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (cli.spinner.isSpinning) {
|
|
88
|
+
cli.spinner.stop();
|
|
89
|
+
}
|
|
90
|
+
cli.warn('Aborted.');
|
|
91
|
+
cli.setExitCode(1);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
55
95
|
export function builder(yargs) {
|
|
56
96
|
yargsInstance = yargs;
|
|
57
97
|
return yargs.options(securityOptions)
|
|
@@ -59,6 +99,10 @@ export function builder(yargs) {
|
|
|
59
99
|
'git node security --start',
|
|
60
100
|
'Prepare a security release of Node.js'
|
|
61
101
|
)
|
|
102
|
+
.example(
|
|
103
|
+
'git node security --apply-patches',
|
|
104
|
+
'Fetch all the patches for an upcoming security release'
|
|
105
|
+
)
|
|
62
106
|
.example(
|
|
63
107
|
'git node security --sync',
|
|
64
108
|
'Synchronize an ongoing security release with HackerOne'
|
|
@@ -94,37 +138,36 @@ export function builder(yargs) {
|
|
|
94
138
|
export function handler(argv) {
|
|
95
139
|
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
96
140
|
const cli = new CLI(logStream);
|
|
141
|
+
let action;
|
|
97
142
|
|
|
98
143
|
if (argv.start) {
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (argv['update-date']) {
|
|
105
|
-
|
|
144
|
+
action = startSecurityRelease(cli, argv);
|
|
145
|
+
} else if (argv['apply-patches']) {
|
|
146
|
+
action = applySecurityPatches(cli, argv);
|
|
147
|
+
} else if (argv.sync) {
|
|
148
|
+
action = syncSecurityRelease(cli, argv);
|
|
149
|
+
} else if (argv['update-date']) {
|
|
150
|
+
action = updateReleaseDate(cli, argv);
|
|
151
|
+
} else if (argv['pre-release']) {
|
|
152
|
+
action = createPreRelease(cli, argv);
|
|
153
|
+
} else if (argv['add-report']) {
|
|
154
|
+
action = addReport(cli, argv);
|
|
155
|
+
} else if (argv['remove-report']) {
|
|
156
|
+
action = removeReport(cli, argv);
|
|
157
|
+
} else if (argv['notify-pre-release']) {
|
|
158
|
+
action = notifyPreRelease(cli, argv);
|
|
159
|
+
} else if (argv['request-cve']) {
|
|
160
|
+
action = requestCVEs(cli, argv);
|
|
161
|
+
} else if (argv['post-release']) {
|
|
162
|
+
action = createPostRelease(cli, argv);
|
|
163
|
+
} else if (argv.cleanup) {
|
|
164
|
+
action = cleanupSecurityRelease(cli, argv);
|
|
106
165
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (argv['add-report']) {
|
|
111
|
-
return addReport(cli, argv);
|
|
112
|
-
}
|
|
113
|
-
if (argv['remove-report']) {
|
|
114
|
-
return removeReport(cli, argv);
|
|
115
|
-
}
|
|
116
|
-
if (argv['notify-pre-release']) {
|
|
117
|
-
return notifyPreRelease(cli, argv);
|
|
118
|
-
}
|
|
119
|
-
if (argv['request-cve']) {
|
|
120
|
-
return requestCVEs(cli, argv);
|
|
121
|
-
}
|
|
122
|
-
if (argv['post-release']) {
|
|
123
|
-
return createPostRelease(cli, argv);
|
|
124
|
-
}
|
|
125
|
-
if (argv.cleanup) {
|
|
126
|
-
return cleanupSecurityRelease(cli, argv);
|
|
166
|
+
|
|
167
|
+
if (action) {
|
|
168
|
+
return runSecurityAction(cli, action);
|
|
127
169
|
}
|
|
170
|
+
|
|
128
171
|
yargsInstance.showHelp();
|
|
129
172
|
}
|
|
130
173
|
|
|
@@ -168,6 +211,194 @@ async function startSecurityRelease(cli) {
|
|
|
168
211
|
return release.start();
|
|
169
212
|
}
|
|
170
213
|
|
|
214
|
+
async function fetchVulnerabilitiesDotJSON(cli, req) {
|
|
215
|
+
const { owner } = SECURITY_REPO;
|
|
216
|
+
const repo = 'security-release';
|
|
217
|
+
|
|
218
|
+
cli.startSpinner(`Looking for Security Release PR on ${owner}/${repo}`);
|
|
219
|
+
const { repository: { pullRequests: { nodes: { length, 0: pr } } } } =
|
|
220
|
+
await req.gql('ListSecurityReleasePRs', { owner, repo });
|
|
221
|
+
if (length !== 1) {
|
|
222
|
+
cli.stopSpinner('Expected exactly one open Pull Request on the ' +
|
|
223
|
+
`${owner}/${repo} repository, found ${length}`,
|
|
224
|
+
cli.SPINNER_STATUS.FAILED);
|
|
225
|
+
cli.setExitCode(1);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (pr.files.nodes.length !== 1 || !pr.files.nodes[0].path.endsWith('vulnerabilities.json')) {
|
|
229
|
+
cli.stopSpinner(
|
|
230
|
+
`${owner}/${repo}#${pr.number} does not contain only vulnerabilities.json`,
|
|
231
|
+
cli.SPINNER_STATUS.FAILED
|
|
232
|
+
);
|
|
233
|
+
cli.setExitCode(1);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
cli.stopSpinner(`Found ${owner}/${repo}#${pr.number} by @${pr.author.login}`);
|
|
237
|
+
cli.startSpinner('Fetching vulnerabilities.json...');
|
|
238
|
+
const result = await req.json(
|
|
239
|
+
`/repos/${owner}/${repo}/contents/${pr.files.nodes[0].path}?ref=${pr.headRefOid}`,
|
|
240
|
+
{ headers: { Accept: 'application/vnd.github.raw+json' } }
|
|
241
|
+
);
|
|
242
|
+
cli.stopSpinner('Fetched vulnerabilities.json');
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function skipIfExisting(cli, prURL) {
|
|
247
|
+
const existingCommit = await forceRunAsync('git',
|
|
248
|
+
['--no-pager', 'log', 'HEAD', '--grep', `^PR-URL: ${prURL}$`, '--format=%h %s'],
|
|
249
|
+
{ ignoreFailure: false, captureStdout: true });
|
|
250
|
+
if (existingCommit.trim()) {
|
|
251
|
+
cli.info(`${prURL} seems to already be on the current tree: ${existingCommit}`);
|
|
252
|
+
return await cli.prompt('Do you want to skip it?', { defaultAnswer: true });
|
|
253
|
+
}
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
async function landingSession(cli, req, prURL, argv, cveIds = undefined) {
|
|
257
|
+
const response = await cli.prompt('Do you want to land it on the current HEAD?',
|
|
258
|
+
{ defaultAnswer: true });
|
|
259
|
+
if (!response) {
|
|
260
|
+
cli.info('Skipping');
|
|
261
|
+
cli.warn('The resulting HEAD will not be ready for a release proposal');
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!cli.hasDetachedHEAD) {
|
|
266
|
+
// Moving to a detached HEAD, we don't want security patches to be pushed to the public repo.
|
|
267
|
+
await forceRunAsync('git', ['checkout', '--detach'], { ignoreFailure: false });
|
|
268
|
+
}
|
|
269
|
+
cli.hasDetachedHEAD = (await forceRunAsync(
|
|
270
|
+
'git', ['rev-parse', 'HEAD'],
|
|
271
|
+
{ ignoreFailure: false, captureStdout: true })).trim();
|
|
272
|
+
|
|
273
|
+
const session = new LandingSession(cli, req, process.cwd(), {
|
|
274
|
+
autorebase: true, oneCommitMax: false, ...argv
|
|
275
|
+
});
|
|
276
|
+
Object.defineProperty(session, 'tryResetBranch', {
|
|
277
|
+
__proto__: null,
|
|
278
|
+
value: Function.prototype,
|
|
279
|
+
configurable: true,
|
|
280
|
+
});
|
|
281
|
+
const metadata = await getMetadata(session.argv, true, cli);
|
|
282
|
+
if (argv?.backport) {
|
|
283
|
+
metadata.metadata += `PR-URL: ${prURL}\n`;
|
|
284
|
+
}
|
|
285
|
+
if (cveIds?.length) {
|
|
286
|
+
metadata.metadata += cveIds.map(cve => `CVE-ID: ${cve}\n`).join('');
|
|
287
|
+
}
|
|
288
|
+
await session.start(metadata);
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
async function applySecurityPatches(cli) {
|
|
292
|
+
const { nodeMajorVersion } = await checkCwd({ nodeDir: process.cwd() });
|
|
293
|
+
const branch = `v${nodeMajorVersion}.x`;
|
|
294
|
+
const credentials = await auth({
|
|
295
|
+
github: true
|
|
296
|
+
});
|
|
297
|
+
const req = new Request(credentials);
|
|
298
|
+
|
|
299
|
+
cli.info('N.B.: if there are commits on the staging branch that need to be included in the ' +
|
|
300
|
+
'security release, please rebase them manually and answer no to the following question');
|
|
301
|
+
// Try reset to the public upstream
|
|
302
|
+
const session = new Session(cli, process.cwd(), undefined, { branch });
|
|
303
|
+
await session.tryResetBranch();
|
|
304
|
+
|
|
305
|
+
const { owner, repo } = SECURITY_REPO;
|
|
306
|
+
const { releaseDate, reports, dependencies } = await fetchVulnerabilitiesDotJSON(cli, req);
|
|
307
|
+
|
|
308
|
+
let patchedVersion;
|
|
309
|
+
const releaseLine = `${nodeMajorVersion}.x`;
|
|
310
|
+
for (const [name, dep] of Object.entries(dependencies ?? {})) {
|
|
311
|
+
const prURL = getPullRequestURLForLine(
|
|
312
|
+
dep.affectedVersions, releaseLine, dep.prURL);
|
|
313
|
+
if (!prURL) continue;
|
|
314
|
+
cli.separator(`Taking care of ${name}...`);
|
|
315
|
+
if (await skipIfExisting(cli, prURL)) continue;
|
|
316
|
+
|
|
317
|
+
const argv = parsePRFromURL(prURL);
|
|
318
|
+
if (argv.owner === SECURITY_REPO.owner && argv.repo === SECURITY_REPO.repo) {
|
|
319
|
+
await landingSession(cli, req, prURL, argv);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const existingCommits = (await forceRunAsync('git',
|
|
324
|
+
['--no-pager', 'log', `${session.upstream}/v${nodeMajorVersion}.x-staging`,
|
|
325
|
+
'--grep', `^PR-URL: ${prURL}$`,
|
|
326
|
+
'--format=%H %h %s'],
|
|
327
|
+
{ ignoreFailure: false, captureStdout: true })).trim().split('\n');
|
|
328
|
+
if (existingCommits[0] === '') {
|
|
329
|
+
cli.error(`${prURL} was not found on ${session.upstream}/v${nodeMajorVersion}.x-staging.`);
|
|
330
|
+
cli.info('Please cherry-pick the adequate commits to the public staging branch.');
|
|
331
|
+
cli.info('Skipping');
|
|
332
|
+
cli.warn('The resulting HEAD will not be ready for a release proposal');
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
cli.info(`The following commit(s) have been found on ${session.upstream}/v${nodeMajorVersion
|
|
336
|
+
}.x-staging:\n${existingCommits.map(c => ` - ${c.slice(41)}`).join('\n')}`);
|
|
337
|
+
if (!await cli.prompt('Cherry-pick those for the current proposal?')) {
|
|
338
|
+
cli.info('Skipping');
|
|
339
|
+
cli.warn('The resulting HEAD will not be ready for a release proposal');
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
await forceRunAsync('git',
|
|
343
|
+
['cherry-pick', ...existingCommits.map(c => c.slice(0, 40))],
|
|
344
|
+
{ ignoreFailure: false });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
cli.startSpinner(`Fetching open PRs on ${owner}/${repo}...`);
|
|
348
|
+
const { repository: { pullRequests: { nodes } } } = await req.gql('PRs', {
|
|
349
|
+
owner, repo, labels: [branch],
|
|
350
|
+
});
|
|
351
|
+
cli.stopSpinner(`Fetched all PRs labeled for v${nodeMajorVersion}.x`);
|
|
352
|
+
|
|
353
|
+
for (const { affectedVersions, prURL, cveIds, patchedVersions } of reports) {
|
|
354
|
+
if (!getAffectedVersionLines(affectedVersions).includes(`${nodeMajorVersion}.x`)) continue;
|
|
355
|
+
patchedVersion ??= patchedVersions?.find(v => v.startsWith(`${nodeMajorVersion}.`));
|
|
356
|
+
cli.separator(`Taking care of ${cveIds.join(', ')}...`);
|
|
357
|
+
|
|
358
|
+
if (await skipIfExisting(cli, prURL)) continue;
|
|
359
|
+
|
|
360
|
+
let pr = nodes.find(({ url }) => url === prURL);
|
|
361
|
+
if (!pr) {
|
|
362
|
+
cli.info(
|
|
363
|
+
`${prURL} is not labelled for v${nodeMajorVersion}.x, there might be a backport PR.`
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
cli.startSpinner('Fetching PR title to find a match...');
|
|
367
|
+
const { title } = await req.getPullRequest(prURL);
|
|
368
|
+
pr = nodes.find((pr) => pr.title.endsWith(title));
|
|
369
|
+
if (pr) {
|
|
370
|
+
cli.stopSpinner(`Found ${pr.url}`);
|
|
371
|
+
} else {
|
|
372
|
+
cli.stopSpinner(`Did not find a match for "${title}"`, cli.SPINNER_STATUS.WARN);
|
|
373
|
+
const prID = await cli.prompt(
|
|
374
|
+
'Please enter the PR number to use:',
|
|
375
|
+
{ questionType: cli.QUESTION_TYPE.NUMBER, defaultAnswer: NaN }
|
|
376
|
+
);
|
|
377
|
+
pr = nodes.find(({ number }) => number === prID);
|
|
378
|
+
if (!pr) {
|
|
379
|
+
cli.error(`${prID} is not in the list of PRs labelled for v${nodeMajorVersion}.x`);
|
|
380
|
+
cli.info('The list of labelled PRs and vulnerabilities.json are fetched ' +
|
|
381
|
+
'once at the start of the session; to refresh those, start a new NCU session');
|
|
382
|
+
const response = await cli.prompt('Do you want to skip that CVE?',
|
|
383
|
+
{ defaultAnswer: false });
|
|
384
|
+
if (response) continue;
|
|
385
|
+
throw new Error(`Found no patch for ${cveIds}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
cli.ok(`${pr.url} is labelled for v${nodeMajorVersion}.x.`);
|
|
390
|
+
|
|
391
|
+
const backport = prURL !== pr.url;
|
|
392
|
+
|
|
393
|
+
await landingSession(cli, req, prURL, { prid: pr.number, backport, ...SECURITY_REPO }, cveIds);
|
|
394
|
+
}
|
|
395
|
+
cli.ok('All patches are on the local HEAD!');
|
|
396
|
+
cli.info('You can now build and test, and create a proposal with the following commands:');
|
|
397
|
+
cli.info(`git switch -C v${nodeMajorVersion}.x HEAD`);
|
|
398
|
+
cli.info(`git node release --prepare --security --newVersion=${patchedVersion} ` +
|
|
399
|
+
`--releaseDate=${releaseDate.replaceAll('/', '-')} --skipBranchDiff`);
|
|
400
|
+
}
|
|
401
|
+
|
|
171
402
|
async function cleanupSecurityRelease(cli) {
|
|
172
403
|
const release = new PrepareSecurityRelease(cli);
|
|
173
404
|
return release.cleanup();
|
package/components/git/v8.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
|
|
3
|
-
import logSymbols from 'log-symbols';
|
|
4
|
-
|
|
5
3
|
import { minor, major, backport, deps } from '../../lib/update-v8/index.js';
|
|
6
4
|
import { defaultBaseDir } from '../../lib/update-v8/constants.js';
|
|
7
5
|
import { checkCwd } from '../../lib/update-v8/common.js';
|
|
8
6
|
import { forceRunAsync } from '../../lib/run.js';
|
|
7
|
+
import { error } from '../../lib/figures.js';
|
|
9
8
|
|
|
10
9
|
export const command = 'v8 [major|minor|backport|deps]';
|
|
11
10
|
export const describe = 'Update or patch the V8 engine';
|
|
@@ -31,6 +30,10 @@ export function builder(yargs) {
|
|
|
31
30
|
describe: 'Update dependencies concurrently',
|
|
32
31
|
default: true,
|
|
33
32
|
});
|
|
33
|
+
yargs.option('cargo', {
|
|
34
|
+
describe: 'The cargo binary that will be executed when updating the Rust crates',
|
|
35
|
+
default: 'cargo',
|
|
36
|
+
});
|
|
34
37
|
}
|
|
35
38
|
})
|
|
36
39
|
.command({
|
|
@@ -150,7 +153,7 @@ export function handler(argv) {
|
|
|
150
153
|
})
|
|
151
154
|
.catch((err) => {
|
|
152
155
|
console.error(
|
|
153
|
-
|
|
156
|
+
error,
|
|
154
157
|
options.verbose ? err.stack : err.message
|
|
155
158
|
);
|
|
156
159
|
process.exitCode = 1;
|