@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.
- 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 +25 -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 +312 -46
- 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 +5 -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/lib/file.js
CHANGED
|
@@ -8,7 +8,7 @@ export function appendFile(file, content) {
|
|
|
8
8
|
}
|
|
9
9
|
// TODO(joyeecheung): what if the file is a dir?
|
|
10
10
|
fs.appendFileSync(file, content, 'utf8');
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
12
|
|
|
13
13
|
export function writeFile(file, content) {
|
|
14
14
|
const parts = path.parse(file);
|
|
@@ -17,18 +17,18 @@ export function writeFile(file, content) {
|
|
|
17
17
|
}
|
|
18
18
|
// TODO(joyeecheung): what if the file is a dir?
|
|
19
19
|
fs.writeFileSync(file, content, 'utf8');
|
|
20
|
-
}
|
|
20
|
+
}
|
|
21
21
|
|
|
22
22
|
export function writeJson(file, obj) {
|
|
23
23
|
writeFile(file, `${JSON.stringify(obj, null, 2)}\n`);
|
|
24
|
-
}
|
|
24
|
+
}
|
|
25
25
|
|
|
26
26
|
export function readFile(file) {
|
|
27
27
|
if (fs.existsSync(file)) {
|
|
28
28
|
return fs.readFileSync(file, 'utf8');
|
|
29
29
|
}
|
|
30
30
|
return '';
|
|
31
|
-
}
|
|
31
|
+
}
|
|
32
32
|
|
|
33
33
|
export function readJson(file) {
|
|
34
34
|
const content = readFile(file);
|
|
@@ -36,7 +36,7 @@ export function readJson(file) {
|
|
|
36
36
|
return JSON.parse(content);
|
|
37
37
|
}
|
|
38
38
|
return {};
|
|
39
|
-
}
|
|
39
|
+
}
|
|
40
40
|
|
|
41
41
|
export function removeDirectory(directory) {
|
|
42
42
|
return fs.promises.rm(directory, { recursive: true, force: true });
|
package/lib/landing_session.js
CHANGED
|
@@ -82,7 +82,9 @@ export default class LandingSession extends Session {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
async downloadAndPatch() {
|
|
85
|
-
const {
|
|
85
|
+
const {
|
|
86
|
+
cli, upstream, prid, expectedCommitShas, crossRepoPR, landedCommitSha
|
|
87
|
+
} = this;
|
|
86
88
|
|
|
87
89
|
if (crossRepoPR) {
|
|
88
90
|
const { owner, repo } = this;
|
|
@@ -107,6 +109,12 @@ export default class LandingSession extends Session {
|
|
|
107
109
|
{ ignoreFailure: false });
|
|
108
110
|
}
|
|
109
111
|
} while (!isHeadAMergeCommit());
|
|
112
|
+
} else if (landedCommitSha) {
|
|
113
|
+
// The PR already landed (e.g. a security fix released publicly), so
|
|
114
|
+
// its merge ref is gone - fetch the commits from the base branch.
|
|
115
|
+
cli.startSpinner(`Downloading landed commits for ${prid}`);
|
|
116
|
+
await forceRunAsync('git', ['fetch', upstream, landedCommitSha],
|
|
117
|
+
{ ignoreFailure: false });
|
|
110
118
|
} else {
|
|
111
119
|
cli.startSpinner(`Downloading patch for ${prid}`);
|
|
112
120
|
await runAsync('git', [
|
|
@@ -114,29 +122,60 @@ export default class LandingSession extends Session {
|
|
|
114
122
|
`refs/pull/${prid}/merge`]);
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
let base, head, rebaseHead;
|
|
126
|
+
if (landedCommitSha && !crossRepoPR) {
|
|
127
|
+
// FETCH_HEAD is the last commit of the PR as landed on the base branch.
|
|
128
|
+
// Walk back while the PR-URL trailer still points to this PR to find
|
|
129
|
+
// the base of the landed series (landing may have squashed commits).
|
|
130
|
+
const prUrlRe = new RegExp(`^PR-URL: \\S+/pull/${prid}$`, 'm');
|
|
131
|
+
let count = 0;
|
|
132
|
+
while (count < expectedCommitShas.length) {
|
|
133
|
+
const message = await runAsync('git',
|
|
134
|
+
['log', '-1', '--format=%B', `FETCH_HEAD~${count}`],
|
|
135
|
+
{ captureStdout: true });
|
|
136
|
+
if (!prUrlRe.test(message)) break;
|
|
137
|
+
count++;
|
|
138
|
+
}
|
|
139
|
+
if (count === 0) {
|
|
140
|
+
cli.error(`Landed commit ${landedCommitSha} does not have a ` +
|
|
141
|
+
`PR-URL trailer pointing to pull request ${prid}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
[base, head, rebaseHead] = await runAsync('git',
|
|
145
|
+
['rev-parse', `FETCH_HEAD~${count}`, 'FETCH_HEAD', 'HEAD'],
|
|
146
|
+
{ captureStdout: 'lines' });
|
|
147
|
+
} else {
|
|
148
|
+
// We fetched the commit that would result if we used `git merge`.
|
|
149
|
+
// ^1 and ^2 refer to the PR base and the PR head, respectively.
|
|
150
|
+
[base, head, rebaseHead] = await runAsync('git',
|
|
151
|
+
['rev-parse', 'FETCH_HEAD^1', 'FETCH_HEAD^2', 'HEAD'],
|
|
152
|
+
{ captureStdout: 'lines' });
|
|
153
|
+
}
|
|
122
154
|
const commitShas = await runAsync('git',
|
|
123
155
|
['rev-list', `${base}..${head}`],
|
|
124
156
|
{ captureStdout: 'lines' });
|
|
125
157
|
cli.stopSpinner(`Fetched commits as ${shortSha(base)}..${shortSha(head)}`);
|
|
126
158
|
cli.separator();
|
|
127
159
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
160
|
+
if (landedCommitSha && !crossRepoPR) {
|
|
161
|
+
// The landed commits have different SHAs from the ones in the PR, so
|
|
162
|
+
// they cannot be compared - they were validated by their PR-URL
|
|
163
|
+
// trailers above.
|
|
164
|
+
cli.ok(`Using ${commitShas.length} commit(s) as landed for PR ${prid}`);
|
|
165
|
+
} else {
|
|
166
|
+
const mismatchedCommits = [
|
|
167
|
+
...commitShas.filter((sha) => !expectedCommitShas.includes(sha))
|
|
168
|
+
.map((sha) => `Unexpected commit ${sha}`),
|
|
169
|
+
...expectedCommitShas.filter((sha) => !commitShas.includes(sha))
|
|
170
|
+
.map((sha) => `Missing commit ${sha}`)
|
|
171
|
+
].join('\n');
|
|
172
|
+
if (mismatchedCommits.length > 0) {
|
|
173
|
+
cli.error(`Mismatched commits:\n${mismatchedCommits}`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
137
176
|
}
|
|
138
177
|
|
|
139
|
-
const commitInfo = { base, head, shas: commitShas };
|
|
178
|
+
const commitInfo = { base, head, shas: commitShas, rebaseHead };
|
|
140
179
|
this.saveCommitInfo(commitInfo);
|
|
141
180
|
|
|
142
181
|
try {
|
|
@@ -233,12 +272,11 @@ export default class LandingSession extends Session {
|
|
|
233
272
|
// so that it will perform everything automatically.
|
|
234
273
|
cli.log(`There are ${subjects.length} commits in the PR. ` +
|
|
235
274
|
'Attempting autorebase.');
|
|
236
|
-
const { upstream, branch } = this;
|
|
237
275
|
const assumeYes = this.cli.assumeYes ? '--yes' : '';
|
|
238
276
|
const msgAmend = `-x "git node land --amend ${assumeYes}"`;
|
|
239
277
|
try {
|
|
240
278
|
await forceRunAsync('git',
|
|
241
|
-
['rebase', ...this.gpgSign,
|
|
279
|
+
['rebase', ...this.gpgSign, commitInfo.rebaseHead,
|
|
242
280
|
'--no-keep-empty', '-i', '--autosquash', msgAmend],
|
|
243
281
|
{
|
|
244
282
|
ignoreFailure: false,
|
|
@@ -248,7 +286,7 @@ export default class LandingSession extends Session {
|
|
|
248
286
|
}
|
|
249
287
|
});
|
|
250
288
|
return this.final();
|
|
251
|
-
} catch
|
|
289
|
+
} catch {
|
|
252
290
|
await runAsync('git', ['rebase', '--abort']);
|
|
253
291
|
const count = subjects.length;
|
|
254
292
|
cli.log(`Couldn't rebase ${count} commits in the PR automatically`);
|
|
@@ -399,14 +437,14 @@ export default class LandingSession extends Session {
|
|
|
399
437
|
if (!cveID) {
|
|
400
438
|
cveID = await cli.prompt(
|
|
401
439
|
'Git found no CVE-ID trailer in the original commit message. ' +
|
|
402
|
-
'Please, provide the CVE-ID or leave it empty',
|
|
403
|
-
{ questionType: 'input', defaultAnswer: '
|
|
440
|
+
'Please, provide the CVE-ID (e.g. CVE-2026-12345) or leave it empty',
|
|
441
|
+
{ questionType: 'input', defaultAnswer: '' }
|
|
404
442
|
);
|
|
405
443
|
}
|
|
406
444
|
}
|
|
407
445
|
// Some commits might not address a vulnerability, but it is necessary
|
|
408
446
|
// for the security release to happen.
|
|
409
|
-
if (cveID
|
|
447
|
+
if (cveID) {
|
|
410
448
|
amended.push('CVE-ID: ' + cveID);
|
|
411
449
|
}
|
|
412
450
|
}
|
|
@@ -431,7 +469,10 @@ export default class LandingSession extends Session {
|
|
|
431
469
|
const messageFile = this.saveMessage(rev, message);
|
|
432
470
|
cli.separator('New Message');
|
|
433
471
|
cli.log(message.trim());
|
|
434
|
-
|
|
472
|
+
// `skipMessagePrompt` is only set by callers that already got a blanket
|
|
473
|
+
// confirmation; it is undefined on a regular landing session.
|
|
474
|
+
const takeMessage = this.skipMessagePrompt ||
|
|
475
|
+
await cli.prompt('Use this message?');
|
|
435
476
|
if (takeMessage) {
|
|
436
477
|
await runAsync('git', ['commit', '--amend', '-F', messageFile, ...this.gpgSign]);
|
|
437
478
|
return true;
|
|
@@ -469,7 +510,7 @@ export default class LandingSession extends Session {
|
|
|
469
510
|
cli.log('A git rebase/am is in progress.' +
|
|
470
511
|
' Please complete it before running git node land --final');
|
|
471
512
|
return;
|
|
472
|
-
}
|
|
513
|
+
}
|
|
473
514
|
|
|
474
515
|
const stray = this.getStrayCommits();
|
|
475
516
|
if (stray.length > 1) {
|
package/lib/links.js
CHANGED
|
@@ -100,15 +100,15 @@ export function parsePRFromURL(url) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
return undefined;
|
|
103
|
-
}
|
|
103
|
+
}
|
|
104
104
|
|
|
105
105
|
export function getPrURL({ owner, repo, prid }) {
|
|
106
106
|
return `https://github.com/${owner}/${repo}/pull/${prid}`;
|
|
107
|
-
}
|
|
107
|
+
}
|
|
108
108
|
|
|
109
109
|
export function getMachineUrl(machine) {
|
|
110
110
|
return `[${machine.hostname}](${machine.url})`;
|
|
111
|
-
}
|
|
111
|
+
}
|
|
112
112
|
|
|
113
113
|
const PR_URL_RE = /PR-URL: https:\/\/github.com\/.+/;
|
|
114
114
|
export function parsePrURL(text) {
|
|
@@ -120,4 +120,4 @@ export function parsePrURL(text) {
|
|
|
120
120
|
return undefined;
|
|
121
121
|
}
|
|
122
122
|
return parsePRFromURL(match[0]);
|
|
123
|
-
}
|
|
123
|
+
}
|
package/lib/pr_checker.js
CHANGED
|
@@ -29,6 +29,30 @@ const FAST_TRACK_RE = /^Fast-track has been requested by @(.+?)\. Please 👍 to
|
|
|
29
29
|
const FAST_TRACK_MIN_APPROVALS = 2;
|
|
30
30
|
const GIT_CONFIG_GUIDE_URL = 'https://github.com/nodejs/node/blob/99b1ada/doc/guides/contributing/pull-requests.md#step-1-fork';
|
|
31
31
|
|
|
32
|
+
export const PR_CHECK_REASON_CODES = Object.freeze({
|
|
33
|
+
CANCELLED_GITHUB_CI: 'cancelled-github-ci',
|
|
34
|
+
CLOSED: 'closed',
|
|
35
|
+
CONFLICT: 'conflict',
|
|
36
|
+
FAILED_GITHUB_CI: 'failed-github-ci',
|
|
37
|
+
FAILED_JENKINS_CI: 'failed-jenkins-ci',
|
|
38
|
+
INVALID_CI_TYPE: 'invalid-ci-type',
|
|
39
|
+
MERGED: 'merged',
|
|
40
|
+
MISSING_APPROVAL: 'missing-approval',
|
|
41
|
+
MISSING_FAST_TRACK_COMMENT: 'missing-fast-track-comment',
|
|
42
|
+
MISSING_FULL_JENKINS_CI: 'missing-full-jenkins-ci',
|
|
43
|
+
MISSING_GITHUB_CI: 'missing-github-ci',
|
|
44
|
+
MISSING_JENKINS_CI: 'missing-jenkins-ci',
|
|
45
|
+
MISSING_TSC_APPROVAL: 'missing-tsc-approval',
|
|
46
|
+
NEW_CONTRIBUTOR: 'new-contributor',
|
|
47
|
+
NO_COMMITS: 'no-commits',
|
|
48
|
+
PENDING_GITHUB_CI: 'pending-github-ci',
|
|
49
|
+
PENDING_JENKINS_CI: 'pending-jenkins-ci',
|
|
50
|
+
REQUESTED_CHANGES: 'requested-changes',
|
|
51
|
+
STALE_CI: 'stale-ci',
|
|
52
|
+
STALE_REVIEW: 'stale-review',
|
|
53
|
+
WAIT_TIME: 'wait-time'
|
|
54
|
+
});
|
|
55
|
+
|
|
32
56
|
export default class PRChecker {
|
|
33
57
|
/**
|
|
34
58
|
* @param {{}} cli
|
|
@@ -51,6 +75,22 @@ export default class PRChecker {
|
|
|
51
75
|
this.reviews = reviews;
|
|
52
76
|
this.commits = commits;
|
|
53
77
|
this.argv = argv;
|
|
78
|
+
this.reasons = [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
addReason(code, message, details = {}) {
|
|
82
|
+
this.reasons.push({
|
|
83
|
+
code,
|
|
84
|
+
message,
|
|
85
|
+
...details
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getResult(status) {
|
|
90
|
+
return {
|
|
91
|
+
ready: status,
|
|
92
|
+
reasons: this.reasons
|
|
93
|
+
};
|
|
54
94
|
}
|
|
55
95
|
|
|
56
96
|
get waitTimeSingleApproval() {
|
|
@@ -68,6 +108,8 @@ export default class PRChecker {
|
|
|
68
108
|
}
|
|
69
109
|
|
|
70
110
|
async checkAll(checkComments = false, checkCI = true) {
|
|
111
|
+
this.reasons = [];
|
|
112
|
+
|
|
71
113
|
const status = [
|
|
72
114
|
this.checkCommitsAfterReview(),
|
|
73
115
|
this.checkReviewsAndWait(new Date(), checkComments),
|
|
@@ -107,14 +149,20 @@ export default class PRChecker {
|
|
|
107
149
|
displayReviews(checkComments) {
|
|
108
150
|
const { cli, reviewers: { requestedChanges, approved } } = this;
|
|
109
151
|
if (requestedChanges.length > 0) {
|
|
110
|
-
|
|
152
|
+
const message = `Requested Changes: ${requestedChanges.length}`;
|
|
153
|
+
cli.error(message);
|
|
154
|
+
this.addReason(PR_CHECK_REASON_CODES.REQUESTED_CHANGES, message, {
|
|
155
|
+
count: requestedChanges.length
|
|
156
|
+
});
|
|
111
157
|
for (const { reviewer, review } of requestedChanges) {
|
|
112
158
|
cli.error(this.formatReview(reviewer, review));
|
|
113
159
|
}
|
|
114
160
|
}
|
|
115
161
|
|
|
116
162
|
if (approved.length === 0) {
|
|
117
|
-
|
|
163
|
+
const message = 'Approvals: 0';
|
|
164
|
+
cli.error(message);
|
|
165
|
+
this.addReason(PR_CHECK_REASON_CODES.MISSING_APPROVAL, message);
|
|
118
166
|
return;
|
|
119
167
|
}
|
|
120
168
|
|
|
@@ -166,7 +214,12 @@ export default class PRChecker {
|
|
|
166
214
|
.filter((p) => p.reviewer.isTSC())
|
|
167
215
|
.map((p) => p.reviewer.login);
|
|
168
216
|
if (tscApproved.length < 2) {
|
|
169
|
-
|
|
217
|
+
const message = 'semver-major requires at least 2 TSC approvals';
|
|
218
|
+
cli.error(message);
|
|
219
|
+
this.addReason(PR_CHECK_REASON_CODES.MISSING_TSC_APPROVAL, message, {
|
|
220
|
+
approvals: tscApproved.length,
|
|
221
|
+
required: 2
|
|
222
|
+
});
|
|
170
223
|
return false; // 7 day rule doesn't matter here
|
|
171
224
|
}
|
|
172
225
|
}
|
|
@@ -176,7 +229,12 @@ export default class PRChecker {
|
|
|
176
229
|
const comment = [...this.comments].reverse().find((c) =>
|
|
177
230
|
FAST_TRACK_RE.test(c.bodyText));
|
|
178
231
|
if (!comment) {
|
|
179
|
-
|
|
232
|
+
const message = 'Unable to find the fast-track request comment.';
|
|
233
|
+
cli.error(message);
|
|
234
|
+
this.addReason(
|
|
235
|
+
PR_CHECK_REASON_CODES.MISSING_FAST_TRACK_COMMENT,
|
|
236
|
+
message
|
|
237
|
+
);
|
|
180
238
|
return false;
|
|
181
239
|
}
|
|
182
240
|
const [, requester] = comment.bodyText.match(FAST_TRACK_RE);
|
|
@@ -221,9 +279,10 @@ export default class PRChecker {
|
|
|
221
279
|
if (timeLeftMulti < 0) {
|
|
222
280
|
return true;
|
|
223
281
|
}
|
|
224
|
-
|
|
225
|
-
`This PR needs to wait ${timeToText(timeLeftMulti, 'more')} to land${fastTrackAppendix}
|
|
226
|
-
);
|
|
282
|
+
const message =
|
|
283
|
+
`This PR needs to wait ${timeToText(timeLeftMulti, 'more')} to land${fastTrackAppendix}`;
|
|
284
|
+
cli.error(message);
|
|
285
|
+
this.addReason(PR_CHECK_REASON_CODES.WAIT_TIME, message);
|
|
227
286
|
return false;
|
|
228
287
|
}
|
|
229
288
|
|
|
@@ -231,9 +290,12 @@ export default class PRChecker {
|
|
|
231
290
|
if (timeLeftSingle < 0) {
|
|
232
291
|
return true;
|
|
233
292
|
}
|
|
234
|
-
|
|
293
|
+
const message =
|
|
294
|
+
`This PR needs to wait ${timeToText(timeLeftSingle, 'more')} to land (or ${
|
|
235
295
|
timeToText(timeLeftMulti < 0 || isFastTracked ? 0 : timeLeftMulti)
|
|
236
|
-
} if there is one more approval)${fastTrackAppendix}
|
|
296
|
+
} if there is one more approval)${fastTrackAppendix}`;
|
|
297
|
+
cli.error(message);
|
|
298
|
+
this.addReason(PR_CHECK_REASON_CODES.WAIT_TIME, message);
|
|
237
299
|
return false;
|
|
238
300
|
}
|
|
239
301
|
}
|
|
@@ -248,8 +310,12 @@ export default class PRChecker {
|
|
|
248
310
|
const providers = Object.values(CI_PROVIDERS);
|
|
249
311
|
|
|
250
312
|
if (!providers.includes(ciType)) {
|
|
251
|
-
|
|
252
|
-
`Invalid ciType ${ciType} - must be one of ${providers.join(', ')}
|
|
313
|
+
const message =
|
|
314
|
+
`Invalid ciType ${ciType} - must be one of ${providers.join(', ')}`;
|
|
315
|
+
this.cli.error(message);
|
|
316
|
+
this.addReason(PR_CHECK_REASON_CODES.INVALID_CI_TYPE, message, {
|
|
317
|
+
ciType
|
|
318
|
+
});
|
|
253
319
|
return false;
|
|
254
320
|
}
|
|
255
321
|
|
|
@@ -273,12 +339,20 @@ export default class PRChecker {
|
|
|
273
339
|
|
|
274
340
|
let status = true;
|
|
275
341
|
if (!ciMap.size) {
|
|
276
|
-
|
|
342
|
+
const message = 'No Jenkins CI runs detected';
|
|
343
|
+
cli.error(message);
|
|
344
|
+
this.addReason(PR_CHECK_REASON_CODES.MISSING_JENKINS_CI, message, {
|
|
345
|
+
provider: 'jenkins'
|
|
346
|
+
});
|
|
277
347
|
this.CIStatus = false;
|
|
278
348
|
return false;
|
|
279
349
|
} else if (!this.hasFullCI(ciMap)) {
|
|
280
350
|
status = false;
|
|
281
|
-
|
|
351
|
+
const message = 'No full Jenkins CI runs detected';
|
|
352
|
+
cli.error(message);
|
|
353
|
+
this.addReason(PR_CHECK_REASON_CODES.MISSING_FULL_JENKINS_CI, message, {
|
|
354
|
+
provider: 'jenkins'
|
|
355
|
+
});
|
|
282
356
|
}
|
|
283
357
|
|
|
284
358
|
let lastCI;
|
|
@@ -310,6 +384,10 @@ export default class PRChecker {
|
|
|
310
384
|
`${lastCI.typeName} CI run:`;
|
|
311
385
|
|
|
312
386
|
cli.warn(warnMsg);
|
|
387
|
+
this.addReason(PR_CHECK_REASON_CODES.STALE_CI, warnMsg, {
|
|
388
|
+
commits: totalCommits,
|
|
389
|
+
provider: 'jenkins'
|
|
390
|
+
});
|
|
313
391
|
const sliceLength = maxCommits === 0 ? totalCommits : -maxCommits;
|
|
314
392
|
afterCommits.slice(sliceLength)
|
|
315
393
|
.forEach(commit => {
|
|
@@ -329,13 +407,21 @@ export default class PRChecker {
|
|
|
329
407
|
const { result, failures } = await build.getResults();
|
|
330
408
|
|
|
331
409
|
if (result === 'FAILURE') {
|
|
332
|
-
|
|
333
|
-
`${failures.length} failure(s) on the last Jenkins CI run
|
|
410
|
+
const message =
|
|
411
|
+
`${failures.length} failure(s) on the last Jenkins CI run`;
|
|
412
|
+
cli.error(message);
|
|
413
|
+
this.addReason(PR_CHECK_REASON_CODES.FAILED_JENKINS_CI, message, {
|
|
414
|
+
failures: failures.length,
|
|
415
|
+
provider: 'jenkins'
|
|
416
|
+
});
|
|
334
417
|
status = false;
|
|
335
418
|
// NOTE(mmarchini): not sure why PEDING returns null
|
|
336
419
|
} else if (result === null) {
|
|
337
|
-
|
|
338
|
-
|
|
420
|
+
const message = 'Last Jenkins CI still running';
|
|
421
|
+
cli.error(message);
|
|
422
|
+
this.addReason(PR_CHECK_REASON_CODES.PENDING_JENKINS_CI, message, {
|
|
423
|
+
provider: 'jenkins'
|
|
424
|
+
});
|
|
339
425
|
status = false;
|
|
340
426
|
} else {
|
|
341
427
|
cli.ok('Last Jenkins CI successful');
|
|
@@ -350,7 +436,9 @@ export default class PRChecker {
|
|
|
350
436
|
const { cli, commits } = this;
|
|
351
437
|
|
|
352
438
|
if (!commits || commits.length === 0) {
|
|
353
|
-
|
|
439
|
+
const message = 'No commits detected';
|
|
440
|
+
cli.error(message);
|
|
441
|
+
this.addReason(PR_CHECK_REASON_CODES.NO_COMMITS, message);
|
|
354
442
|
return false;
|
|
355
443
|
}
|
|
356
444
|
|
|
@@ -361,7 +449,11 @@ export default class PRChecker {
|
|
|
361
449
|
this.CIStatus = false;
|
|
362
450
|
const checkSuites = commit.checkSuites || { nodes: [] };
|
|
363
451
|
if (!commit.status && checkSuites.nodes.length === 0) {
|
|
364
|
-
|
|
452
|
+
const message = 'No GitHub CI runs detected';
|
|
453
|
+
cli.error(message);
|
|
454
|
+
this.addReason(PR_CHECK_REASON_CODES.MISSING_GITHUB_CI, message, {
|
|
455
|
+
provider: 'github'
|
|
456
|
+
});
|
|
365
457
|
return false;
|
|
366
458
|
}
|
|
367
459
|
|
|
@@ -428,13 +520,23 @@ export default class PRChecker {
|
|
|
428
520
|
|
|
429
521
|
// Report pending jobs
|
|
430
522
|
if (pendingJobs.length > 0) {
|
|
431
|
-
|
|
523
|
+
const message = 'GitHub CI is still running';
|
|
524
|
+
cli.error(message);
|
|
525
|
+
this.addReason(PR_CHECK_REASON_CODES.PENDING_GITHUB_CI, message, {
|
|
526
|
+
jobs: pendingJobs.length,
|
|
527
|
+
provider: 'github'
|
|
528
|
+
});
|
|
432
529
|
return false;
|
|
433
530
|
}
|
|
434
531
|
|
|
435
532
|
// Report failed jobs
|
|
436
533
|
if (failedJobs.length > 0) {
|
|
437
|
-
|
|
534
|
+
const message = `${failedJobs.length} GitHub CI job(s) failed:`;
|
|
535
|
+
cli.error(message);
|
|
536
|
+
this.addReason(PR_CHECK_REASON_CODES.FAILED_GITHUB_CI, message, {
|
|
537
|
+
jobs: failedJobs.length,
|
|
538
|
+
provider: 'github'
|
|
539
|
+
});
|
|
438
540
|
for (const job of failedJobs) {
|
|
439
541
|
const urlInfo = job.url ? ` (${job.url})` : '';
|
|
440
542
|
cli.error(` - ${job.name}: ${job.conclusion}${urlInfo}`);
|
|
@@ -443,7 +545,12 @@ export default class PRChecker {
|
|
|
443
545
|
|
|
444
546
|
// Report cancelled jobs
|
|
445
547
|
if (cancelledJobs.length > 0) {
|
|
446
|
-
|
|
548
|
+
const message = `${cancelledJobs.length} GitHub CI job(s) cancelled:`;
|
|
549
|
+
cli.error(message);
|
|
550
|
+
this.addReason(PR_CHECK_REASON_CODES.CANCELLED_GITHUB_CI, message, {
|
|
551
|
+
jobs: cancelledJobs.length,
|
|
552
|
+
provider: 'github'
|
|
553
|
+
});
|
|
447
554
|
for (const job of cancelledJobs) {
|
|
448
555
|
const urlInfo = job.url ? ` (${job.url})` : '';
|
|
449
556
|
cli.error(` - ${job.name}: ${job.conclusion}${urlInfo}`);
|
|
@@ -458,12 +565,21 @@ export default class PRChecker {
|
|
|
458
565
|
if (commit.status) {
|
|
459
566
|
const { state } = commit.status;
|
|
460
567
|
if (state === 'PENDING') {
|
|
461
|
-
|
|
568
|
+
const message = 'GitHub CI is still running';
|
|
569
|
+
cli.error(message);
|
|
570
|
+
this.addReason(PR_CHECK_REASON_CODES.PENDING_GITHUB_CI, message, {
|
|
571
|
+
provider: 'github'
|
|
572
|
+
});
|
|
462
573
|
return false;
|
|
463
574
|
}
|
|
464
575
|
|
|
465
576
|
if (!['SUCCESS', 'EXPECTED'].includes(state)) {
|
|
466
|
-
|
|
577
|
+
const message = `GitHub CI failed with status: ${state}`;
|
|
578
|
+
cli.error(message);
|
|
579
|
+
this.addReason(PR_CHECK_REASON_CODES.FAILED_GITHUB_CI, message, {
|
|
580
|
+
provider: 'github',
|
|
581
|
+
state
|
|
582
|
+
});
|
|
467
583
|
return false;
|
|
468
584
|
}
|
|
469
585
|
}
|
|
@@ -534,7 +650,9 @@ export default class PRChecker {
|
|
|
534
650
|
}
|
|
535
651
|
|
|
536
652
|
const prAuthor = `${pr.author.login}(${pr.author.email})`;
|
|
537
|
-
|
|
653
|
+
const message = `PR author is a new contributor: @${prAuthor}`;
|
|
654
|
+
cli.warn(message);
|
|
655
|
+
this.addReason(PR_CHECK_REASON_CODES.NEW_CONTRIBUTOR, message);
|
|
538
656
|
for (const c of oddCommits) {
|
|
539
657
|
const { oid, author } = c.commit;
|
|
540
658
|
const hash = shortSha(oid);
|
|
@@ -594,7 +712,9 @@ export default class PRChecker {
|
|
|
594
712
|
const { maxCommits } = argv;
|
|
595
713
|
|
|
596
714
|
if (commits.length === 0) {
|
|
597
|
-
|
|
715
|
+
const message = 'No commits detected';
|
|
716
|
+
cli.warn(message);
|
|
717
|
+
this.addReason(PR_CHECK_REASON_CODES.NO_COMMITS, message);
|
|
598
718
|
return false;
|
|
599
719
|
}
|
|
600
720
|
|
|
@@ -603,7 +723,9 @@ export default class PRChecker {
|
|
|
603
723
|
);
|
|
604
724
|
|
|
605
725
|
if (reviewIndex === -1) {
|
|
606
|
-
|
|
726
|
+
const message = 'No approving reviews found';
|
|
727
|
+
cli.warn(message);
|
|
728
|
+
this.addReason(PR_CHECK_REASON_CODES.MISSING_APPROVAL, message);
|
|
607
729
|
return false;
|
|
608
730
|
}
|
|
609
731
|
|
|
@@ -611,7 +733,11 @@ export default class PRChecker {
|
|
|
611
733
|
.findLastIndex(({ commit }) => commit.oid === reviews[reviewIndex].commit.oid);
|
|
612
734
|
|
|
613
735
|
if (reviewedCommitIndex !== commits.length - 1) {
|
|
614
|
-
|
|
736
|
+
const message = 'Commits were pushed since the last approving review:';
|
|
737
|
+
cli.warn(message);
|
|
738
|
+
this.addReason(PR_CHECK_REASON_CODES.STALE_REVIEW, message, {
|
|
739
|
+
commits: commits.length - reviewedCommitIndex - 1
|
|
740
|
+
});
|
|
615
741
|
commits.slice(Math.max(reviewedCommitIndex + 1, commits.length - maxCommits))
|
|
616
742
|
.forEach(({ commit }) => {
|
|
617
743
|
cli.warn(`- ${commit.messageHeadline}`);
|
|
@@ -641,7 +767,9 @@ export default class PRChecker {
|
|
|
641
767
|
} = this;
|
|
642
768
|
|
|
643
769
|
if (pr.mergeable && pr.mergeable === CONFLICTING) {
|
|
644
|
-
|
|
770
|
+
const message = 'This PR has conflicts that must be resolved';
|
|
771
|
+
cli.warn(message);
|
|
772
|
+
this.addReason(PR_CHECK_REASON_CODES.CONFLICT, message);
|
|
645
773
|
return false;
|
|
646
774
|
}
|
|
647
775
|
|
|
@@ -656,13 +784,17 @@ export default class PRChecker {
|
|
|
656
784
|
|
|
657
785
|
if (merged) {
|
|
658
786
|
const dateStr = new Date(mergedAt).toUTCString();
|
|
659
|
-
|
|
787
|
+
const message = `This PR was merged on ${dateStr}`;
|
|
788
|
+
cli.warn(message);
|
|
789
|
+
this.addReason(PR_CHECK_REASON_CODES.MERGED, message);
|
|
660
790
|
return false;
|
|
661
791
|
}
|
|
662
792
|
|
|
663
793
|
if (closed) {
|
|
664
794
|
const dateStr = new Date(closedAt).toUTCString();
|
|
665
|
-
|
|
795
|
+
const message = `This PR was closed on ${dateStr}`;
|
|
796
|
+
cli.warn(message);
|
|
797
|
+
this.addReason(PR_CHECK_REASON_CODES.CLOSED, message);
|
|
666
798
|
return false;
|
|
667
799
|
}
|
|
668
800
|
|
package/lib/pr_data.js
CHANGED
package/lib/pr_summary.js
CHANGED