@node-core/utils 5.12.2 → 5.14.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 +1 -1
- package/bin/ncu-ci.js +1 -1
- package/components/git/release.js +26 -8
- package/components/git/wpt.js +2 -1
- package/lib/cherry_pick.js +19 -185
- package/lib/ci/run_ci.js +2 -1
- package/lib/landing_session.js +17 -3
- package/lib/pr_checker.js +1 -13
- package/lib/prepare_security.js +1 -1
- package/lib/promote_release.js +146 -126
- package/lib/session.js +2 -1
- package/lib/update-v8/constants.js +13 -1
- package/lib/update_security_release.js +4 -0
- package/lib/wpt/index.js +7 -3
- package/package.json +13 -13
package/README.md
CHANGED
@@ -98,7 +98,7 @@ these commands.
|
|
98
98
|
To obtain the Jenkins API token
|
99
99
|
|
100
100
|
1. Open
|
101
|
-
`https://ci.nodejs.org/user/<your-github-username>/
|
101
|
+
`https://ci.nodejs.org/user/<your-github-username>/security` (replace
|
102
102
|
\<your-github-username\> with your own GitHub username).
|
103
103
|
2. Click on the `ADD NEW TOKEN` button in the `API Token` section.
|
104
104
|
3. Enter an identifiable name (for example, `node-core-utils`) for this
|
package/bin/ncu-ci.js
CHANGED
@@ -114,7 +114,7 @@ const args = yargs(hideBin(process.argv))
|
|
114
114
|
describe: 'ID of the PR',
|
115
115
|
type: 'number'
|
116
116
|
})
|
117
|
-
.
|
117
|
+
.option('certify-safe', {
|
118
118
|
describe: 'SHA of the commit that is expected to be at the tip of the PR head. ' +
|
119
119
|
'If not provided, the command will use the SHA of the last approved commit.',
|
120
120
|
type: 'string'
|
@@ -6,7 +6,7 @@ import TeamInfo from '../../lib/team_info.js';
|
|
6
6
|
import Request from '../../lib/request.js';
|
7
7
|
import { runPromise } from '../../lib/run.js';
|
8
8
|
|
9
|
-
export const command = 'release [prid
|
9
|
+
export const command = 'release [prid..]';
|
10
10
|
export const describe = 'Manage an in-progress release or start a new one.';
|
11
11
|
|
12
12
|
const PREPARE = 'prepare';
|
@@ -34,8 +34,13 @@ const releaseOptions = {
|
|
34
34
|
describe: 'Promote new release of Node.js',
|
35
35
|
type: 'boolean'
|
36
36
|
},
|
37
|
+
fetchFrom: {
|
38
|
+
describe: 'Remote to fetch the release proposal(s) from, if different from the one where to' +
|
39
|
+
'push the tags and commits.',
|
40
|
+
type: 'string',
|
41
|
+
},
|
37
42
|
releaseDate: {
|
38
|
-
describe: 'Default
|
43
|
+
describe: 'Default release date when --prepare is used. It must be YYYY-MM-DD',
|
39
44
|
type: 'string'
|
40
45
|
},
|
41
46
|
run: {
|
@@ -112,11 +117,6 @@ function release(state, argv) {
|
|
112
117
|
}
|
113
118
|
|
114
119
|
async function main(state, argv, cli, dir) {
|
115
|
-
const prID = /^(?:https:\/\/github\.com\/nodejs(-private)?\/node\1\/pull\/)?(\d+)$/.exec(argv.prid);
|
116
|
-
if (prID) {
|
117
|
-
if (prID[1]) argv.security = true;
|
118
|
-
argv.prid = Number(prID[2]);
|
119
|
-
}
|
120
120
|
if (state === PREPARE) {
|
121
121
|
const release = new ReleasePreparation(argv, cli, dir);
|
122
122
|
|
@@ -160,6 +160,24 @@ async function main(state, argv, cli, dir) {
|
|
160
160
|
cli.stopSpinner(`${release.username} is a Releaser`);
|
161
161
|
}
|
162
162
|
|
163
|
-
|
163
|
+
const releases = [];
|
164
|
+
for (const pr of argv.prid) {
|
165
|
+
const match = /^(?:https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/)?(\d+)(?:#.*)?$/.exec(pr);
|
166
|
+
if (!match) throw new Error('Invalid PR ID or URL', { cause: pr });
|
167
|
+
const [,owner, repo, prid] = match;
|
168
|
+
|
169
|
+
if (
|
170
|
+
owner &&
|
171
|
+
(owner !== release.owner || repo !== release.repo) &&
|
172
|
+
!argv.fetchFrom
|
173
|
+
) {
|
174
|
+
console.warn('The configured owner/repo does not match the PR URL.');
|
175
|
+
console.info('You should either pass `--fetch-from` flag or check your configuration');
|
176
|
+
console.info(`E.g. --fetch-from=git@github.com:${owner}/${repo}.git`);
|
177
|
+
throw new Error('You need to tell what remote use to fetch security release proposal.');
|
178
|
+
}
|
179
|
+
releases.push(await release.preparePromotion({ owner, repo, prid: Number(prid) }));
|
180
|
+
}
|
181
|
+
return release.promote(releases);
|
164
182
|
}
|
165
183
|
}
|
package/components/git/wpt.js
CHANGED
package/lib/cherry_pick.js
CHANGED
@@ -1,23 +1,13 @@
|
|
1
|
-
import os from 'node:os';
|
2
1
|
import path from 'node:path';
|
3
2
|
import { getMetadata } from '../components/metadata.js';
|
4
3
|
|
5
4
|
import {
|
6
|
-
runAsync, runSync
|
5
|
+
runAsync, runSync
|
7
6
|
} from './run.js';
|
8
|
-
import { writeFile } from './file.js';
|
9
|
-
import {
|
10
|
-
shortSha, getEditor
|
11
|
-
} from './utils.js';
|
12
7
|
import { getNcuDir } from './config.js';
|
8
|
+
import LandingSession, { LINT_RESULTS } from './landing_session.js';
|
13
9
|
|
14
|
-
|
15
|
-
SKIPPED: 'skipped',
|
16
|
-
FAILED: 'failed',
|
17
|
-
SUCCESS: 'success'
|
18
|
-
};
|
19
|
-
|
20
|
-
export default class CheckPick {
|
10
|
+
export default class CherryPick {
|
21
11
|
constructor(prid, dir, cli, {
|
22
12
|
owner,
|
23
13
|
repo,
|
@@ -46,11 +36,6 @@ export default class CheckPick {
|
|
46
36
|
return this.options.lint;
|
47
37
|
}
|
48
38
|
|
49
|
-
getUpstreamHead() {
|
50
|
-
const { upstream, branch } = this;
|
51
|
-
return runSync('git', ['rev-parse', `${upstream}/${branch}`]).trim();
|
52
|
-
}
|
53
|
-
|
54
39
|
getCurrentRev() {
|
55
40
|
return runSync('git', ['rev-parse', 'HEAD']).trim();
|
56
41
|
}
|
@@ -73,16 +58,6 @@ export default class CheckPick {
|
|
73
58
|
return path.resolve(this.ncuDir, `${this.prid}`);
|
74
59
|
}
|
75
60
|
|
76
|
-
getMessagePath(rev) {
|
77
|
-
return path.resolve(this.pullDir, `${shortSha(rev)}.COMMIT_EDITMSG`);
|
78
|
-
}
|
79
|
-
|
80
|
-
saveMessage(rev, message) {
|
81
|
-
const file = this.getMessagePath(rev);
|
82
|
-
writeFile(file, message);
|
83
|
-
return file;
|
84
|
-
}
|
85
|
-
|
86
61
|
async start() {
|
87
62
|
const { cli } = this;
|
88
63
|
|
@@ -91,7 +66,7 @@ export default class CheckPick {
|
|
91
66
|
owner: this.owner,
|
92
67
|
repo: this.repo
|
93
68
|
}, false, cli);
|
94
|
-
|
69
|
+
this.expectedCommitShas =
|
95
70
|
metadata.data.commits.map(({ commit }) => commit.oid);
|
96
71
|
|
97
72
|
const amend = await cli.prompt(
|
@@ -104,7 +79,7 @@ export default class CheckPick {
|
|
104
79
|
}
|
105
80
|
|
106
81
|
try {
|
107
|
-
const commitInfo = await this.downloadAndPatch(
|
82
|
+
const commitInfo = await this.downloadAndPatch();
|
108
83
|
const cleanLint = await this.validateLint();
|
109
84
|
if (cleanLint === LINT_RESULTS.FAILED) {
|
110
85
|
cli.error('Patch still contains lint errors. ' +
|
@@ -120,68 +95,6 @@ export default class CheckPick {
|
|
120
95
|
}
|
121
96
|
}
|
122
97
|
|
123
|
-
async downloadAndPatch(expectedCommitShas) {
|
124
|
-
const { cli, repo, owner, prid } = this;
|
125
|
-
|
126
|
-
cli.startSpinner(`Downloading patch for ${prid}`);
|
127
|
-
// fetch via ssh to handle private repo
|
128
|
-
await runAsync('git', [
|
129
|
-
'fetch', `git@github.com:${owner}/${repo}.git`,
|
130
|
-
`refs/pull/${prid}/merge`]);
|
131
|
-
// We fetched the commit that would result if we used `git merge`.
|
132
|
-
// ^1 and ^2 refer to the PR base and the PR head, respectively.
|
133
|
-
const [base, head] = await runAsync('git',
|
134
|
-
['rev-parse', 'FETCH_HEAD^1', 'FETCH_HEAD^2'],
|
135
|
-
{ captureStdout: 'lines' });
|
136
|
-
const commitShas = await runAsync('git',
|
137
|
-
['rev-list', `${base}..${head}`],
|
138
|
-
{ captureStdout: 'lines' });
|
139
|
-
cli.stopSpinner(`Fetched commits as ${shortSha(base)}..${shortSha(head)}`);
|
140
|
-
cli.separator();
|
141
|
-
|
142
|
-
const mismatchedCommits = [
|
143
|
-
...commitShas.filter((sha) => !expectedCommitShas.includes(sha))
|
144
|
-
.map((sha) => `Unexpected commit ${sha}`),
|
145
|
-
...expectedCommitShas.filter((sha) => !commitShas.includes(sha))
|
146
|
-
.map((sha) => `Missing commit ${sha}`)
|
147
|
-
].join('\n');
|
148
|
-
if (mismatchedCommits.length > 0) {
|
149
|
-
throw new Error(`Mismatched commits:\n${mismatchedCommits}`);
|
150
|
-
}
|
151
|
-
|
152
|
-
const commitInfo = { base, head, shas: commitShas };
|
153
|
-
|
154
|
-
try {
|
155
|
-
await forceRunAsync('git', ['cherry-pick', `${base}..${head}`], {
|
156
|
-
ignoreFailure: false
|
157
|
-
});
|
158
|
-
} catch (ex) {
|
159
|
-
await forceRunAsync('git', ['cherry-pick', '--abort']);
|
160
|
-
throw new Error('Failed to apply patches');
|
161
|
-
}
|
162
|
-
|
163
|
-
cli.ok('Patches applied');
|
164
|
-
return commitInfo;
|
165
|
-
}
|
166
|
-
|
167
|
-
async validateLint() {
|
168
|
-
// The linter is currently only run on non-Windows platforms.
|
169
|
-
if (os.platform() === 'win32') {
|
170
|
-
return LINT_RESULTS.SKIPPED;
|
171
|
-
}
|
172
|
-
|
173
|
-
if (!this.lint) {
|
174
|
-
return LINT_RESULTS.SKIPPED;
|
175
|
-
}
|
176
|
-
|
177
|
-
try {
|
178
|
-
await runAsync('make', ['lint']);
|
179
|
-
return LINT_RESULTS.SUCCESS;
|
180
|
-
} catch {
|
181
|
-
return LINT_RESULTS.FAILED;
|
182
|
-
}
|
183
|
-
}
|
184
|
-
|
185
98
|
async amend(metadata, commitInfo) {
|
186
99
|
const { cli } = this;
|
187
100
|
const subjects = await runAsync('git',
|
@@ -203,102 +116,23 @@ export default class CheckPick {
|
|
203
116
|
await runAsync('git', ['commit', '--amend', '--no-edit']);
|
204
117
|
}
|
205
118
|
|
206
|
-
return
|
119
|
+
return LandingSession.prototype.amend.call(this, metadata);
|
207
120
|
}
|
208
121
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
const rev = this.getCurrentRev();
|
213
|
-
const original = runSync('git', [
|
214
|
-
'show', 'HEAD', '-s', '--format=%B'
|
215
|
-
]).trim();
|
216
|
-
// git has very specific rules about what is a trailer and what is not.
|
217
|
-
// Instead of trying to implement those ourselves, let git parse the
|
218
|
-
// original commit message and see if it outputs any trailers.
|
219
|
-
const originalHasTrailers = runSync('git', [
|
220
|
-
'interpret-trailers', '--parse', '--no-divider'
|
221
|
-
], {
|
222
|
-
input: `${original}\n`
|
223
|
-
}).trim().length !== 0;
|
224
|
-
const metadata = metadataStr.trim().split('\n');
|
225
|
-
const amended = original.split('\n');
|
226
|
-
|
227
|
-
// If the original commit message already contains trailers (such as
|
228
|
-
// "Co-authored-by"), we simply add our own metadata after those. Otherwise,
|
229
|
-
// we have to add an empty line so that git recognizes our own metadata as
|
230
|
-
// trailers in the amended commit message.
|
231
|
-
if (!originalHasTrailers) {
|
232
|
-
amended.push('');
|
233
|
-
}
|
234
|
-
|
235
|
-
const BACKPORT_RE = /BACKPORT-PR-URL\s*:\s*(\S+)/i;
|
236
|
-
const PR_RE = /PR-URL\s*:\s*(\S+)/i;
|
237
|
-
const REVIEW_RE = /Reviewed-By\s*:\s*(\S+)/i;
|
238
|
-
const CVE_RE = /CVE-ID\s*:\s*(\S+)/i;
|
239
|
-
|
240
|
-
let containCVETrailer = false;
|
241
|
-
for (const line of metadata) {
|
242
|
-
if (line.length !== 0 && original.includes(line)) {
|
243
|
-
if (line.match(CVE_RE)) {
|
244
|
-
containCVETrailer = true;
|
245
|
-
}
|
246
|
-
if (originalHasTrailers) {
|
247
|
-
cli.warn(`Found ${line}, skipping..`);
|
248
|
-
} else {
|
249
|
-
throw new Error(
|
250
|
-
'Git found no trailers in the original commit message, ' +
|
251
|
-
`but '${line}' is present and should be a trailer.`);
|
252
|
-
}
|
253
|
-
} else {
|
254
|
-
if (line.match(BACKPORT_RE)) {
|
255
|
-
let prIndex = amended.findIndex(datum => datum.match(PR_RE));
|
256
|
-
if (prIndex === -1) {
|
257
|
-
prIndex = amended.findIndex(datum => datum.match(REVIEW_RE)) - 1;
|
258
|
-
}
|
259
|
-
amended.splice(prIndex + 1, 0, line);
|
260
|
-
} else {
|
261
|
-
amended.push(line);
|
262
|
-
}
|
263
|
-
}
|
264
|
-
}
|
265
|
-
|
266
|
-
if (!containCVETrailer && this.includeCVE) {
|
267
|
-
const cveID = await cli.prompt(
|
268
|
-
'Git found no CVE-ID trailer in the original commit message. ' +
|
269
|
-
'Please, provide the CVE-ID',
|
270
|
-
{ questionType: 'input', defaultAnswer: 'CVE-2023-XXXXX' }
|
271
|
-
);
|
272
|
-
amended.push('CVE-ID: ' + cveID);
|
273
|
-
}
|
122
|
+
readyToAmend() {
|
123
|
+
return true;
|
124
|
+
}
|
274
125
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
cli.log(message.trim());
|
279
|
-
const takeMessage = await cli.prompt('Use this message?');
|
280
|
-
if (takeMessage) {
|
281
|
-
await runAsync('git', ['commit', '--amend', '-F', messageFile]);
|
282
|
-
return true;
|
283
|
-
}
|
126
|
+
startAmending() {
|
127
|
+
// No-op
|
128
|
+
}
|
284
129
|
|
285
|
-
|
286
|
-
|
287
|
-
try {
|
288
|
-
await forceRunAsync(
|
289
|
-
editor,
|
290
|
-
[`"${messageFile}"`],
|
291
|
-
{ ignoreFailure: false, spawnArgs: { shell: true } }
|
292
|
-
);
|
293
|
-
await runAsync('git', ['commit', '--amend', '-F', messageFile]);
|
294
|
-
return true;
|
295
|
-
} catch {
|
296
|
-
cli.warn(`Please manually edit ${messageFile}, then run\n` +
|
297
|
-
`\`git commit --amend -F ${messageFile}\` ` +
|
298
|
-
'to finish amending the message');
|
299
|
-
throw new Error(
|
300
|
-
'Failed to edit the message using the configured editor');
|
301
|
-
}
|
302
|
-
}
|
130
|
+
saveCommitInfo() {
|
131
|
+
// No-op
|
303
132
|
}
|
304
133
|
}
|
134
|
+
|
135
|
+
CherryPick.prototype.downloadAndPatch = LandingSession.prototype.downloadAndPatch;
|
136
|
+
CherryPick.prototype.validateLint = LandingSession.prototype.validateLint;
|
137
|
+
CherryPick.prototype.getMessagePath = LandingSession.prototype.getMessagePath;
|
138
|
+
CherryPick.prototype.saveMessage = LandingSession.prototype.saveMessage;
|
package/lib/ci/run_ci.js
CHANGED
@@ -62,7 +62,8 @@ export class RunPRJob {
|
|
62
62
|
parameter: [
|
63
63
|
{ name: 'GITHUB_ORG', value: this.owner },
|
64
64
|
{ name: 'REPO_NAME', value: this.repo },
|
65
|
-
{ name: 'GIT_REMOTE_REF', value: `refs/pull/${this.prid}/head` }
|
65
|
+
{ name: 'GIT_REMOTE_REF', value: `refs/pull/${this.prid}/head` },
|
66
|
+
{ name: 'COMMIT_SHA_CHECK', value: this.certifySafe }
|
66
67
|
]
|
67
68
|
}));
|
68
69
|
return payload;
|
package/lib/landing_session.js
CHANGED
@@ -10,7 +10,7 @@ import {
|
|
10
10
|
|
11
11
|
const isWindows = process.platform === 'win32';
|
12
12
|
|
13
|
-
const LINT_RESULTS = {
|
13
|
+
export const LINT_RESULTS = {
|
14
14
|
SKIPPED: 'skipped',
|
15
15
|
FAILED: 'failed',
|
16
16
|
SUCCESS: 'success'
|
@@ -84,11 +84,11 @@ export default class LandingSession extends Session {
|
|
84
84
|
}
|
85
85
|
|
86
86
|
async downloadAndPatch() {
|
87
|
-
const { cli,
|
87
|
+
const { cli, upstream, prid, expectedCommitShas } = this;
|
88
88
|
|
89
89
|
cli.startSpinner(`Downloading patch for ${prid}`);
|
90
90
|
await runAsync('git', [
|
91
|
-
'fetch',
|
91
|
+
'fetch', upstream,
|
92
92
|
`refs/pull/${prid}/merge`]);
|
93
93
|
// We fetched the commit that would result if we used `git merge`.
|
94
94
|
// ^1 and ^2 refer to the PR base and the PR head, respectively.
|
@@ -315,9 +315,14 @@ export default class LandingSession extends Session {
|
|
315
315
|
const BACKPORT_RE = /BACKPORT-PR-URL\s*:\s*(\S+)/i;
|
316
316
|
const PR_RE = /PR-URL\s*:\s*(\S+)/i;
|
317
317
|
const REVIEW_RE = /Reviewed-By\s*:\s*(\S+)/i;
|
318
|
+
const CVE_RE = /CVE-ID\s*:\s*(\S+)/i;
|
318
319
|
|
320
|
+
let containCVETrailer = false;
|
319
321
|
for (const line of metadata) {
|
320
322
|
if (line.length !== 0 && original.includes(line)) {
|
323
|
+
if (line.match(CVE_RE)) {
|
324
|
+
containCVETrailer = true;
|
325
|
+
}
|
321
326
|
if (originalHasTrailers) {
|
322
327
|
cli.warn(`Found ${line}, skipping..`);
|
323
328
|
} else {
|
@@ -338,6 +343,15 @@ export default class LandingSession extends Session {
|
|
338
343
|
}
|
339
344
|
}
|
340
345
|
|
346
|
+
if (!containCVETrailer && this.includeCVE) {
|
347
|
+
const cveID = await cli.prompt(
|
348
|
+
'Git found no CVE-ID trailer in the original commit message. ' +
|
349
|
+
'Please, provide the CVE-ID',
|
350
|
+
{ questionType: 'input', defaultAnswer: 'CVE-2023-XXXXX' }
|
351
|
+
);
|
352
|
+
amended.push('CVE-ID: ' + cveID);
|
353
|
+
}
|
354
|
+
|
341
355
|
const message = amended.join('\n');
|
342
356
|
const messageFile = this.saveMessage(rev, message);
|
343
357
|
cli.separator('New Message');
|
package/lib/pr_checker.js
CHANGED
@@ -30,15 +30,6 @@ const FAST_TRACK_RE = /^Fast-track has been requested by @(.+?)\. Please 👍 to
|
|
30
30
|
const FAST_TRACK_MIN_APPROVALS = 2;
|
31
31
|
const GIT_CONFIG_GUIDE_URL = 'https://github.com/nodejs/node/blob/99b1ada/doc/guides/contributing/pull-requests.md#step-1-fork';
|
32
32
|
|
33
|
-
// eslint-disable-next-line no-extend-native
|
34
|
-
Array.prototype.findLastIndex ??= function findLastIndex(fn) {
|
35
|
-
const reversedIndex = Reflect.apply(
|
36
|
-
Array.prototype.findIndex,
|
37
|
-
this.slice().reverse(),
|
38
|
-
arguments);
|
39
|
-
return reversedIndex === -1 ? -1 : this.length - reversedIndex - 1;
|
40
|
-
};
|
41
|
-
|
42
33
|
export default class PRChecker {
|
43
34
|
/**
|
44
35
|
* @param {{}} cli
|
@@ -49,7 +40,7 @@ export default class PRChecker {
|
|
49
40
|
this.request = request;
|
50
41
|
this.data = data;
|
51
42
|
const {
|
52
|
-
pr, reviewers, comments, reviews, commits
|
43
|
+
pr, reviewers, comments, reviews, commits
|
53
44
|
} = data;
|
54
45
|
this.reviewers = reviewers;
|
55
46
|
this.pr = pr;
|
@@ -61,9 +52,6 @@ export default class PRChecker {
|
|
61
52
|
this.reviews = reviews;
|
62
53
|
this.commits = commits;
|
63
54
|
this.argv = argv;
|
64
|
-
this.collaboratorEmails = new Set(
|
65
|
-
Array.from(collaborators).map((c) => c[1].email)
|
66
|
-
);
|
67
55
|
}
|
68
56
|
|
69
57
|
get waitTimeSingleApproval() {
|
package/lib/prepare_security.js
CHANGED
@@ -70,7 +70,7 @@ export default class PrepareSecurityRelease extends SecurityRelease {
|
|
70
70
|
{ cli: this.cli, repository: this.repository }
|
71
71
|
);
|
72
72
|
}
|
73
|
-
this.cli.info(`
|
73
|
+
this.cli.info(`If the PR is ready (CI is passing): merge pull request with:
|
74
74
|
- git checkout main
|
75
75
|
- git merge ${NEXT_SECURITY_RELEASE_BRANCH} --no-ff -m "chore: add latest security release"
|
76
76
|
- git push origin main`);
|
package/lib/promote_release.js
CHANGED
@@ -17,16 +17,10 @@ const dryRunMessage = 'You are running in dry-run mode, meaning NCU will not run
|
|
17
17
|
|
18
18
|
export default class ReleasePromotion extends Session {
|
19
19
|
constructor(argv, req, cli, dir) {
|
20
|
-
super(cli, dir
|
20
|
+
super(cli, dir);
|
21
21
|
this.req = req;
|
22
|
-
if (argv.security) {
|
23
|
-
this.config.owner = 'nodejs-private';
|
24
|
-
this.config.repo = 'node-private';
|
25
|
-
}
|
26
22
|
this.dryRun = !argv.run;
|
27
|
-
this.
|
28
|
-
this.ltsCodename = '';
|
29
|
-
this.date = '';
|
23
|
+
this.proposalUpstreamRemote = argv.fetchFrom ?? this.upstream;
|
30
24
|
this.gpgSign = argv?.['gpg-sign']
|
31
25
|
? (argv['gpg-sign'] === true ? ['-S'] : ['-S', argv['gpg-sign']])
|
32
26
|
: [];
|
@@ -43,8 +37,8 @@ export default class ReleasePromotion extends Session {
|
|
43
37
|
return defaultBranchRef.name;
|
44
38
|
}
|
45
39
|
|
46
|
-
async
|
47
|
-
const {
|
40
|
+
async preparePromotion({ prid, owner, repo }) {
|
41
|
+
const { cli, proposalUpstreamRemote } = this;
|
48
42
|
|
49
43
|
// In the promotion stage, we can pull most relevant data
|
50
44
|
// from the release commit created in the preparation stage.
|
@@ -54,9 +48,7 @@ export default class ReleasePromotion extends Session {
|
|
54
48
|
isApproved,
|
55
49
|
jenkinsReady,
|
56
50
|
releaseCommitSha
|
57
|
-
} = await this.verifyPRAttributes();
|
58
|
-
|
59
|
-
this.releaseCommitSha = releaseCommitSha;
|
51
|
+
} = await this.verifyPRAttributes({ prid, owner, repo });
|
60
52
|
|
61
53
|
let localCloneIsClean = true;
|
62
54
|
const currentHEAD = await forceRunAsync('git', ['rev-parse', 'HEAD'],
|
@@ -74,7 +66,7 @@ export default class ReleasePromotion extends Session {
|
|
74
66
|
if (!localCloneIsClean) {
|
75
67
|
if (await cli.prompt('Should we reset the local HEAD to be the release proposal?')) {
|
76
68
|
cli.startSpinner('Fetching the proposal upstream...');
|
77
|
-
await forceRunAsync('git', ['fetch',
|
69
|
+
await forceRunAsync('git', ['fetch', proposalUpstreamRemote, releaseCommitSha],
|
78
70
|
{ ignoreFailure: false });
|
79
71
|
await forceRunAsync('git', ['reset', releaseCommitSha, '--hard'], { ignoreFailure: false });
|
80
72
|
cli.stopSpinner('Local HEAD is now in sync with the proposal');
|
@@ -84,9 +76,9 @@ export default class ReleasePromotion extends Session {
|
|
84
76
|
}
|
85
77
|
}
|
86
78
|
|
87
|
-
await this.parseDataFromReleaseCommit();
|
79
|
+
const releaseData = await this.parseDataFromReleaseCommit(releaseCommitSha);
|
88
80
|
|
89
|
-
const { version } =
|
81
|
+
const { version, isLTS, ltsCodename, date, versionComponents } = releaseData;
|
90
82
|
cli.startSpinner('Verifying Jenkins CI status');
|
91
83
|
if (!jenkinsReady) {
|
92
84
|
cli.stopSpinner(
|
@@ -134,102 +126,79 @@ export default class ReleasePromotion extends Session {
|
|
134
126
|
cli.warn(`Aborting release promotion for version ${version}`);
|
135
127
|
throw new Error('Aborted');
|
136
128
|
}
|
137
|
-
await this.secureTagRelease();
|
138
|
-
await this.verifyTagSignature();
|
129
|
+
await this.secureTagRelease({ version, isLTS, ltsCodename, releaseCommitSha, date });
|
130
|
+
await this.verifyTagSignature(version);
|
139
131
|
|
140
132
|
// Set up for next release.
|
141
133
|
cli.startSpinner('Setting up for next release');
|
142
|
-
const workingOnNewReleaseCommit =
|
134
|
+
const workingOnNewReleaseCommit =
|
135
|
+
await this.setupForNextRelease({ prid, owner, repo, versionComponents });
|
143
136
|
cli.stopSpinner('Successfully set up for next release');
|
144
137
|
|
138
|
+
const shouldRebaseStagingBranch = await cli.prompt(
|
139
|
+
'Rebase staging branch on top of the release commit?', { defaultAnswer: true });
|
140
|
+
const tipOfStagingBranch = shouldRebaseStagingBranch
|
141
|
+
? await this.rebaseRemoteBranch(
|
142
|
+
`v${versionComponents.major}.x-staging`,
|
143
|
+
workingOnNewReleaseCommit)
|
144
|
+
: workingOnNewReleaseCommit;
|
145
|
+
|
146
|
+
return { releaseCommitSha, workingOnNewReleaseCommit, tipOfStagingBranch, ...releaseData };
|
147
|
+
}
|
148
|
+
|
149
|
+
async promote(releases) {
|
150
|
+
const { cli } = this;
|
151
|
+
|
145
152
|
// Cherry pick release commit to master.
|
146
153
|
const shouldCherryPick = await cli.prompt(
|
147
|
-
'Cherry-pick release commit to the default branch?', { defaultAnswer: true });
|
154
|
+
'Cherry-pick release commit(s) to the default branch?', { defaultAnswer: true });
|
148
155
|
if (!shouldCherryPick) {
|
149
|
-
cli.warn(`Aborting release promotion for version ${version}`);
|
150
156
|
throw new Error('Aborted');
|
151
157
|
}
|
152
|
-
const
|
153
|
-
|
154
|
-
// Ensure `node_version.h`'s `NODE_VERSION_IS_RELEASE` bit is not updated
|
155
|
-
await forceRunAsync('git', ['checkout',
|
156
|
-
appliedCleanly
|
157
|
-
? 'HEAD^' // In the absence of conflict, the top of the remote branch is the commit before.
|
158
|
-
: 'HEAD', // In case of conflict, HEAD is still the top of the remove branch.
|
159
|
-
'--', 'src/node_version.h'],
|
160
|
-
{ ignoreFailure: false });
|
161
|
-
|
162
|
-
if (appliedCleanly) {
|
163
|
-
// There were no conflicts, we have to amend the commit to revert the
|
164
|
-
// `node_version.h` changes.
|
165
|
-
await forceRunAsync('git', ['commit', ...this.gpgSign, '--amend', '--no-edit', '-n'],
|
166
|
-
{ ignoreFailure: false });
|
167
|
-
} else {
|
168
|
-
// There will be remaining cherry-pick conflicts the Releaser will
|
169
|
-
// need to resolve, so confirm they've been resolved before
|
170
|
-
// proceeding with next steps.
|
171
|
-
cli.separator();
|
172
|
-
cli.info('Resolve the conflicts and commit the result');
|
173
|
-
cli.separator();
|
174
|
-
const didResolveConflicts = await cli.prompt(
|
175
|
-
'Finished resolving cherry-pick conflicts?', { defaultAnswer: true });
|
176
|
-
if (!didResolveConflicts) {
|
177
|
-
cli.warn(`Aborting release promotion for version ${version}`);
|
178
|
-
throw new Error('Aborted');
|
179
|
-
}
|
180
|
-
}
|
158
|
+
const defaultBranch = await this.checkoutDefaultBranch();
|
181
159
|
|
182
|
-
|
183
|
-
|
184
|
-
await forceRunAsync('git', ['cherry-pick', ...this.gpgSign, '--continue'],
|
185
|
-
{ ignoreFailure: false });
|
186
|
-
}
|
187
|
-
|
188
|
-
// Validate release commit on the default branch
|
189
|
-
const releaseCommitOnDefaultBranch =
|
190
|
-
await forceRunAsync('git', ['show', 'HEAD', '--name-only', '--pretty=format:%s'],
|
191
|
-
{ captureStdout: true, ignoreFailure: false });
|
192
|
-
const [commitTitle, ...modifiedFiles] = releaseCommitOnDefaultBranch.trim().split('\n');
|
193
|
-
await this.validateReleaseCommit(commitTitle);
|
194
|
-
if (modifiedFiles.some(file => !file.endsWith('.md'))) {
|
195
|
-
cli.warn('Some modified files are not markdown, that\'s unusual.');
|
196
|
-
cli.info(`The list of modified files: ${modifiedFiles.map(f => `- ${f}`).join('\n')}`);
|
197
|
-
if (!await cli.prompt('Do you want to proceed anyway?', { defaultAnswer: false })) {
|
198
|
-
throw new Error('Aborted');
|
199
|
-
}
|
160
|
+
for (const { releaseCommitSha } of releases) {
|
161
|
+
await this.cherryPickReleaseCommit(releaseCommitSha);
|
200
162
|
}
|
201
163
|
|
202
|
-
// Push to the remote the release tag, and default, release, and staging
|
203
|
-
await this.pushToRemote(
|
164
|
+
// Push to the remote the release tag(s), and default, release, and staging branches.
|
165
|
+
await this.pushToRemote(this.upstream, defaultBranch, ...releases.flatMap(
|
166
|
+
({ version, versionComponents, workingOnNewReleaseCommit, tipOfStagingBranch }) => [
|
167
|
+
`v${version}`,
|
168
|
+
`${workingOnNewReleaseCommit}:refs/heads/v${versionComponents.major}.x`,
|
169
|
+
`+${tipOfStagingBranch}:refs/heads/v${versionComponents.major}.x-staging`,
|
170
|
+
]));
|
204
171
|
|
205
172
|
// Promote and sign the release builds.
|
206
173
|
await this.promoteAndSignRelease();
|
207
174
|
|
208
175
|
cli.separator();
|
209
|
-
cli.ok(
|
176
|
+
cli.ok('Release promotion(s) complete.\n');
|
210
177
|
cli.info(
|
211
178
|
'To finish this release, you\'ll need to: \n' +
|
212
|
-
|
213
|
-
' 2. Create the blog post for nodejs.org.\n' +
|
214
|
-
' 3. Create the release on GitHub.\n' +
|
215
|
-
' 4. Optionally, announce the release on your social networks.\n' +
|
179
|
+
' 1. Check the release(s) at: https://nodejs.org/dist/v{version}\n' +
|
180
|
+
' 2. Create the blog post(s) for nodejs.org.\n' +
|
181
|
+
' 3. Create the release(s) on GitHub.\n' +
|
182
|
+
' 4. Optionally, announce the release(s) on your social networks.\n' +
|
216
183
|
' 5. Tag @nodejs-social-team on #nodejs-release Slack channel.\n');
|
217
184
|
|
218
185
|
cli.separator();
|
219
|
-
cli.info('Use the following command to create the GitHub release:');
|
186
|
+
cli.info('Use the following command(s) to create the GitHub release(s):');
|
220
187
|
cli.separator();
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
188
|
+
for (const { date, version, versionComponents, isLTS, releaseTitle } of releases) {
|
189
|
+
cli.info(
|
190
|
+
'awk \'' +
|
191
|
+
`/^## ${date}, Version ${version.replaceAll('.', '\\.')} /,` +
|
192
|
+
'/^<a id="[0-9]+\\.[0-9]+\\.[0-9]+"><\\x2fa>$/{' +
|
193
|
+
'print buf; if(firstLine == "") firstLine = $0; else buf = $0' +
|
194
|
+
`}' doc/changelogs/CHANGELOG_V${
|
195
|
+
versionComponents.major}.md | gh release create v${version} --verify-tag --latest${
|
196
|
+
isLTS ? '=false' : ''} --title=${JSON.stringify(releaseTitle)} --notes-file -`);
|
197
|
+
}
|
229
198
|
}
|
230
199
|
|
231
|
-
async verifyTagSignature() {
|
232
|
-
const { cli
|
200
|
+
async verifyTagSignature(version) {
|
201
|
+
const { cli } = this;
|
233
202
|
const verifyTagPattern = /gpg:[^\n]+\ngpg:\s+using RSA key ([^\n]+)\ngpg:\s+issuer "([^"]+)"\ngpg:\s+Good signature from "([^<]+) <\2>"/;
|
234
203
|
const [verifyTagOutput, haystack] = await Promise.all([forceRunAsync(
|
235
204
|
'git', ['--no-pager',
|
@@ -258,8 +227,8 @@ export default class ReleasePromotion extends Session {
|
|
258
227
|
}
|
259
228
|
}
|
260
229
|
|
261
|
-
async verifyPRAttributes() {
|
262
|
-
const { cli,
|
230
|
+
async verifyPRAttributes({ prid, owner, repo }) {
|
231
|
+
const { cli, req } = this;
|
263
232
|
|
264
233
|
const data = new PRData({ prid, owner, repo }, cli, req);
|
265
234
|
await data.getAll();
|
@@ -325,8 +294,8 @@ export default class ReleasePromotion extends Session {
|
|
325
294
|
return data;
|
326
295
|
}
|
327
296
|
|
328
|
-
async parseDataFromReleaseCommit() {
|
329
|
-
const { cli
|
297
|
+
async parseDataFromReleaseCommit(releaseCommitSha) {
|
298
|
+
const { cli } = this;
|
330
299
|
|
331
300
|
const releaseCommitMessage = await forceRunAsync('git', [
|
332
301
|
'--no-pager', 'log', '-1',
|
@@ -338,26 +307,19 @@ export default class ReleasePromotion extends Session {
|
|
338
307
|
|
339
308
|
const releaseCommitData = await this.validateReleaseCommit(releaseCommitMessage);
|
340
309
|
|
341
|
-
this.date = releaseCommitData.date;
|
342
|
-
this.version = releaseCommitData.version;
|
343
|
-
this.stagingBranch = releaseCommitData.stagingBranch;
|
344
|
-
this.versionComponents = releaseCommitData.versionComponents;
|
345
|
-
this.isLTS = releaseCommitData.isLTS;
|
346
|
-
this.ltsCodename = releaseCommitData.ltsCodename;
|
347
|
-
|
348
310
|
// Check if CHANGELOG show the correct releaser for the current release
|
349
311
|
const changeLogDiff = await forceRunAsync('git', [
|
350
312
|
'--no-pager', 'diff',
|
351
|
-
`${
|
313
|
+
`${releaseCommitSha}^..${releaseCommitSha}`,
|
352
314
|
'--',
|
353
|
-
`doc/changelogs/CHANGELOG_V${
|
315
|
+
`doc/changelogs/CHANGELOG_V${releaseCommitData.versionComponents.major}.md`
|
354
316
|
], { captureStdout: true, ignoreFailure: false });
|
355
317
|
const headingLine = /^\+## \d{4}-\d{2}-\d{2}, Version \d.+$/m.exec(changeLogDiff);
|
356
318
|
if (headingLine == null) {
|
357
319
|
cli.error('Cannot find section for the new release in CHANGELOG');
|
358
320
|
throw new Error('Aborted');
|
359
321
|
}
|
360
|
-
|
322
|
+
releaseCommitData.releaseTitle = headingLine[0].slice(4);
|
361
323
|
const expectedLine = `+## ${releaseCommitMessage}, @${this.username}`;
|
362
324
|
if (headingLine[0] !== expectedLine &&
|
363
325
|
!headingLine[0].startsWith(`${expectedLine} prepared by @`)) {
|
@@ -370,11 +332,11 @@ export default class ReleasePromotion extends Session {
|
|
370
332
|
throw new Error('Aborted');
|
371
333
|
}
|
372
334
|
}
|
373
|
-
}
|
374
335
|
|
375
|
-
|
376
|
-
|
336
|
+
return releaseCommitData;
|
337
|
+
}
|
377
338
|
|
339
|
+
async secureTagRelease({ version, isLTS, ltsCodename, releaseCommitSha, date }) {
|
378
340
|
const releaseInfo = isLTS ? `${ltsCodename} (LTS)` : '(Current)';
|
379
341
|
|
380
342
|
try {
|
@@ -382,7 +344,7 @@ export default class ReleasePromotion extends Session {
|
|
382
344
|
const api = new gst.API(process.cwd());
|
383
345
|
api.sign(`v${version}`, releaseCommitSha, {
|
384
346
|
insecure: false,
|
385
|
-
m: `${
|
347
|
+
m: `${date} Node.js v${version} ${releaseInfo} Release`
|
386
348
|
}, (err) => err ? reject(err) : resolve());
|
387
349
|
});
|
388
350
|
} catch (err) {
|
@@ -401,9 +363,7 @@ export default class ReleasePromotion extends Session {
|
|
401
363
|
|
402
364
|
// Set up the branch so that nightly builds are produced with the next
|
403
365
|
// version number and a pre-release tag.
|
404
|
-
async setupForNextRelease() {
|
405
|
-
const { versionComponents, prid, owner, repo } = this;
|
406
|
-
|
366
|
+
async setupForNextRelease({ prid, owner, repo, versionComponents }) {
|
407
367
|
// Update node_version.h for next patch release.
|
408
368
|
const filePath = path.resolve('src', 'node_version.h');
|
409
369
|
const nodeVersionFile = await fs.open(filePath, 'r+');
|
@@ -440,22 +400,16 @@ export default class ReleasePromotion extends Session {
|
|
440
400
|
return workingOnNewReleaseCommit.trim();
|
441
401
|
}
|
442
402
|
|
443
|
-
async pushToRemote(
|
444
|
-
const { cli, dryRun
|
445
|
-
const releaseBranch = `v${versionComponents.major}.x`;
|
446
|
-
const tagVersion = `v${version}`;
|
403
|
+
async pushToRemote(upstream, ...refs) {
|
404
|
+
const { cli, dryRun } = this;
|
447
405
|
|
448
406
|
this.defaultBranch ??= await this.getDefaultBranch();
|
449
407
|
|
450
|
-
let prompt = `Push release tag and commits to ${
|
408
|
+
let prompt = `Push release tag and commits to ${upstream}?`;
|
451
409
|
if (dryRun) {
|
452
410
|
cli.info(dryRunMessage);
|
453
411
|
cli.info('Run the following command to push to remote:');
|
454
|
-
cli.info(`git push ${
|
455
|
-
this.defaultBranch} ${
|
456
|
-
tagVersion} ${
|
457
|
-
workingOnNewReleaseCommit}:refs/heads/${releaseBranch} ${
|
458
|
-
workingOnNewReleaseCommit}:refs/heads/${stagingBranch}`);
|
412
|
+
cli.info(`git push ${upstream} ${refs.join(' ')}`);
|
459
413
|
cli.warn('Once pushed, you must not delete the local tag');
|
460
414
|
prompt = 'Ready to continue?';
|
461
415
|
}
|
@@ -469,12 +423,8 @@ export default class ReleasePromotion extends Session {
|
|
469
423
|
}
|
470
424
|
|
471
425
|
cli.startSpinner('Pushing to remote');
|
472
|
-
await forceRunAsync('git', ['push',
|
473
|
-
|
474
|
-
`${workingOnNewReleaseCommit}:refs/heads/${stagingBranch}`],
|
475
|
-
{ ignoreFailure: false });
|
476
|
-
cli.stopSpinner(`Pushed ${tagVersion}, ${this.defaultBranch}, ${
|
477
|
-
releaseBranch}, and ${stagingBranch} to remote`);
|
426
|
+
await forceRunAsync('git', ['push', upstream, ...refs], { ignoreFailure: false });
|
427
|
+
cli.stopSpinner(`Pushed ${JSON.stringify(refs)} to remote`);
|
478
428
|
cli.warn('Now that it has been pushed, you must not delete the local tag');
|
479
429
|
}
|
480
430
|
|
@@ -507,21 +457,91 @@ export default class ReleasePromotion extends Session {
|
|
507
457
|
cli.stopSpinner('Release has been signed and promoted');
|
508
458
|
}
|
509
459
|
|
510
|
-
async
|
460
|
+
async rebaseRemoteBranch(branchName, workingOnNewReleaseCommit) {
|
461
|
+
const { cli, upstream } = this;
|
462
|
+
cli.startSpinner('Fetch staging branch');
|
463
|
+
await forceRunAsync('git', ['fetch', upstream, branchName], { ignoreFailure: false });
|
464
|
+
cli.updateSpinner('Reset and rebase');
|
465
|
+
await forceRunAsync('git', ['reset', 'FETCH_HEAD', '--hard'], { ignoreFailure: false });
|
466
|
+
await forceRunAsync('git',
|
467
|
+
['rebase', workingOnNewReleaseCommit, ...this.gpgSign], { ignoreFailure: false });
|
468
|
+
const tipOfStagingBranch = await forceRunAsync('git', ['rev-parse', 'HEAD'],
|
469
|
+
{ ignoreFailure: false, captureStdout: true });
|
470
|
+
cli.stopSpinner('Rebased successfully');
|
471
|
+
|
472
|
+
return tipOfStagingBranch.trim();
|
473
|
+
}
|
474
|
+
|
475
|
+
async checkoutDefaultBranch() {
|
511
476
|
this.defaultBranch ??= await this.getDefaultBranch();
|
512
|
-
const releaseCommitSha = this.releaseCommitSha;
|
513
477
|
await forceRunAsync('git', ['checkout', this.defaultBranch], { ignoreFailure: false });
|
514
478
|
|
515
479
|
await this.tryResetBranch();
|
516
480
|
|
481
|
+
return this.defaultBranch;
|
482
|
+
}
|
483
|
+
|
484
|
+
async cherryPick(commit) {
|
517
485
|
// There might be conflicts, we do not want to treat this as a hard failure,
|
518
486
|
// but we want to retain that information.
|
519
487
|
try {
|
520
|
-
await forceRunAsync('git', ['cherry-pick', ...this.gpgSign,
|
488
|
+
await forceRunAsync('git', ['cherry-pick', ...this.gpgSign, commit],
|
521
489
|
{ ignoreFailure: false });
|
522
490
|
return true;
|
523
491
|
} catch {
|
524
492
|
return false;
|
525
493
|
}
|
526
494
|
}
|
495
|
+
|
496
|
+
async cherryPickReleaseCommit(releaseCommitSha) {
|
497
|
+
const { cli } = this;
|
498
|
+
|
499
|
+
const appliedCleanly = await this.cherryPick(releaseCommitSha);
|
500
|
+
// Ensure `node_version.h`'s `NODE_VERSION_IS_RELEASE` bit is not updated
|
501
|
+
await forceRunAsync('git', ['checkout',
|
502
|
+
appliedCleanly
|
503
|
+
? 'HEAD^' // In the absence of conflict, the top of the remote branch is the commit before.
|
504
|
+
: 'HEAD', // In case of conflict, HEAD is still the top of the remove branch.
|
505
|
+
'--', 'src/node_version.h'],
|
506
|
+
{ ignoreFailure: false });
|
507
|
+
|
508
|
+
if (appliedCleanly) {
|
509
|
+
// There were no conflicts, we have to amend the commit to revert the
|
510
|
+
// `node_version.h` changes.
|
511
|
+
await forceRunAsync('git', ['commit', ...this.gpgSign, '--amend', '--no-edit', '-n'],
|
512
|
+
{ ignoreFailure: false });
|
513
|
+
} else {
|
514
|
+
// There will be remaining cherry-pick conflicts the Releaser will
|
515
|
+
// need to resolve, so confirm they've been resolved before
|
516
|
+
// proceeding with next steps.
|
517
|
+
cli.separator();
|
518
|
+
cli.info('Resolve the conflicts and commit the result');
|
519
|
+
cli.separator();
|
520
|
+
const didResolveConflicts = await cli.prompt(
|
521
|
+
'Finished resolving cherry-pick conflicts?', { defaultAnswer: true });
|
522
|
+
if (!didResolveConflicts) {
|
523
|
+
throw new Error('Aborted');
|
524
|
+
}
|
525
|
+
}
|
526
|
+
|
527
|
+
if (existsSync('.git/CHERRY_PICK_HEAD')) {
|
528
|
+
cli.info('Cherry-pick is still in progress, attempting to continue it.');
|
529
|
+
await forceRunAsync('git', ['cherry-pick', ...this.gpgSign, '--continue'],
|
530
|
+
{ ignoreFailure: false });
|
531
|
+
}
|
532
|
+
|
533
|
+
// Validate release commit on the default branch
|
534
|
+
const releaseCommitOnDefaultBranch =
|
535
|
+
await forceRunAsync('git', ['show', 'HEAD', '--name-only', '--pretty=format:%s'],
|
536
|
+
{ captureStdout: true, ignoreFailure: false });
|
537
|
+
const [commitTitle, ...modifiedFiles] = releaseCommitOnDefaultBranch.trim().split('\n');
|
538
|
+
await this.validateReleaseCommit(commitTitle);
|
539
|
+
if (modifiedFiles.some(file => !file.endsWith('.md'))) {
|
540
|
+
cli.warn('Some modified files are not markdown, that\'s unusual.');
|
541
|
+
cli.info(`The list of modified files: ${modifiedFiles.map(f => `- ${f}`).join('\n')}`);
|
542
|
+
if (!await cli.prompt('Do you want to proceed anyway?', { defaultAnswer: false })) {
|
543
|
+
throw new Error('Aborted');
|
544
|
+
}
|
545
|
+
}
|
546
|
+
}
|
527
547
|
}
|
package/lib/session.js
CHANGED
@@ -347,7 +347,8 @@ export default class Session {
|
|
347
347
|
const { cli, upstream, branch } = this;
|
348
348
|
const branchName = `${upstream}/${branch}`;
|
349
349
|
cli.startSpinner(`Bringing ${branchName} up to date...`);
|
350
|
-
|
350
|
+
const maybeUnshallow = fs.existsSync('.git/shallow') ? ['--unshallow'] : [];
|
351
|
+
await runAsync('git', ['fetch', ...maybeUnshallow, upstream, branch]);
|
351
352
|
cli.stopSpinner(`${branchName} is now up-to-date`);
|
352
353
|
const stray = this.getStrayCommits(true);
|
353
354
|
if (!stray.length) {
|
@@ -44,6 +44,9 @@ const fastFloatReplace = `/third_party/fast_float/src/*
|
|
44
44
|
const highwayIgnore = `/third_party/highway/src/*
|
45
45
|
!/third_party/highway/src/hwy`;
|
46
46
|
|
47
|
+
const dragonboxIgnore = `/third_party/dragonbox/src/*
|
48
|
+
!/third_party/dragonbox/src/include`;
|
49
|
+
|
47
50
|
export const v8Deps = [
|
48
51
|
{
|
49
52
|
name: 'trace_event',
|
@@ -133,5 +136,14 @@ export const v8Deps = [
|
|
133
136
|
repo: 'third_party/simdutf',
|
134
137
|
gitignore: '!/third_party/simdutf',
|
135
138
|
since: 134
|
136
|
-
}
|
139
|
+
},
|
140
|
+
{
|
141
|
+
name: 'dragonbox',
|
142
|
+
repo: 'third_party/dragonbox/src',
|
143
|
+
gitignore: {
|
144
|
+
match: '/third_party/dragonbox/src',
|
145
|
+
replace: dragonboxIgnore
|
146
|
+
},
|
147
|
+
since: 138
|
148
|
+
},
|
137
149
|
];
|
@@ -46,6 +46,10 @@ export default class UpdateSecurityRelease extends SecurityRelease {
|
|
46
46
|
}
|
47
47
|
const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath();
|
48
48
|
fs.writeFileSync(vulnerabilitiesJSONPath, JSON.stringify(content, null, 2));
|
49
|
+
|
50
|
+
const commitMessage = 'chore: git node security --sync';
|
51
|
+
commitAndPushVulnerabilitiesJSON([vulnerabilitiesJSONPath],
|
52
|
+
commitMessage, { cli: this.cli, repository: this.repository });
|
49
53
|
this.cli.ok('Synced vulnerabilities.json with HackerOne');
|
50
54
|
}
|
51
55
|
|
package/lib/wpt/index.js
CHANGED
@@ -79,9 +79,13 @@ export class WPTUpdater {
|
|
79
79
|
await removeDirectory(this.fixtures(this.path));
|
80
80
|
|
81
81
|
this.cli.startSpinner('Pulling assets...');
|
82
|
-
|
83
|
-
|
84
|
-
|
82
|
+
// See https://github.com/nodejs/node-core-utils/issues/810
|
83
|
+
for (let i = 0; i < assets.length; i += 10) {
|
84
|
+
const chunk = assets.slice(i, i + 10);
|
85
|
+
await Promise.all(chunk.map(
|
86
|
+
(asset) => this.pullTextFile(fixtures, asset.name)
|
87
|
+
));
|
88
|
+
}
|
85
89
|
this.cli.stopSpinner(`Downloaded ${assets.length} assets.`);
|
86
90
|
|
87
91
|
return assets;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@node-core/utils",
|
3
|
-
"version": "5.
|
3
|
+
"version": "5.14.0",
|
4
4
|
"description": "Utilities for Node.js core collaborators",
|
5
5
|
"type": "module",
|
6
6
|
"engines": {
|
@@ -34,40 +34,40 @@
|
|
34
34
|
],
|
35
35
|
"license": "MIT",
|
36
36
|
"dependencies": {
|
37
|
-
"@inquirer/prompts": "^7.
|
37
|
+
"@inquirer/prompts": "^7.4.1",
|
38
38
|
"@listr2/prompt-adapter-enquirer": "^2.0.12",
|
39
39
|
"@node-core/caritat": "^1.6.0",
|
40
40
|
"@pkgjs/nv": "^0.2.2",
|
41
41
|
"branch-diff": "^3.1.1",
|
42
42
|
"chalk": "^5.4.1",
|
43
|
-
"changelog-maker": "^4.
|
43
|
+
"changelog-maker": "^4.4.1",
|
44
44
|
"cheerio": "^1.0.0",
|
45
45
|
"clipboardy": "^4.0.0",
|
46
46
|
"core-validate-commit": "^4.1.0",
|
47
47
|
"figures": "^6.1.0",
|
48
|
-
"ghauth": "^6.0.
|
48
|
+
"ghauth": "^6.0.12",
|
49
49
|
"git-secure-tag": "^2.3.1",
|
50
50
|
"js-yaml": "^4.1.0",
|
51
51
|
"listr2": "^8.2.5",
|
52
52
|
"lodash": "^4.17.21",
|
53
53
|
"log-symbols": "^7.0.0",
|
54
|
-
"ora": "^8.
|
54
|
+
"ora": "^8.2.0",
|
55
55
|
"replace-in-file": "^8.3.0",
|
56
|
-
"semver": "^7.
|
57
|
-
"undici": "^7.
|
56
|
+
"semver": "^7.7.1",
|
57
|
+
"undici": "^7.7.0",
|
58
58
|
"which": "^5.0.0",
|
59
59
|
"yargs": "^17.7.2"
|
60
60
|
},
|
61
61
|
"devDependencies": {
|
62
|
-
"@eslint/js": "^9.
|
62
|
+
"@eslint/js": "^9.24.0",
|
63
63
|
"@reporters/github": "^1.7.2",
|
64
64
|
"c8": "^10.1.3",
|
65
|
-
"eslint": "^9.
|
65
|
+
"eslint": "^9.24.0",
|
66
66
|
"eslint-plugin-import": "^2.31.0",
|
67
|
-
"eslint-plugin-n": "^17.
|
67
|
+
"eslint-plugin-n": "^17.17.0",
|
68
68
|
"eslint-plugin-promise": "^7.2.1",
|
69
|
-
"globals": "^
|
70
|
-
"neostandard": "^0.12.
|
71
|
-
"sinon": "^
|
69
|
+
"globals": "^16.0.0",
|
70
|
+
"neostandard": "^0.12.1",
|
71
|
+
"sinon": "^20.0.0"
|
72
72
|
}
|
73
73
|
}
|