@node-core/utils 4.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/LICENSE +7 -0
- package/README.md +158 -0
- package/bin/get-metadata.js +11 -0
- package/bin/git-node.js +30 -0
- package/bin/ncu-ci.js +600 -0
- package/bin/ncu-config.js +101 -0
- package/bin/ncu-team.js +76 -0
- package/components/git/backport.js +70 -0
- package/components/git/epilogue.js +18 -0
- package/components/git/land.js +223 -0
- package/components/git/metadata.js +94 -0
- package/components/git/release.js +99 -0
- package/components/git/security.js +35 -0
- package/components/git/status.js +32 -0
- package/components/git/sync.js +24 -0
- package/components/git/v8.js +121 -0
- package/components/git/vote.js +84 -0
- package/components/git/wpt.js +87 -0
- package/components/metadata.js +49 -0
- package/lib/auth.js +133 -0
- package/lib/backport_session.js +302 -0
- package/lib/cache.js +107 -0
- package/lib/cherry_pick.js +304 -0
- package/lib/ci/build-types/benchmark_run.js +72 -0
- package/lib/ci/build-types/citgm_build.js +194 -0
- package/lib/ci/build-types/citgm_comparison_build.js +174 -0
- package/lib/ci/build-types/commit_build.js +112 -0
- package/lib/ci/build-types/daily_build.js +24 -0
- package/lib/ci/build-types/fanned_build.js +87 -0
- package/lib/ci/build-types/health_build.js +63 -0
- package/lib/ci/build-types/job.js +114 -0
- package/lib/ci/build-types/linter_build.js +35 -0
- package/lib/ci/build-types/normal_build.js +89 -0
- package/lib/ci/build-types/pr_build.js +101 -0
- package/lib/ci/build-types/test_build.js +186 -0
- package/lib/ci/build-types/test_run.js +41 -0
- package/lib/ci/ci_failure_parser.js +325 -0
- package/lib/ci/ci_type_parser.js +203 -0
- package/lib/ci/ci_utils.js +106 -0
- package/lib/ci/failure_aggregator.js +152 -0
- package/lib/ci/jenkins_constants.js +28 -0
- package/lib/ci/run_ci.js +120 -0
- package/lib/cli.js +192 -0
- package/lib/collaborators.js +140 -0
- package/lib/config.js +72 -0
- package/lib/figures.js +7 -0
- package/lib/file.js +43 -0
- package/lib/github/templates/next-security-release.md +97 -0
- package/lib/github/tree.js +162 -0
- package/lib/landing_session.js +506 -0
- package/lib/links.js +123 -0
- package/lib/mergeable_state.js +3 -0
- package/lib/metadata_gen.js +61 -0
- package/lib/pr_checker.js +605 -0
- package/lib/pr_data.js +115 -0
- package/lib/pr_summary.js +62 -0
- package/lib/prepare_release.js +772 -0
- package/lib/prepare_security.js +117 -0
- package/lib/proxy.js +21 -0
- package/lib/queries/DefaultBranchRef.gql +8 -0
- package/lib/queries/LastCommit.gql +16 -0
- package/lib/queries/PR.gql +37 -0
- package/lib/queries/PRComments.gql +27 -0
- package/lib/queries/PRCommits.gql +45 -0
- package/lib/queries/PRs.gql +25 -0
- package/lib/queries/Reviews.gql +23 -0
- package/lib/queries/SearchIssue.gql +51 -0
- package/lib/queries/Team.gql +22 -0
- package/lib/queries/TreeEntries.gql +12 -0
- package/lib/queries/VotePRInfo.gql +28 -0
- package/lib/release/utils.js +53 -0
- package/lib/request.js +185 -0
- package/lib/review_state.js +5 -0
- package/lib/reviews.js +178 -0
- package/lib/run.js +106 -0
- package/lib/session.js +415 -0
- package/lib/sync_session.js +15 -0
- package/lib/team_info.js +95 -0
- package/lib/update-v8/applyNodeChanges.js +49 -0
- package/lib/update-v8/backport.js +258 -0
- package/lib/update-v8/commitUpdate.js +26 -0
- package/lib/update-v8/common.js +35 -0
- package/lib/update-v8/constants.js +86 -0
- package/lib/update-v8/index.js +56 -0
- package/lib/update-v8/majorUpdate.js +171 -0
- package/lib/update-v8/minorUpdate.js +105 -0
- package/lib/update-v8/updateMaintainingDependencies.js +34 -0
- package/lib/update-v8/updateV8Clone.js +53 -0
- package/lib/update-v8/updateVersionNumbers.js +122 -0
- package/lib/update-v8/util.js +62 -0
- package/lib/user.js +4 -0
- package/lib/user_status.js +5 -0
- package/lib/utils.js +66 -0
- package/lib/verbosity.js +26 -0
- package/lib/voting_session.js +136 -0
- package/lib/wpt/index.js +243 -0
- package/lib/wpt/templates/README.md +16 -0
- package/package.json +69 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
import semver from 'semver';
|
|
5
|
+
import replace from 'replace-in-file';
|
|
6
|
+
|
|
7
|
+
import { getMergedConfig } from './config.js';
|
|
8
|
+
import { runAsync, runSync } from './run.js';
|
|
9
|
+
import { writeJson, readJson } from './file.js';
|
|
10
|
+
import Request from './request.js';
|
|
11
|
+
import auth from './auth.js';
|
|
12
|
+
import {
|
|
13
|
+
getEOLDate,
|
|
14
|
+
getStartLTSBlurb,
|
|
15
|
+
updateTestProcessRelease
|
|
16
|
+
} from './release/utils.js';
|
|
17
|
+
import CherryPick from './cherry_pick.js';
|
|
18
|
+
|
|
19
|
+
const isWindows = process.platform === 'win32';
|
|
20
|
+
|
|
21
|
+
export default class ReleasePreparation {
|
|
22
|
+
constructor(argv, cli, dir) {
|
|
23
|
+
this.cli = cli;
|
|
24
|
+
this.dir = dir;
|
|
25
|
+
this.isSecurityRelease = argv.security;
|
|
26
|
+
this.isLTS = false;
|
|
27
|
+
this.isLTSTransition = argv.startLTS;
|
|
28
|
+
this.runBranchDiff = !argv.skipBranchDiff;
|
|
29
|
+
this.ltsCodename = '';
|
|
30
|
+
this.date = '';
|
|
31
|
+
this.config = getMergedConfig(this.dir);
|
|
32
|
+
this.filterLabels = argv.filterLabel && argv.filterLabel.split(',');
|
|
33
|
+
|
|
34
|
+
// Ensure the preparer has set an upstream and username.
|
|
35
|
+
if (this.warnForMissing()) {
|
|
36
|
+
cli.error('Failed to begin the release preparation process.');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Allow passing optional new version.
|
|
41
|
+
if (argv.newVersion) {
|
|
42
|
+
const newVersion = semver.clean(argv.newVersion);
|
|
43
|
+
if (!semver.valid(newVersion)) {
|
|
44
|
+
cli.warn(`${newVersion} is not a valid semantic version.`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.newVersion = newVersion;
|
|
48
|
+
} else {
|
|
49
|
+
this.newVersion = this.calculateNewVersion();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { upstream, owner, repo, newVersion } = this;
|
|
53
|
+
|
|
54
|
+
this.versionComponents = {
|
|
55
|
+
major: semver.major(newVersion),
|
|
56
|
+
minor: semver.minor(newVersion),
|
|
57
|
+
patch: semver.patch(newVersion)
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
this.stagingBranch = `v${this.versionComponents.major}.x-staging`;
|
|
61
|
+
this.releaseBranch = `v${this.versionComponents.major}.x`;
|
|
62
|
+
|
|
63
|
+
const upstreamHref = runSync('git', [
|
|
64
|
+
'config', '--get',
|
|
65
|
+
`remote.${upstream}.url`]).trim();
|
|
66
|
+
if (!new RegExp(`${owner}/${repo}(?:.git)?$`).test(upstreamHref)) {
|
|
67
|
+
cli.warn('Remote repository URL does not point to the expected ' +
|
|
68
|
+
`repository ${owner}/${repo}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
warnForNonMergeablePR(pr) {
|
|
73
|
+
const { cli } = this;
|
|
74
|
+
|
|
75
|
+
cli.warn(`PR#${pr.number} - ${pr.title} is not 'MERGEABLE'.
|
|
76
|
+
So, it will be skipped. Status: ${pr.mergeable}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getOpenPRs(filterLabels) {
|
|
80
|
+
const credentials = await auth({ github: true });
|
|
81
|
+
const request = new Request(credentials);
|
|
82
|
+
const data = await request.gql('PRs', {
|
|
83
|
+
owner: this.owner,
|
|
84
|
+
repo: this.repo,
|
|
85
|
+
labels: filterLabels
|
|
86
|
+
});
|
|
87
|
+
return data.repository.pullRequests.nodes;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async cherryPickSecurityPRs(filterLabels) {
|
|
91
|
+
const { cli } = this;
|
|
92
|
+
|
|
93
|
+
const prs = await this.getOpenPRs(filterLabels);
|
|
94
|
+
if (prs.length === 0) {
|
|
95
|
+
cli.warn(`There are no PRs available in ${this.owner}/${this.repo}`);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const pr of prs) {
|
|
100
|
+
if (pr.mergeable !== 'MERGEABLE') {
|
|
101
|
+
this.warnForNonMergeablePR(pr);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const cp = new CherryPick(pr.number, this.dir, cli, {
|
|
105
|
+
owner: this.owner,
|
|
106
|
+
repo: this.repo,
|
|
107
|
+
lint: false,
|
|
108
|
+
includeCVE: true
|
|
109
|
+
});
|
|
110
|
+
const success = await cp.start();
|
|
111
|
+
if (!success) {
|
|
112
|
+
cli.warn('The cherry-pick has failed. PR-ID: ' + pr.number);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async prepareSecurity() {
|
|
120
|
+
const {
|
|
121
|
+
cli,
|
|
122
|
+
newVersion,
|
|
123
|
+
versionComponents,
|
|
124
|
+
releaseBranch,
|
|
125
|
+
filterLabels
|
|
126
|
+
} = this;
|
|
127
|
+
|
|
128
|
+
// Create new proposal branch.
|
|
129
|
+
cli.startSpinner(`Creating new proposal branch for ${newVersion}`);
|
|
130
|
+
const proposalBranch = await this.createProposalBranch(releaseBranch);
|
|
131
|
+
cli.stopSpinner(`Created new proposal branch for ${newVersion}`);
|
|
132
|
+
|
|
133
|
+
const success = await this.cherryPickSecurityPRs(filterLabels);
|
|
134
|
+
if (!success) {
|
|
135
|
+
cli.error('Aborting security release preparation. ' +
|
|
136
|
+
'Remember to exclude the proposal branch.' +
|
|
137
|
+
'git branch -D ' + proposalBranch);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Update version and release info in src/node_version.h.
|
|
141
|
+
cli.startSpinner(`Updating 'src/node_version.h' for ${newVersion}`);
|
|
142
|
+
await this.updateNodeVersion();
|
|
143
|
+
cli.stopSpinner(`Updated 'src/node_version.h' for ${newVersion}`);
|
|
144
|
+
|
|
145
|
+
// Update any REPLACEME tags in the docs.
|
|
146
|
+
cli.startSpinner('Updating REPLACEME items in docs');
|
|
147
|
+
await this.updateREPLACEMEs();
|
|
148
|
+
cli.stopSpinner('Updated REPLACEME items in docs');
|
|
149
|
+
|
|
150
|
+
// Fetch date to use in release commit & changelogs.
|
|
151
|
+
const todayDate = new Date().toISOString().split('T')[0];
|
|
152
|
+
this.date = await cli.prompt('Enter release date in YYYY-MM-DD format:',
|
|
153
|
+
{ questionType: 'input', defaultAnswer: todayDate });
|
|
154
|
+
|
|
155
|
+
cli.startSpinner('Updating CHANGELOG.md');
|
|
156
|
+
await this.updateMainChangelog();
|
|
157
|
+
cli.stopSpinner('Updated CHANGELOG.md');
|
|
158
|
+
|
|
159
|
+
cli.startSpinner(`Updating CHANGELOG_V${versionComponents.major}.md`);
|
|
160
|
+
await this.updateMajorChangelog();
|
|
161
|
+
cli.stopSpinner(`Updated CHANGELOG_V${versionComponents.major}.md`);
|
|
162
|
+
|
|
163
|
+
// Create release commit.
|
|
164
|
+
const shouldCreateReleaseCommit = await cli.prompt(
|
|
165
|
+
'Create release commit?');
|
|
166
|
+
if (!shouldCreateReleaseCommit) {
|
|
167
|
+
cli.warn(`Aborting \`git node release\` for version ${newVersion}`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Proceed with release only after the releaser has amended
|
|
172
|
+
// it to their liking.
|
|
173
|
+
const createDefaultCommit = await this.createReleaseCommit();
|
|
174
|
+
if (!createDefaultCommit) {
|
|
175
|
+
const lastCommitSha = runSync('git', ['rev-parse', '--short', 'HEAD']);
|
|
176
|
+
cli.warn(`Please manually edit commit ${lastCommitSha} by running ` +
|
|
177
|
+
'`git commit --amend` before proceeding.');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
cli.separator();
|
|
181
|
+
cli.ok(`Release preparation for ${newVersion} complete.\n`);
|
|
182
|
+
cli.info(
|
|
183
|
+
'To finish the release proposal, run: \n' +
|
|
184
|
+
` $ git push -u ${this.upstream} v${newVersion}-proposal\n` +
|
|
185
|
+
'Finally, proceed to Jenkins and begin the following CI jobs:\n' +
|
|
186
|
+
' * https://ci.nodejs.org/job/node-test-pull-request/\n' +
|
|
187
|
+
' * https://ci.nodejs.org/job/citgm-smoker/');
|
|
188
|
+
cli.info(
|
|
189
|
+
'If this release has deps/v8 changes, you\'ll also need to run:\n' +
|
|
190
|
+
' * https://ci.nodejs.org/job/node-test-commit-v8-linux/'
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async prepare() {
|
|
195
|
+
const { cli, newVersion, versionComponents, isSecurityRelease } = this;
|
|
196
|
+
|
|
197
|
+
if (isSecurityRelease) {
|
|
198
|
+
this.config.owner = 'nodejs-private';
|
|
199
|
+
this.config.repo = 'node-private';
|
|
200
|
+
return this.prepareSecurity();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (this.runBranchDiff) {
|
|
204
|
+
// TODO: UPDATE re-use
|
|
205
|
+
// Check the branch diff to determine if the releaser
|
|
206
|
+
// wants to backport any more commits before proceeding.
|
|
207
|
+
cli.startSpinner('Fetching branch-diff');
|
|
208
|
+
const raw = this.getBranchDiff({
|
|
209
|
+
onlyNotableChanges: false,
|
|
210
|
+
comparisonBranch: newVersion
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const diff = raw.split('*');
|
|
214
|
+
cli.stopSpinner('Got branch diff');
|
|
215
|
+
|
|
216
|
+
const outstandingCommits = diff.length - 1;
|
|
217
|
+
if (outstandingCommits !== 0) {
|
|
218
|
+
const staging = `v${semver.major(newVersion)}.x-staging`;
|
|
219
|
+
const proceed = await cli.prompt(
|
|
220
|
+
`There are ${outstandingCommits} commits that may be ` +
|
|
221
|
+
`backported to ${staging} - do you still want to proceed?`,
|
|
222
|
+
{ defaultAnswer: false });
|
|
223
|
+
|
|
224
|
+
if (!proceed) {
|
|
225
|
+
const seeDiff = await cli.prompt(
|
|
226
|
+
'Do you want to see the branch diff?');
|
|
227
|
+
if (seeDiff) cli.log(raw);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Create new proposal branch.
|
|
234
|
+
cli.startSpinner(`Creating new proposal branch for ${newVersion}`);
|
|
235
|
+
await this.createProposalBranch();
|
|
236
|
+
cli.stopSpinner(`Created new proposal branch for ${newVersion}`);
|
|
237
|
+
|
|
238
|
+
if (this.isLTSTransition) {
|
|
239
|
+
// For releases transitioning into LTS, fetch the new code name.
|
|
240
|
+
this.ltsCodename = await this.getLTSCodename(versionComponents.major);
|
|
241
|
+
// Update test for new LTS code name.
|
|
242
|
+
const testFile = path.resolve(
|
|
243
|
+
'test',
|
|
244
|
+
'parallel',
|
|
245
|
+
'test-process-release.js'
|
|
246
|
+
);
|
|
247
|
+
cli.startSpinner(`Updating ${testFile}`);
|
|
248
|
+
await this.updateTestProcessRelease(testFile);
|
|
249
|
+
cli.stopSpinner(`Updating ${testFile}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Update version and release info in src/node_version.h.
|
|
253
|
+
cli.startSpinner(`Updating 'src/node_version.h' for ${newVersion}`);
|
|
254
|
+
await this.updateNodeVersion();
|
|
255
|
+
cli.stopSpinner(`Updated 'src/node_version.h' for ${newVersion}`);
|
|
256
|
+
|
|
257
|
+
// Check whether to update NODE_MODULE_VERSION.
|
|
258
|
+
const isSemverMajor = versionComponents.minor === 0;
|
|
259
|
+
if (isSemverMajor) {
|
|
260
|
+
const shouldUpdateNodeModuleVersion = await cli.prompt(
|
|
261
|
+
'Update NODE_MODULE_VERSION?', { defaultAnswer: false });
|
|
262
|
+
if (shouldUpdateNodeModuleVersion) {
|
|
263
|
+
const variant = await cli.prompt(
|
|
264
|
+
'Specify variant (ex. \'v8_7.9\') for new NODE_MODULE_VERSION:',
|
|
265
|
+
{ questionType: 'input', noSeparator: true });
|
|
266
|
+
const versions = await cli.prompt(
|
|
267
|
+
'Specify versions (ex. \'14.0.0-pre\') for new NODE_MODULE_VERSION:',
|
|
268
|
+
{ questionType: 'input', noSeparator: true });
|
|
269
|
+
this.updateNodeModuleVersion('node', variant, versions);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Update any REPLACEME tags in the docs.
|
|
274
|
+
cli.startSpinner('Updating REPLACEME items in docs');
|
|
275
|
+
await this.updateREPLACEMEs();
|
|
276
|
+
cli.stopSpinner('Updated REPLACEME items in docs');
|
|
277
|
+
|
|
278
|
+
// Fetch date to use in release commit & changelogs.
|
|
279
|
+
const todayDate = new Date().toISOString().split('T')[0];
|
|
280
|
+
this.date = await cli.prompt('Enter release date in YYYY-MM-DD format:',
|
|
281
|
+
{ questionType: 'input', defaultAnswer: todayDate });
|
|
282
|
+
|
|
283
|
+
cli.startSpinner('Updating CHANGELOG.md');
|
|
284
|
+
await this.updateMainChangelog();
|
|
285
|
+
cli.stopSpinner('Updated CHANGELOG.md');
|
|
286
|
+
|
|
287
|
+
cli.startSpinner(`Updating CHANGELOG_V${versionComponents.major}.md`);
|
|
288
|
+
await this.updateMajorChangelog();
|
|
289
|
+
cli.stopSpinner(`Updated CHANGELOG_V${versionComponents.major}.md`);
|
|
290
|
+
|
|
291
|
+
await cli.prompt('Finished editing the changelogs?',
|
|
292
|
+
{ defaultAnswer: false });
|
|
293
|
+
|
|
294
|
+
// Create release commit.
|
|
295
|
+
const shouldCreateReleaseCommit = await cli.prompt(
|
|
296
|
+
'Create release commit?');
|
|
297
|
+
if (!shouldCreateReleaseCommit) {
|
|
298
|
+
cli.warn(`Aborting \`git node release\` for version ${newVersion}`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Proceed with release only after the releaser has amended
|
|
303
|
+
// it to their liking.
|
|
304
|
+
const createDefaultCommit = await this.createReleaseCommit();
|
|
305
|
+
if (!createDefaultCommit) {
|
|
306
|
+
const lastCommitSha = runSync('git', ['rev-parse', '--short', 'HEAD']);
|
|
307
|
+
cli.warn(`Please manually edit commit ${lastCommitSha} by running ` +
|
|
308
|
+
'`git commit --amend` before proceeding.');
|
|
309
|
+
|
|
310
|
+
await cli.prompt(
|
|
311
|
+
'Finished editing the release commit?',
|
|
312
|
+
{ defaultAnswer: false });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
cli.separator();
|
|
316
|
+
cli.ok(`Release preparation for ${newVersion} complete.\n`);
|
|
317
|
+
cli.info(
|
|
318
|
+
'To finish the release proposal, run: \n' +
|
|
319
|
+
` $ git push -u ${this.upstream} v${newVersion}-proposal\n` +
|
|
320
|
+
'Finally, proceed to Jenkins and begin the following CI jobs:\n' +
|
|
321
|
+
' * https://ci.nodejs.org/job/node-test-pull-request/\n' +
|
|
322
|
+
' * https://ci.nodejs.org/job/citgm-smoker/');
|
|
323
|
+
cli.info(
|
|
324
|
+
'If this release has deps/v8 changes, you\'ll also need to run:\n' +
|
|
325
|
+
' * https://ci.nodejs.org/job/node-test-commit-v8-linux/'
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
get owner() {
|
|
330
|
+
return this.config.owner || 'nodejs';
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
get repo() {
|
|
334
|
+
return this.config.repo || 'node';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
get upstream() {
|
|
338
|
+
return this.config.upstream;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
get username() {
|
|
342
|
+
return this.config.username;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
warnForMissing() {
|
|
346
|
+
const { cli, upstream, username } = this;
|
|
347
|
+
|
|
348
|
+
const missing = !username || !upstream;
|
|
349
|
+
if (!upstream) {
|
|
350
|
+
cli.warn('You have not told git-node the remote you want to sync with.');
|
|
351
|
+
cli.separator();
|
|
352
|
+
cli.info(
|
|
353
|
+
'For example, if your remote pointing to nodejs/node is' +
|
|
354
|
+
' `remote-upstream`, you can run:\n\n' +
|
|
355
|
+
' $ ncu-config set upstream remote-upstream');
|
|
356
|
+
cli.separator();
|
|
357
|
+
cli.setExitCode(1);
|
|
358
|
+
}
|
|
359
|
+
if (!username) {
|
|
360
|
+
cli.warn('You have not told git-node your username.');
|
|
361
|
+
cli.separator();
|
|
362
|
+
cli.info(
|
|
363
|
+
'To fix this, you can run: ' +
|
|
364
|
+
' $ ncu-config set username <your_username>');
|
|
365
|
+
cli.separator();
|
|
366
|
+
cli.setExitCode(1);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return missing;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
calculateNewVersion() {
|
|
373
|
+
let newVersion;
|
|
374
|
+
|
|
375
|
+
const lastTagVersion = semver.clean(this.getLastRef());
|
|
376
|
+
const lastTag = {
|
|
377
|
+
major: semver.major(lastTagVersion),
|
|
378
|
+
minor: semver.minor(lastTagVersion),
|
|
379
|
+
patch: semver.patch(lastTagVersion)
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const changelog = this.getChangelog();
|
|
383
|
+
|
|
384
|
+
if (changelog.includes('SEMVER-MAJOR')) {
|
|
385
|
+
newVersion = `${lastTag.major + 1}.0.0`;
|
|
386
|
+
} else if (changelog.includes('SEMVER-MINOR') || this.isLTSTransition) {
|
|
387
|
+
newVersion = `${lastTag.major}.${lastTag.minor + 1}.0`;
|
|
388
|
+
} else {
|
|
389
|
+
newVersion = `${lastTag.major}.${lastTag.minor}.${lastTag.patch + 1}`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return newVersion;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
getCurrentBranch() {
|
|
396
|
+
return runSync('git', ['rev-parse', '--abbrev-ref', 'HEAD']).trim();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
getLastRef() {
|
|
400
|
+
return runSync('git', ['describe', '--abbrev=0', '--tags']).trim();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
getChangelog() {
|
|
404
|
+
const changelogMaker = new URL(
|
|
405
|
+
'../node_modules/.bin/changelog-maker' + (isWindows ? '.cmd' : ''),
|
|
406
|
+
import.meta.url
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
return runSync(changelogMaker, [
|
|
410
|
+
'--group',
|
|
411
|
+
'--markdown',
|
|
412
|
+
'--filter-release',
|
|
413
|
+
'--start-ref',
|
|
414
|
+
this.getLastRef()
|
|
415
|
+
]).trim();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async getLTSCodename(version) {
|
|
419
|
+
const { cli } = this;
|
|
420
|
+
return await cli.prompt(
|
|
421
|
+
'Enter the LTS code name for this release line\n' +
|
|
422
|
+
'(Refs: https://github.com/nodejs/Release/blob/main/CODENAMES.md):',
|
|
423
|
+
{ questionType: 'input', noSeparator: true, defaultAnswer: '' }
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async updateREPLACEMEs() {
|
|
428
|
+
const { newVersion } = this;
|
|
429
|
+
|
|
430
|
+
await replace({
|
|
431
|
+
files: 'doc/api/*.md',
|
|
432
|
+
from: /REPLACEME/g,
|
|
433
|
+
to: `v${newVersion}`
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async updateMainChangelog() {
|
|
438
|
+
const { date, isLTSTransition, versionComponents, newVersion } = this;
|
|
439
|
+
|
|
440
|
+
// Remove the leading 'v'.
|
|
441
|
+
const lastRef = this.getLastRef().substring(1);
|
|
442
|
+
|
|
443
|
+
const mainChangelogPath = path.resolve('CHANGELOG.md');
|
|
444
|
+
const data = await fs.readFile(mainChangelogPath, 'utf8');
|
|
445
|
+
const arr = data.split('\n');
|
|
446
|
+
|
|
447
|
+
const major = versionComponents.major;
|
|
448
|
+
const hrefLink = `doc/changelogs/CHANGELOG_V${major}.md`;
|
|
449
|
+
const newRefLink = `<a href="${hrefLink}#${newVersion}">${newVersion}</a>`;
|
|
450
|
+
const lastRefLink = `<a href="${hrefLink}#${lastRef}">${lastRef}</a>`;
|
|
451
|
+
|
|
452
|
+
for (let idx = 0; idx < arr.length; idx++) {
|
|
453
|
+
if (isLTSTransition) {
|
|
454
|
+
if (arr[idx].includes(hrefLink)) {
|
|
455
|
+
const eolDate = getEOLDate(date);
|
|
456
|
+
const eol = eolDate.toISOString().split('-').slice(0, 2).join('-');
|
|
457
|
+
arr[idx] = arr[idx].replace('**Current**', '**Long Term Support**');
|
|
458
|
+
arr[idx] = arr[idx].replace('"Current"', `"LTS Until ${eol}"`);
|
|
459
|
+
arr[idx] = arr[idx].replace('(Current)', '(LTS)');
|
|
460
|
+
} else if (arr[idx].includes('**Long Term Support**')) {
|
|
461
|
+
arr[idx] = arr[idx].replace(
|
|
462
|
+
'**Long Term Support**',
|
|
463
|
+
'Long Term Support'
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (arr[idx].includes(`<b>${lastRefLink}</b><br/>`)) {
|
|
468
|
+
arr.splice(idx, 1, `<b>${newRefLink}</b><br/>`, `${lastRefLink}<br/>`);
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
await fs.writeFile(mainChangelogPath, arr.join('\n'));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async updateMajorChangelog() {
|
|
477
|
+
const {
|
|
478
|
+
versionComponents,
|
|
479
|
+
newVersion,
|
|
480
|
+
date,
|
|
481
|
+
isLTS,
|
|
482
|
+
isLTSTransition,
|
|
483
|
+
isSecurityRelease,
|
|
484
|
+
ltsCodename,
|
|
485
|
+
username
|
|
486
|
+
} = this;
|
|
487
|
+
|
|
488
|
+
const releaseInfo = isLTS ? `'${ltsCodename}' (LTS)` : '(Current)';
|
|
489
|
+
const lastRef = this.getLastRef();
|
|
490
|
+
const majorChangelogPath = path.resolve(
|
|
491
|
+
'doc',
|
|
492
|
+
'changelogs',
|
|
493
|
+
`CHANGELOG_V${versionComponents.major}.md`
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
const data = await fs.readFile(majorChangelogPath, 'utf8');
|
|
497
|
+
const arr = data.split('\n');
|
|
498
|
+
const allCommits = this.getChangelog();
|
|
499
|
+
const notableChanges = this.getBranchDiff({ onlyNotableChanges: true });
|
|
500
|
+
let releaseHeader = `## ${date}, Version ${newVersion}` +
|
|
501
|
+
` ${releaseInfo}, @${username}\n`;
|
|
502
|
+
if (isSecurityRelease) {
|
|
503
|
+
releaseHeader += '\nThis is a security release.';
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const topHeader =
|
|
507
|
+
`<a href="#${lastRef.substring(1)}">${lastRef.substring(1)}</a>`;
|
|
508
|
+
const newHeader =
|
|
509
|
+
`<a href="#${newVersion}">${newVersion}</a><br/>`;
|
|
510
|
+
for (let idx = 0; idx < arr.length; idx++) {
|
|
511
|
+
if (isLTSTransition && arr[idx].includes('<th>Current</th>')) {
|
|
512
|
+
// Create a new column for LTS.
|
|
513
|
+
arr.splice(idx, 0, `<th>LTS '${ltsCodename}'</th>`);
|
|
514
|
+
idx++;
|
|
515
|
+
} else if (arr[idx].includes(topHeader)) {
|
|
516
|
+
if (isLTSTransition) {
|
|
517
|
+
// New release needs to go into the new column for LTS.
|
|
518
|
+
const toAppend = [
|
|
519
|
+
newHeader,
|
|
520
|
+
'</td>',
|
|
521
|
+
arr[idx - 1]
|
|
522
|
+
];
|
|
523
|
+
arr.splice(idx, 0, ...toAppend);
|
|
524
|
+
idx += toAppend.length;
|
|
525
|
+
} else {
|
|
526
|
+
arr.splice(idx, 0, newHeader);
|
|
527
|
+
idx++;
|
|
528
|
+
}
|
|
529
|
+
} else if (arr[idx].includes(`<a id="${lastRef.substring(1)}"></a>`)) {
|
|
530
|
+
const toAppend = [];
|
|
531
|
+
toAppend.push(`<a id="${newVersion}"></a>\n`);
|
|
532
|
+
toAppend.push(releaseHeader);
|
|
533
|
+
toAppend.push('### Notable Changes\n');
|
|
534
|
+
if (isLTSTransition) {
|
|
535
|
+
toAppend.push(`${getStartLTSBlurb(this)}\n`);
|
|
536
|
+
}
|
|
537
|
+
if (notableChanges.trim()) {
|
|
538
|
+
toAppend.push(notableChanges);
|
|
539
|
+
}
|
|
540
|
+
toAppend.push('### Commits\n');
|
|
541
|
+
toAppend.push(allCommits);
|
|
542
|
+
toAppend.push('');
|
|
543
|
+
|
|
544
|
+
arr.splice(idx, 0, ...toAppend);
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
await fs.writeFile(majorChangelogPath, arr.join('\n'));
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async createProposalBranch(base = this.stagingBranch) {
|
|
553
|
+
const { upstream, newVersion } = this;
|
|
554
|
+
const proposalBranch = `v${newVersion}-proposal`;
|
|
555
|
+
|
|
556
|
+
await runAsync('git', [
|
|
557
|
+
'checkout',
|
|
558
|
+
'-b',
|
|
559
|
+
proposalBranch,
|
|
560
|
+
`${upstream}/${base}`
|
|
561
|
+
]);
|
|
562
|
+
return proposalBranch;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async updateNodeVersion() {
|
|
566
|
+
const { ltsCodename, versionComponents } = this;
|
|
567
|
+
|
|
568
|
+
const filePath = path.resolve('src', 'node_version.h');
|
|
569
|
+
const data = await fs.readFile(filePath, 'utf8');
|
|
570
|
+
const arr = data.split('\n');
|
|
571
|
+
|
|
572
|
+
arr.forEach((line, idx) => {
|
|
573
|
+
if (line.includes('#define NODE_MAJOR_VERSION')) {
|
|
574
|
+
arr[idx] = `#define NODE_MAJOR_VERSION ${versionComponents.major}`;
|
|
575
|
+
} else if (line.includes('#define NODE_MINOR_VERSION')) {
|
|
576
|
+
arr[idx] = `#define NODE_MINOR_VERSION ${versionComponents.minor}`;
|
|
577
|
+
} else if (line.includes('#define NODE_PATCH_VERSION')) {
|
|
578
|
+
arr[idx] = `#define NODE_PATCH_VERSION ${versionComponents.patch}`;
|
|
579
|
+
} else if (line.includes('#define NODE_VERSION_IS_RELEASE')) {
|
|
580
|
+
arr[idx] = '#define NODE_VERSION_IS_RELEASE 1';
|
|
581
|
+
} else if (line.includes('#define NODE_VERSION_IS_LTS')) {
|
|
582
|
+
this.isLTS = arr[idx].split(' ')[2] === '1';
|
|
583
|
+
if (this.isLTSTransition) {
|
|
584
|
+
if (this.isLTS) {
|
|
585
|
+
throw new Error('Previous release was already marked as LTS.');
|
|
586
|
+
}
|
|
587
|
+
this.isLTS = true;
|
|
588
|
+
arr[idx] = '#define NODE_VERSION_IS_LTS 1';
|
|
589
|
+
arr[idx + 1] = `#define NODE_VERSION_LTS_CODENAME "${ltsCodename}"`;
|
|
590
|
+
} else {
|
|
591
|
+
this.ltsCodename = arr[idx + 1].split(' ')[2].slice(1, -1);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
await fs.writeFile(filePath, arr.join('\n'));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
updateNodeModuleVersion(runtime, variant, versions) {
|
|
600
|
+
const nmvFilePath = path.resolve('doc', 'abi_version_registry.json');
|
|
601
|
+
const nmvArray = readJson(nmvFilePath).NODE_MODULE_VERSION;
|
|
602
|
+
|
|
603
|
+
const latestNMV = nmvArray[0];
|
|
604
|
+
const modules = latestNMV.modules + 1;
|
|
605
|
+
nmvArray.unshift({ modules, runtime, variant, versions });
|
|
606
|
+
|
|
607
|
+
writeJson(nmvFilePath, { NODE_MODULE_VERSION: nmvArray });
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
async updateTestProcessRelease(testFile) {
|
|
611
|
+
const data = await fs.readFile(testFile, { encoding: 'utf8' });
|
|
612
|
+
const updated = updateTestProcessRelease(data, this);
|
|
613
|
+
await fs.writeFile(testFile, updated);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async createReleaseCommit() {
|
|
617
|
+
const {
|
|
618
|
+
cli,
|
|
619
|
+
isLTS,
|
|
620
|
+
isLTSTransition,
|
|
621
|
+
ltsCodename,
|
|
622
|
+
newVersion,
|
|
623
|
+
isSecurityRelease,
|
|
624
|
+
date
|
|
625
|
+
} = this;
|
|
626
|
+
|
|
627
|
+
const releaseInfo = isLTS ? `'${ltsCodename}' (LTS)` : '(Current)';
|
|
628
|
+
const messageTitle = `${date}, Version ${newVersion} ${releaseInfo}`;
|
|
629
|
+
|
|
630
|
+
const messageBody = [];
|
|
631
|
+
if (isSecurityRelease) {
|
|
632
|
+
messageBody.push('This is a security release.\n\n');
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const notableChanges = this.getBranchDiff({
|
|
636
|
+
onlyNotableChanges: true,
|
|
637
|
+
format: 'plaintext'
|
|
638
|
+
});
|
|
639
|
+
messageBody.push('Notable changes:\n\n');
|
|
640
|
+
if (isLTSTransition) {
|
|
641
|
+
messageBody.push(`${getStartLTSBlurb(this)}\n\n`);
|
|
642
|
+
}
|
|
643
|
+
messageBody.push(notableChanges);
|
|
644
|
+
messageBody.push('\nPR-URL: TODO');
|
|
645
|
+
|
|
646
|
+
// Create commit and then allow releaser to amend.
|
|
647
|
+
runSync('git', ['add', '.']);
|
|
648
|
+
runSync('git', [
|
|
649
|
+
'commit',
|
|
650
|
+
'-m',
|
|
651
|
+
messageTitle,
|
|
652
|
+
'-m',
|
|
653
|
+
messageBody.join('')
|
|
654
|
+
]);
|
|
655
|
+
|
|
656
|
+
cli.log(`${messageTitle}\n\n${messageBody.join('')}`);
|
|
657
|
+
const useMessage = await cli.prompt(
|
|
658
|
+
'Continue with this commit message?', { defaultAnswer: false });
|
|
659
|
+
return useMessage;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
getBranchDiff(opts) {
|
|
663
|
+
const {
|
|
664
|
+
versionComponents = {},
|
|
665
|
+
upstream,
|
|
666
|
+
newVersion,
|
|
667
|
+
isLTS
|
|
668
|
+
} = this;
|
|
669
|
+
|
|
670
|
+
let majorVersion;
|
|
671
|
+
let stagingBranch;
|
|
672
|
+
if (Object.keys(versionComponents).length !== 0) {
|
|
673
|
+
majorVersion = versionComponents.major;
|
|
674
|
+
stagingBranch = this.stagingBranch;
|
|
675
|
+
} else {
|
|
676
|
+
stagingBranch = this.getCurrentBranch();
|
|
677
|
+
const stagingBranchSemver = semver.coerce(stagingBranch);
|
|
678
|
+
majorVersion = stagingBranchSemver.major;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
let branchDiffOptions;
|
|
682
|
+
if (opts.onlyNotableChanges) {
|
|
683
|
+
const proposalBranch = `v${newVersion}-proposal`;
|
|
684
|
+
const releaseBranch = `v${majorVersion}.x`;
|
|
685
|
+
|
|
686
|
+
const notableLabels = [
|
|
687
|
+
'notable-change',
|
|
688
|
+
'semver-minor'
|
|
689
|
+
];
|
|
690
|
+
|
|
691
|
+
branchDiffOptions = [
|
|
692
|
+
`${upstream}/${releaseBranch}`,
|
|
693
|
+
proposalBranch,
|
|
694
|
+
`--require-label=${notableLabels.join(',')}`,
|
|
695
|
+
`--format=${opts.format || 'markdown'}`,
|
|
696
|
+
'--group'
|
|
697
|
+
];
|
|
698
|
+
} else {
|
|
699
|
+
const excludeLabels = [
|
|
700
|
+
'semver-major',
|
|
701
|
+
`dont-land-on-v${majorVersion}.x`,
|
|
702
|
+
`backport-requested-v${majorVersion}.x`,
|
|
703
|
+
`backported-to-v${majorVersion}.x`,
|
|
704
|
+
`backport-blocked-v${majorVersion}.x`,
|
|
705
|
+
`backport-open-v${majorVersion}.x`,
|
|
706
|
+
'baking-for-lts'
|
|
707
|
+
];
|
|
708
|
+
|
|
709
|
+
let comparisonBranch = 'main';
|
|
710
|
+
const isSemverMinor = versionComponents.patch === 0;
|
|
711
|
+
if (isLTS) {
|
|
712
|
+
// Assume Current branch matches tag with highest semver value.
|
|
713
|
+
const tags = runSync('git',
|
|
714
|
+
['tag', '-l', '--sort', '-version:refname']).trim();
|
|
715
|
+
const highestVersionTag = tags.split('\n')[0];
|
|
716
|
+
comparisonBranch = `v${semver.coerce(highestVersionTag).major}.x`;
|
|
717
|
+
|
|
718
|
+
if (!isSemverMinor) {
|
|
719
|
+
excludeLabels.push('semver-minor');
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
branchDiffOptions = [
|
|
724
|
+
stagingBranch,
|
|
725
|
+
comparisonBranch,
|
|
726
|
+
`--exclude-label=${excludeLabels.join(',')}`,
|
|
727
|
+
'--filter-release'
|
|
728
|
+
];
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const branchDiff = new URL(
|
|
732
|
+
'../node_modules/.bin/branch-diff' + (isWindows ? '.cmd' : ''),
|
|
733
|
+
import.meta.url
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
return runSync(branchDiff, branchDiffOptions);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
warnForWrongBranch() {
|
|
740
|
+
const {
|
|
741
|
+
cli,
|
|
742
|
+
stagingBranch,
|
|
743
|
+
releaseBranch,
|
|
744
|
+
versionComponents,
|
|
745
|
+
isSecurityRelease
|
|
746
|
+
} = this;
|
|
747
|
+
const rev = this.getCurrentBranch();
|
|
748
|
+
|
|
749
|
+
if (rev === 'HEAD') {
|
|
750
|
+
cli.warn(
|
|
751
|
+
'You are in detached HEAD state. Please run git-node on a valid ' +
|
|
752
|
+
'branch');
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
const targetBranch = isSecurityRelease ? releaseBranch : stagingBranch;
|
|
756
|
+
|
|
757
|
+
if (rev === targetBranch) {
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
cli.warn(
|
|
762
|
+
'You are trying to create a new release proposal branch for ' +
|
|
763
|
+
`v${versionComponents.major}, but you're checked out on ` +
|
|
764
|
+
`${rev} and not ${targetBranch}.`);
|
|
765
|
+
cli.separator();
|
|
766
|
+
cli.info(
|
|
767
|
+
`Switch to \`${targetBranch}\` with \`git` +
|
|
768
|
+
` checkout ${targetBranch}\` before proceeding.`);
|
|
769
|
+
cli.separator();
|
|
770
|
+
return true;
|
|
771
|
+
}
|
|
772
|
+
}
|