@node-core/utils 6.2.1 → 6.3.1
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/bin/ncu-ci.js +9 -1
- package/components/git/land.js +5 -9
- package/components/git/security.js +23 -40
- package/lib/auth.js +7 -11
- package/lib/ci/build-types/pr_build.js +1 -2
- package/lib/ci/run_ci.js +25 -6
- package/lib/landing_session.js +70 -43
- package/lib/metadata_gen.js +4 -12
- package/lib/prepare_release.js +28 -96
- package/lib/prepare_security.js +7 -11
- package/lib/request.js +15 -36
- package/lib/session.js +5 -2
- package/lib/voting_session.js +13 -18
- package/npm-shrinkwrap.json +463 -377
- package/package.json +4 -4
package/bin/ncu-ci.js
CHANGED
|
@@ -119,6 +119,11 @@ const args = yargs(hideBin(process.argv))
|
|
|
119
119
|
'If not provided, the command will use the SHA of the last approved commit.',
|
|
120
120
|
type: 'string'
|
|
121
121
|
})
|
|
122
|
+
.option('check-for-duplicates', {
|
|
123
|
+
describe: 'When set, NCU will query Jenkins recent builds to ensure ' +
|
|
124
|
+
'there is not an existing job for the same commit.',
|
|
125
|
+
type: 'boolean'
|
|
126
|
+
})
|
|
122
127
|
.option('owner', {
|
|
123
128
|
default: '',
|
|
124
129
|
describe: 'GitHub repository owner'
|
|
@@ -298,7 +303,10 @@ class RunPRJobCommand {
|
|
|
298
303
|
this.cli.setExitCode(1);
|
|
299
304
|
return;
|
|
300
305
|
}
|
|
301
|
-
const
|
|
306
|
+
const { certifySafe, checkForDuplicates } = this.argv;
|
|
307
|
+
const jobRunner = new RunPRJob(
|
|
308
|
+
cli, request, owner, repo, prid,
|
|
309
|
+
certifySafe, checkForDuplicates);
|
|
302
310
|
if (!(await jobRunner.start())) {
|
|
303
311
|
this.cli.setExitCode(1);
|
|
304
312
|
process.exitCode = 1;
|
package/components/git/land.js
CHANGED
|
@@ -189,6 +189,7 @@ async function main(state, argv, cli, dir) {
|
|
|
189
189
|
cli.warn(
|
|
190
190
|
'Failed to detect previous session. ' +
|
|
191
191
|
'please run `git node land --abort`');
|
|
192
|
+
cli.setErrorCode(1);
|
|
192
193
|
return;
|
|
193
194
|
}
|
|
194
195
|
|
|
@@ -197,17 +198,12 @@ async function main(state, argv, cli, dir) {
|
|
|
197
198
|
cli.warn(
|
|
198
199
|
'Previous `git node land` session for ' +
|
|
199
200
|
`${session.pullName} in progress.`);
|
|
200
|
-
cli.
|
|
201
|
-
|
|
201
|
+
const response = await cli.prompt('Do you want to abort it?', { defaultAnswer: false });
|
|
202
|
+
if (!response) throw new Error('Please run `git node land --abort`');
|
|
203
|
+
await session.abort();
|
|
204
|
+
session = new LandingSession(cli, req, dir, argv);
|
|
202
205
|
}
|
|
203
|
-
session = new LandingSession(cli, req, dir, argv);
|
|
204
206
|
const metadata = await getMetadata(session.argv, argv.skipRefs, cli);
|
|
205
|
-
if (argv.backport) {
|
|
206
|
-
const split = metadata.metadata.split('\n')[0];
|
|
207
|
-
if (split === 'PR-URL: ') {
|
|
208
|
-
cli.error('Commit message is missing PR-URL');
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
207
|
return session.start(metadata);
|
|
212
208
|
} else if (state === APPLY) {
|
|
213
209
|
return session.apply();
|
|
@@ -92,110 +92,93 @@ export function builder(yargs) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
export function handler(argv) {
|
|
95
|
+
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
96
|
+
const cli = new CLI(logStream);
|
|
97
|
+
|
|
95
98
|
if (argv.start) {
|
|
96
|
-
return startSecurityRelease(argv);
|
|
99
|
+
return startSecurityRelease(cli, argv);
|
|
97
100
|
}
|
|
98
101
|
if (argv.sync) {
|
|
99
|
-
return syncSecurityRelease(argv);
|
|
102
|
+
return syncSecurityRelease(cli, argv);
|
|
100
103
|
}
|
|
101
104
|
if (argv['update-date']) {
|
|
102
|
-
return updateReleaseDate(argv);
|
|
105
|
+
return updateReleaseDate(cli, argv);
|
|
103
106
|
}
|
|
104
107
|
if (argv['pre-release']) {
|
|
105
|
-
return createPreRelease(argv);
|
|
108
|
+
return createPreRelease(cli, argv);
|
|
106
109
|
}
|
|
107
110
|
if (argv['add-report']) {
|
|
108
|
-
return addReport(argv);
|
|
111
|
+
return addReport(cli, argv);
|
|
109
112
|
}
|
|
110
113
|
if (argv['remove-report']) {
|
|
111
|
-
return removeReport(argv);
|
|
114
|
+
return removeReport(cli, argv);
|
|
112
115
|
}
|
|
113
116
|
if (argv['notify-pre-release']) {
|
|
114
|
-
return notifyPreRelease(argv);
|
|
117
|
+
return notifyPreRelease(cli, argv);
|
|
115
118
|
}
|
|
116
119
|
if (argv['request-cve']) {
|
|
117
|
-
return requestCVEs(argv);
|
|
120
|
+
return requestCVEs(cli, argv);
|
|
118
121
|
}
|
|
119
122
|
if (argv['post-release']) {
|
|
120
|
-
return createPostRelease(argv);
|
|
123
|
+
return createPostRelease(cli, argv);
|
|
121
124
|
}
|
|
122
125
|
if (argv.cleanup) {
|
|
123
|
-
return cleanupSecurityRelease(argv);
|
|
126
|
+
return cleanupSecurityRelease(cli, argv);
|
|
124
127
|
}
|
|
125
128
|
yargsInstance.showHelp();
|
|
126
129
|
}
|
|
127
130
|
|
|
128
|
-
async function removeReport(argv) {
|
|
131
|
+
async function removeReport(cli, argv) {
|
|
129
132
|
const reportId = argv['remove-report'];
|
|
130
|
-
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
131
|
-
const cli = new CLI(logStream);
|
|
132
133
|
const update = new UpdateSecurityRelease(cli);
|
|
133
134
|
return update.removeReport(reportId);
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
async function addReport(argv) {
|
|
137
|
+
async function addReport(cli, argv) {
|
|
137
138
|
const reportId = argv['add-report'];
|
|
138
|
-
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
139
|
-
const cli = new CLI(logStream);
|
|
140
139
|
const update = new UpdateSecurityRelease(cli);
|
|
141
140
|
return update.addReport(reportId);
|
|
142
141
|
}
|
|
143
142
|
|
|
144
|
-
async function updateReleaseDate(argv) {
|
|
143
|
+
async function updateReleaseDate(cli, argv) {
|
|
145
144
|
const releaseDate = argv['update-date'];
|
|
146
|
-
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
147
|
-
const cli = new CLI(logStream);
|
|
148
145
|
const update = new UpdateSecurityRelease(cli);
|
|
149
146
|
return update.updateReleaseDate(releaseDate);
|
|
150
147
|
}
|
|
151
148
|
|
|
152
|
-
async function createPreRelease(argv) {
|
|
149
|
+
async function createPreRelease(cli, argv) {
|
|
153
150
|
const nodejsOrgFolder = argv['pre-release'];
|
|
154
|
-
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
155
|
-
const cli = new CLI(logStream);
|
|
156
151
|
const preRelease = new SecurityBlog(cli);
|
|
157
152
|
return preRelease.createPreRelease(nodejsOrgFolder);
|
|
158
153
|
}
|
|
159
154
|
|
|
160
|
-
async function requestCVEs() {
|
|
161
|
-
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
162
|
-
const cli = new CLI(logStream);
|
|
155
|
+
async function requestCVEs(cli) {
|
|
163
156
|
const hackerOneCve = new UpdateSecurityRelease(cli);
|
|
164
157
|
return hackerOneCve.requestCVEs();
|
|
165
158
|
}
|
|
166
159
|
|
|
167
|
-
async function createPostRelease(argv) {
|
|
160
|
+
async function createPostRelease(cli, argv) {
|
|
168
161
|
const nodejsOrgFolder = argv['post-release'];
|
|
169
|
-
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
170
|
-
const cli = new CLI(logStream);
|
|
171
162
|
const blog = new SecurityBlog(cli);
|
|
172
163
|
return blog.createPostRelease(nodejsOrgFolder);
|
|
173
164
|
}
|
|
174
165
|
|
|
175
|
-
async function startSecurityRelease() {
|
|
176
|
-
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
177
|
-
const cli = new CLI(logStream);
|
|
166
|
+
async function startSecurityRelease(cli) {
|
|
178
167
|
const release = new PrepareSecurityRelease(cli);
|
|
179
168
|
return release.start();
|
|
180
169
|
}
|
|
181
170
|
|
|
182
|
-
async function cleanupSecurityRelease() {
|
|
183
|
-
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
184
|
-
const cli = new CLI(logStream);
|
|
171
|
+
async function cleanupSecurityRelease(cli) {
|
|
185
172
|
const release = new PrepareSecurityRelease(cli);
|
|
186
173
|
return release.cleanup();
|
|
187
174
|
}
|
|
188
175
|
|
|
189
|
-
async function syncSecurityRelease(
|
|
190
|
-
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
191
|
-
const cli = new CLI(logStream);
|
|
176
|
+
async function syncSecurityRelease(cli) {
|
|
192
177
|
const release = new UpdateSecurityRelease(cli);
|
|
193
178
|
return release.sync();
|
|
194
179
|
}
|
|
195
180
|
|
|
196
|
-
async function notifyPreRelease() {
|
|
197
|
-
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
198
|
-
const cli = new CLI(logStream);
|
|
181
|
+
async function notifyPreRelease(cli) {
|
|
199
182
|
const preRelease = new SecurityAnnouncement(cli);
|
|
200
183
|
return preRelease.notifyPreRelease();
|
|
201
184
|
}
|
package/lib/auth.js
CHANGED
|
@@ -1,32 +1,28 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { ClientRequest } from 'node:http';
|
|
3
3
|
|
|
4
|
+
// eslint-disable-next-line import/no-unresolved
|
|
4
5
|
import ghauth from 'ghauth';
|
|
5
6
|
|
|
6
7
|
import { clearCachedConfig, encryptValue, getMergedConfig, getNcurcPath } from './config.js';
|
|
7
8
|
|
|
8
9
|
export default lazy(auth);
|
|
9
10
|
|
|
10
|
-
function errorExit(message) {
|
|
11
|
-
process.stderr.write(`${message}\n`);
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
11
|
function check(username, token, format = /^[A-Za-z0-9_]+$/) {
|
|
16
12
|
if (typeof username !== 'string') {
|
|
17
|
-
|
|
13
|
+
throw new Error(`username must be a string, received ${typeof username}`);
|
|
18
14
|
}
|
|
19
15
|
if (!/^[a-zA-Z0-9-]+$/.test(username)) {
|
|
20
|
-
|
|
16
|
+
throw new Error(
|
|
21
17
|
'username may only contain alphanumeric characters or hyphens, ' +
|
|
22
18
|
`received ${username}`
|
|
23
19
|
);
|
|
24
20
|
}
|
|
25
21
|
if (typeof token !== 'string') {
|
|
26
|
-
|
|
22
|
+
throw new Error(`token must be a string, received ${typeof token}`);
|
|
27
23
|
}
|
|
28
24
|
if (!format.test(token)) {
|
|
29
|
-
|
|
25
|
+
throw new Error(`token is misformatted: ${token}`);
|
|
30
26
|
}
|
|
31
27
|
}
|
|
32
28
|
|
|
@@ -51,7 +47,7 @@ async function tryCreateGitHubToken(githubAuth) {
|
|
|
51
47
|
noDeviceFlow: true
|
|
52
48
|
});
|
|
53
49
|
} catch (e) {
|
|
54
|
-
|
|
50
|
+
throw new Error(`Could not get token: ${e.message}`, { cause: e });
|
|
55
51
|
}
|
|
56
52
|
return credentials;
|
|
57
53
|
}
|
|
@@ -93,7 +89,7 @@ async function auth(
|
|
|
93
89
|
get jenkins() {
|
|
94
90
|
const { username, jenkins_token } = getMergedConfig();
|
|
95
91
|
if (!username || !jenkins_token) {
|
|
96
|
-
|
|
92
|
+
throw new Error(
|
|
97
93
|
'Get your Jenkins API token in https://ci.nodejs.org/me/security ' +
|
|
98
94
|
'and run the following command to add it to your ncu config: ' +
|
|
99
95
|
'ncu-config --global set -x jenkins_token'
|
|
@@ -15,9 +15,8 @@ const {
|
|
|
15
15
|
} = CIFailureParser;
|
|
16
16
|
|
|
17
17
|
export class PRBuild extends TestBuild {
|
|
18
|
-
constructor(cli, request, id, skipMoreThan) {
|
|
18
|
+
constructor(cli, request, id, skipMoreThan, tree = PR_TREE) {
|
|
19
19
|
const path = `job/node-test-pull-request/${id}/`;
|
|
20
|
-
const tree = PR_TREE;
|
|
21
20
|
super(cli, request, path, tree);
|
|
22
21
|
this.skipMoreThan = skipMoreThan;
|
|
23
22
|
this.commitBuild = null;
|
package/lib/ci/run_ci.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FormData } from 'undici';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
JobParser,
|
|
4
5
|
CI_DOMAIN,
|
|
5
6
|
CI_TYPES,
|
|
6
7
|
CI_TYPES_KEYS
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
import PRData from '../pr_data.js';
|
|
9
10
|
import { debuglog } from '../verbosity.js';
|
|
10
11
|
import PRChecker from '../pr_checker.js';
|
|
12
|
+
import { PRBuild } from './build-types/pr_build.js';
|
|
11
13
|
|
|
12
14
|
export const CI_CRUMB_URL = `https://${CI_DOMAIN}/crumbIssuer/api/json`;
|
|
13
15
|
const CI_PR_NAME = CI_TYPES.get(CI_TYPES_KEYS.PR).jobName;
|
|
@@ -17,7 +19,7 @@ const CI_V8_NAME = CI_TYPES.get(CI_TYPES_KEYS.V8).jobName;
|
|
|
17
19
|
export const CI_V8_URL = `https://${CI_DOMAIN}/job/${CI_V8_NAME}/build`;
|
|
18
20
|
|
|
19
21
|
export class RunPRJob {
|
|
20
|
-
constructor(cli, request, owner, repo, prid, certifySafe) {
|
|
22
|
+
constructor(cli, request, owner, repo, prid, certifySafe, checkForDuplicates) {
|
|
21
23
|
this.cli = cli;
|
|
22
24
|
this.request = request;
|
|
23
25
|
this.owner = owner;
|
|
@@ -29,6 +31,7 @@ export class RunPRJob {
|
|
|
29
31
|
Promise.all([this.prData.getReviews(), this.prData.getCommits()]).then(() =>
|
|
30
32
|
(this.certifySafe = new PRChecker(cli, this.prData, request, {}).getApprovedTipOfHead())
|
|
31
33
|
);
|
|
34
|
+
this.checkForDuplicates = checkForDuplicates;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
async getCrumb() {
|
|
@@ -70,7 +73,7 @@ export class RunPRJob {
|
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
async start() {
|
|
73
|
-
const { cli, certifySafe } = this;
|
|
76
|
+
const { cli, request, certifySafe, checkForDuplicates } = this;
|
|
74
77
|
|
|
75
78
|
if (!(await certifySafe)) {
|
|
76
79
|
cli.error('Refusing to run CI on potentially unsafe PR');
|
|
@@ -87,9 +90,25 @@ export class RunPRJob {
|
|
|
87
90
|
}
|
|
88
91
|
cli.stopSpinner('Jenkins credentials valid');
|
|
89
92
|
|
|
93
|
+
if (checkForDuplicates) {
|
|
94
|
+
await this.prData.getComments();
|
|
95
|
+
const { jobid, link } = new JobParser(this.prData.comments).parse().get('PR') ?? {};
|
|
96
|
+
const { actions } = jobid
|
|
97
|
+
? (await new PRBuild(cli, request, jobid, undefined, 'actions[parameters[name,value]]')
|
|
98
|
+
.getBuildData())
|
|
99
|
+
: {};
|
|
100
|
+
const { parameters } = actions?.find(a => 'parameters' in a) ?? {};
|
|
101
|
+
if (parameters?.find(c => c.name === 'COMMIT_SHA_CHECK')?.value === certifySafe) {
|
|
102
|
+
cli.info('Existing CI run found: ' + link);
|
|
103
|
+
cli.error('Refusing to start a potentially duplicate CI job. Use the ' +
|
|
104
|
+
'"Resume build" button in the Jenkins UI, or start a new CI manually.');
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
90
109
|
try {
|
|
91
110
|
cli.startSpinner('Starting PR CI job');
|
|
92
|
-
const response = await
|
|
111
|
+
const response = await request.fetch(CI_PR_URL, {
|
|
93
112
|
method: 'POST',
|
|
94
113
|
headers: {
|
|
95
114
|
'Jenkins-Crumb': crumb
|
|
@@ -106,10 +125,10 @@ export class RunPRJob {
|
|
|
106
125
|
|
|
107
126
|
// check if the job need a v8 build and trigger it
|
|
108
127
|
await this.prData.getPR();
|
|
109
|
-
const labels = this.prData.pr
|
|
110
|
-
if (labels
|
|
128
|
+
const { labels } = this.prData.pr;
|
|
129
|
+
if (labels?.nodes.some(i => i.name === 'v8 engine')) {
|
|
111
130
|
cli.startSpinner('Starting V8 CI job');
|
|
112
|
-
const response = await
|
|
131
|
+
const response = await request.fetch(CI_V8_URL, {
|
|
113
132
|
method: 'POST',
|
|
114
133
|
headers: {
|
|
115
134
|
'Jenkins-Crumb': crumb
|
package/lib/landing_session.js
CHANGED
|
@@ -82,12 +82,38 @@ export default class LandingSession extends Session {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
async downloadAndPatch() {
|
|
85
|
-
const { cli, upstream, prid, expectedCommitShas } = this;
|
|
85
|
+
const { cli, upstream, prid, expectedCommitShas, crossRepoPR } = this;
|
|
86
|
+
|
|
87
|
+
if (crossRepoPR) {
|
|
88
|
+
const { owner, repo } = this;
|
|
89
|
+
cli.warn(`The configured remote "${upstream}" does not point to the ` +
|
|
90
|
+
'Pull Request repository.');
|
|
91
|
+
const isHeadAMergeCommit = () => {
|
|
92
|
+
try {
|
|
93
|
+
return runSync('git', ['rev-parse', 'FETCH_HEAD^2']).trim();
|
|
94
|
+
} catch {}
|
|
95
|
+
};
|
|
96
|
+
do {
|
|
97
|
+
cli.warn('Fetching commits from a different upstream might require user intervention.');
|
|
98
|
+
|
|
99
|
+
cli.info('Run the following command to fetch the commit(s):');
|
|
100
|
+
cli.info(`git fetch git@github.com:${owner}/${repo}.git refs/pull/${prid}/merge`);
|
|
101
|
+
const response = await cli.prompt('Should NCU run the command for you?',
|
|
102
|
+
{ defaultAnswer: false });
|
|
103
|
+
if (response) {
|
|
104
|
+
cli.startSpinner(`Downloading patch for ${prid}`);
|
|
105
|
+
await forceRunAsync('git',
|
|
106
|
+
['fetch', `git@github.com:${owner}/${repo}.git`, `refs/pull/${prid}/merge`],
|
|
107
|
+
{ ignoreFailure: false });
|
|
108
|
+
}
|
|
109
|
+
} while (!isHeadAMergeCommit());
|
|
110
|
+
} else {
|
|
111
|
+
cli.startSpinner(`Downloading patch for ${prid}`);
|
|
112
|
+
await runAsync('git', [
|
|
113
|
+
'fetch', upstream,
|
|
114
|
+
`refs/pull/${prid}/merge`]);
|
|
115
|
+
}
|
|
86
116
|
|
|
87
|
-
cli.startSpinner(`Downloading patch for ${prid}`);
|
|
88
|
-
await runAsync('git', [
|
|
89
|
-
'fetch', upstream,
|
|
90
|
-
`refs/pull/${prid}/merge`]);
|
|
91
117
|
// We fetched the commit that would result if we used `git merge`.
|
|
92
118
|
// ^1 and ^2 refer to the PR base and the PR head, respectively.
|
|
93
119
|
const [base, head] = await runAsync('git',
|
|
@@ -286,6 +312,12 @@ export default class LandingSession extends Session {
|
|
|
286
312
|
cli.warn('Not yet ready to amend, run `git node land --abort`');
|
|
287
313
|
return;
|
|
288
314
|
}
|
|
315
|
+
|
|
316
|
+
const BACKPORT_RE = /^BACKPORT-PR-URL\s*:\s*(\S+)$/i;
|
|
317
|
+
const PR_RE = /^PR-URL\s*:\s*(\S+)$/i;
|
|
318
|
+
const REVIEW_RE = /^Reviewed-By\s*:\s*(\S+)$/i;
|
|
319
|
+
const CVE_RE = /^CVE-ID\s*:\s*(\S+)$/im;
|
|
320
|
+
|
|
289
321
|
this.startAmending();
|
|
290
322
|
|
|
291
323
|
const rev = this.getCurrentRev();
|
|
@@ -296,52 +328,40 @@ export default class LandingSession extends Session {
|
|
|
296
328
|
// git has very specific rules about what is a trailer and what is not.
|
|
297
329
|
// Instead of trying to implement those ourselves, let git parse the
|
|
298
330
|
// original commit message and see if it outputs any trailers.
|
|
299
|
-
const
|
|
331
|
+
const originalTrailers = runSync('git', [
|
|
300
332
|
'interpret-trailers', '--parse', '--no-divider'
|
|
301
333
|
], {
|
|
302
334
|
input: `${original}\n`
|
|
303
|
-
})
|
|
335
|
+
});
|
|
336
|
+
const containCVETrailer = CVE_RE.test(originalTrailers);
|
|
304
337
|
|
|
305
338
|
const metadata = this.metadata.trim().split('\n');
|
|
306
|
-
const amended = original.split('\n');
|
|
307
|
-
|
|
308
|
-
// If the original commit message already contains trailers (such as
|
|
309
|
-
// "Co-authored-by"), we simply add our own metadata after those. Otherwise,
|
|
310
|
-
// we have to add an empty line so that git recognizes our own metadata as
|
|
311
|
-
// trailers in the amended commit message.
|
|
312
|
-
if (!originalHasTrailers) {
|
|
313
|
-
amended.push('');
|
|
314
|
-
}
|
|
339
|
+
const amended = original.slice(0, -originalTrailers.length).trim().split('\n');
|
|
315
340
|
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
341
|
+
// Only keep existing trailers that we won't add ourselves
|
|
342
|
+
const keptTrailers = originalTrailers.split('\n').filter(line =>
|
|
343
|
+
!!line &&
|
|
344
|
+
(!PR_RE.test(line) || this.backport) &&
|
|
345
|
+
!BACKPORT_RE.test(line) &&
|
|
346
|
+
!REVIEW_RE.test(line));
|
|
347
|
+
amended.push('', ...keptTrailers);
|
|
320
348
|
|
|
321
|
-
let containCVETrailer = false;
|
|
322
349
|
for (const line of metadata) {
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (line.match(BACKPORT_RE)) {
|
|
336
|
-
let prIndex = amended.findIndex(datum => datum.match(PR_RE));
|
|
337
|
-
if (prIndex === -1) {
|
|
338
|
-
prIndex = amended.findIndex(datum => datum.match(REVIEW_RE)) - 1;
|
|
339
|
-
}
|
|
340
|
-
amended.splice(prIndex + 1, 0, line);
|
|
341
|
-
} else {
|
|
342
|
-
amended.push(line);
|
|
350
|
+
if (keptTrailers.includes(line)) {
|
|
351
|
+
cli.warn(`Found ${line}, skipping..`);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (BACKPORT_RE.test(line)) {
|
|
356
|
+
const prIndex =
|
|
357
|
+
(amended.findIndex(datum => PR_RE.test(datum)) + 1) ||
|
|
358
|
+
amended.findIndex(datum => REVIEW_RE.test(datum));
|
|
359
|
+
if (prIndex !== -1) {
|
|
360
|
+
amended.splice(prIndex, 0, line);
|
|
361
|
+
continue;
|
|
343
362
|
}
|
|
344
363
|
}
|
|
364
|
+
amended.push(line);
|
|
345
365
|
}
|
|
346
366
|
|
|
347
367
|
if (!containCVETrailer && this.includeCVE) {
|
|
@@ -472,7 +492,7 @@ export default class LandingSession extends Session {
|
|
|
472
492
|
}
|
|
473
493
|
|
|
474
494
|
cli.separator();
|
|
475
|
-
cli.log('The following commits are
|
|
495
|
+
cli.log('The following commits are landable on ' +
|
|
476
496
|
`${upstream}/${branch}`);
|
|
477
497
|
cli.log(`- ${strayVerbose.join('\n- ')}`);
|
|
478
498
|
cli.separator();
|
|
@@ -483,8 +503,15 @@ export default class LandingSession extends Session {
|
|
|
483
503
|
willBeLanded = `${head}...${willBeLanded}`;
|
|
484
504
|
}
|
|
485
505
|
|
|
506
|
+
cli.startSpinner('Removing temporary files');
|
|
486
507
|
this.cleanFiles();
|
|
487
|
-
cli.
|
|
508
|
+
cli.stopSpinner('Temporary files removed.');
|
|
509
|
+
if (this.crossRepoPR) {
|
|
510
|
+
cli.warn(`The configured remote "${upstream}" does not point to the ` +
|
|
511
|
+
'Pull Request repository.');
|
|
512
|
+
cli.info(`${willBeLanded} should likely not be pushed to "${upstream}"`);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
488
515
|
cli.log('To finish landing:');
|
|
489
516
|
cli.log('1. Run: ');
|
|
490
517
|
cli.log(` git push ${upstream} ${branch}`);
|
package/lib/metadata_gen.js
CHANGED
|
@@ -31,7 +31,6 @@ export default class MetadataGenerator {
|
|
|
31
31
|
const parser = new LinkParser(owner, repo, op);
|
|
32
32
|
const fixes = parser.getFixes();
|
|
33
33
|
const refs = parser.getRefs().filter(f => f !== prUrl);
|
|
34
|
-
const altPrUrl = parser.getAltPrUrl();
|
|
35
34
|
|
|
36
35
|
const meta = [];
|
|
37
36
|
|
|
@@ -44,17 +43,10 @@ export default class MetadataGenerator {
|
|
|
44
43
|
meta.push(...refs.map((ref) => `Refs: ${ref}`));
|
|
45
44
|
}
|
|
46
45
|
const backport = this.argv ? this.argv.backport : undefined;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// Reviews are only added here as backports should not contain reviews
|
|
52
|
-
// for the backport itself in the metadata
|
|
53
|
-
meta.unshift(`PR-URL: ${prUrl}`);
|
|
54
|
-
meta.push(
|
|
55
|
-
...reviewedBy.map((r) => `Reviewed-By: ${r.reviewer.getContact()}`)
|
|
56
|
-
);
|
|
57
|
-
}
|
|
46
|
+
meta.unshift(`${backport ? 'Backport-' : ''}PR-URL: ${prUrl}`);
|
|
47
|
+
meta.push(
|
|
48
|
+
...reviewedBy.map((r) => `Reviewed-By: ${r.reviewer.getContact()}`)
|
|
49
|
+
);
|
|
58
50
|
meta.push(''); // creates final EOL
|
|
59
51
|
return meta.join('\n');
|
|
60
52
|
}
|
package/lib/prepare_release.js
CHANGED
|
@@ -133,88 +133,12 @@ export default class ReleasePreparation extends Session {
|
|
|
133
133
|
return true;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
async prepareSecurity() {
|
|
137
|
-
const {
|
|
138
|
-
cli,
|
|
139
|
-
newVersion,
|
|
140
|
-
versionComponents,
|
|
141
|
-
releaseBranch,
|
|
142
|
-
filterLabels
|
|
143
|
-
} = this;
|
|
144
|
-
|
|
145
|
-
// Create new proposal branch.
|
|
146
|
-
cli.startSpinner(`Switching to proposal branch for ${newVersion}`);
|
|
147
|
-
const proposalBranch = await this.createProposalBranch(releaseBranch);
|
|
148
|
-
cli.stopSpinner(`Switched to proposal branch for ${newVersion}`);
|
|
149
|
-
|
|
150
|
-
const success = await this.cherryPickSecurityPRs(filterLabels);
|
|
151
|
-
if (!success) {
|
|
152
|
-
cli.error('Aborting security release preparation. ' +
|
|
153
|
-
'Remember to exclude the proposal branch.' +
|
|
154
|
-
'git branch -D ' + proposalBranch);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
// Update version and release info in src/node_version.h.
|
|
158
|
-
cli.startSpinner(`Updating 'src/node_version.h' for ${newVersion}`);
|
|
159
|
-
await this.updateNodeVersion();
|
|
160
|
-
cli.stopSpinner(`Updated 'src/node_version.h' for ${newVersion}`);
|
|
161
|
-
|
|
162
|
-
// Update any REPLACEME tags in the docs.
|
|
163
|
-
cli.startSpinner('Updating REPLACEME items in docs');
|
|
164
|
-
await this.updateREPLACEMEs();
|
|
165
|
-
cli.stopSpinner('Updated REPLACEME items in docs');
|
|
166
|
-
|
|
167
|
-
// Fetch date to use in release commit & changelogs.
|
|
168
|
-
const todayDate = new Date().toISOString().split('T')[0];
|
|
169
|
-
this.date = await cli.prompt('Enter release date in YYYY-MM-DD format:',
|
|
170
|
-
{ questionType: 'input', defaultAnswer: todayDate });
|
|
171
|
-
|
|
172
|
-
cli.startSpinner('Updating CHANGELOG.md');
|
|
173
|
-
await this.updateMainChangelog();
|
|
174
|
-
cli.stopSpinner('Updated CHANGELOG.md');
|
|
175
|
-
|
|
176
|
-
cli.startSpinner(`Updating CHANGELOG_V${versionComponents.major}.md`);
|
|
177
|
-
await this.updateMajorChangelog();
|
|
178
|
-
cli.stopSpinner(`Updated CHANGELOG_V${versionComponents.major}.md`);
|
|
179
|
-
|
|
180
|
-
// Create release commit.
|
|
181
|
-
const shouldCreateReleaseCommit = await cli.prompt(
|
|
182
|
-
'Create release commit?');
|
|
183
|
-
if (!shouldCreateReleaseCommit) {
|
|
184
|
-
cli.warn(`Aborting \`git node release\` for version ${newVersion}`);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Proceed with release only after the releaser has amended
|
|
189
|
-
// it to their liking.
|
|
190
|
-
const createDefaultCommit = await this.createReleaseCommit();
|
|
191
|
-
if (!createDefaultCommit) {
|
|
192
|
-
const lastCommitSha = runSync('git', ['rev-parse', '--short', 'HEAD']);
|
|
193
|
-
cli.warn(`Please manually edit commit ${lastCommitSha} by running ` +
|
|
194
|
-
'`git commit --amend` before proceeding.');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
cli.separator();
|
|
198
|
-
cli.ok(`Release preparation for ${newVersion} complete.\n`);
|
|
199
|
-
cli.info(
|
|
200
|
-
'To finish the release proposal, run: \n' +
|
|
201
|
-
` $ git push -u ${this.upstream} v${newVersion}-proposal\n` +
|
|
202
|
-
'Finally, proceed to Jenkins and begin the following CI jobs:\n' +
|
|
203
|
-
' * https://ci.nodejs.org/job/node-test-pull-request/\n' +
|
|
204
|
-
' * https://ci.nodejs.org/job/citgm-smoker/');
|
|
205
|
-
cli.info(
|
|
206
|
-
'If this release has deps/v8 changes, you\'ll also need to run:\n' +
|
|
207
|
-
' * https://ci.nodejs.org/job/node-test-commit-v8-linux/'
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
136
|
async prepare() {
|
|
212
137
|
const { cli, newVersion, versionComponents, isSecurityRelease } = this;
|
|
213
138
|
|
|
214
139
|
if (isSecurityRelease) {
|
|
215
140
|
this.config.owner = 'nodejs-private';
|
|
216
141
|
this.config.repo = 'node-private';
|
|
217
|
-
return this.prepareSecurity();
|
|
218
142
|
}
|
|
219
143
|
|
|
220
144
|
const runBranchDiff = await cli.prompt(
|
|
@@ -222,30 +146,38 @@ export default class ReleasePreparation extends Session {
|
|
|
222
146
|
'(recommended except for Maintenance releases)?',
|
|
223
147
|
{ defaultAnswer: this.runBranchDiff });
|
|
224
148
|
if (runBranchDiff) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
149
|
+
if (isSecurityRelease) {
|
|
150
|
+
const success = await this.cherryPickSecurityPRs(this.filterLabels);
|
|
151
|
+
if (!success) {
|
|
152
|
+
cli.error('Aborting security release preparation.');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
// TODO: UPDATE re-use
|
|
157
|
+
// Check the branch diff to determine if the releaser
|
|
158
|
+
// wants to backport any more commits before proceeding.
|
|
159
|
+
cli.startSpinner('Fetching branch-diff');
|
|
160
|
+
const raw = await this.getBranchDiff({
|
|
161
|
+
onlyNotableChanges: false,
|
|
162
|
+
comparisonBranch: newVersion
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const diff = raw.split('*');
|
|
166
|
+
cli.stopSpinner('Got branch diff');
|
|
167
|
+
|
|
168
|
+
const outstandingCommits = diff.length - 1;
|
|
169
|
+
if (outstandingCommits !== 0) {
|
|
170
|
+
const proceed = await cli.prompt(
|
|
240
171
|
`There are ${outstandingCommits} commits that may be ` +
|
|
241
172
|
`backported to ${this.stagingBranch} - do you still want to proceed?`,
|
|
242
173
|
{ defaultAnswer: false });
|
|
243
174
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
175
|
+
if (!proceed) {
|
|
176
|
+
const seeDiff = await cli.prompt(
|
|
177
|
+
'Do you want to see the branch diff?');
|
|
178
|
+
if (seeDiff) cli.log(raw);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
249
181
|
}
|
|
250
182
|
}
|
|
251
183
|
}
|