@node-core/utils 6.2.0 → 6.3.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/bin/ncu-ci.js +9 -1
- package/components/git/land.js +6 -10
- package/components/git/security.js +23 -40
- package/lib/auth.js +6 -11
- package/lib/ci/build-types/pr_build.js +1 -2
- package/lib/ci/run_ci.js +25 -6
- package/lib/config.js +14 -2
- package/lib/landing_session.js +64 -26
- 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 +3 -2
- package/lib/voting_session.js +13 -18
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
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
|
@@ -168,7 +168,7 @@ async function main(state, argv, cli, dir) {
|
|
|
168
168
|
github: true
|
|
169
169
|
});
|
|
170
170
|
const req = new Request(credentials);
|
|
171
|
-
let session = new LandingSession(cli, req, dir);
|
|
171
|
+
let session = new LandingSession(cli, req, dir, argv);
|
|
172
172
|
|
|
173
173
|
if (state !== AMEND &&
|
|
174
174
|
state !== CONTINUE &&
|
|
@@ -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
|
@@ -7,26 +7,21 @@ import { clearCachedConfig, encryptValue, getMergedConfig, getNcurcPath } from '
|
|
|
7
7
|
|
|
8
8
|
export default lazy(auth);
|
|
9
9
|
|
|
10
|
-
function errorExit(message) {
|
|
11
|
-
process.stderr.write(`${message}\n`);
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
10
|
function check(username, token, format = /^[A-Za-z0-9_]+$/) {
|
|
16
11
|
if (typeof username !== 'string') {
|
|
17
|
-
|
|
12
|
+
throw new Error(`username must be a string, received ${typeof username}`);
|
|
18
13
|
}
|
|
19
14
|
if (!/^[a-zA-Z0-9-]+$/.test(username)) {
|
|
20
|
-
|
|
15
|
+
throw new Error(
|
|
21
16
|
'username may only contain alphanumeric characters or hyphens, ' +
|
|
22
17
|
`received ${username}`
|
|
23
18
|
);
|
|
24
19
|
}
|
|
25
20
|
if (typeof token !== 'string') {
|
|
26
|
-
|
|
21
|
+
throw new Error(`token must be a string, received ${typeof token}`);
|
|
27
22
|
}
|
|
28
23
|
if (!format.test(token)) {
|
|
29
|
-
|
|
24
|
+
throw new Error(`token is misformatted: ${token}`);
|
|
30
25
|
}
|
|
31
26
|
}
|
|
32
27
|
|
|
@@ -51,7 +46,7 @@ async function tryCreateGitHubToken(githubAuth) {
|
|
|
51
46
|
noDeviceFlow: true
|
|
52
47
|
});
|
|
53
48
|
} catch (e) {
|
|
54
|
-
|
|
49
|
+
throw new Error(`Could not get token: ${e.message}`, { cause: e });
|
|
55
50
|
}
|
|
56
51
|
return credentials;
|
|
57
52
|
}
|
|
@@ -93,7 +88,7 @@ async function auth(
|
|
|
93
88
|
get jenkins() {
|
|
94
89
|
const { username, jenkins_token } = getMergedConfig();
|
|
95
90
|
if (!username || !jenkins_token) {
|
|
96
|
-
|
|
91
|
+
throw new Error(
|
|
97
92
|
'Get your Jenkins API token in https://ci.nodejs.org/me/security ' +
|
|
98
93
|
'and run the following command to add it to your ncu config: ' +
|
|
99
94
|
'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/config.js
CHANGED
|
@@ -25,9 +25,21 @@ export function getMergedConfig(dir, home, additional) {
|
|
|
25
25
|
const globalConfig = getConfig(GLOBAL_CONFIG, home);
|
|
26
26
|
const projectConfig = getConfig(PROJECT_CONFIG, dir);
|
|
27
27
|
const localConfig = getConfig(LOCAL_CONFIG, dir);
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
// Using property descriptors to avoid calling any setter if e.g. a
|
|
30
|
+
// localConfig value overrides a globalConfig key.
|
|
31
|
+
mergedConfig = Object.create(null, {
|
|
32
|
+
...Object.getOwnPropertyDescriptors(globalConfig),
|
|
33
|
+
...Object.getOwnPropertyDescriptors(projectConfig),
|
|
34
|
+
...Object.getOwnPropertyDescriptors(localConfig),
|
|
35
|
+
});
|
|
29
36
|
}
|
|
30
|
-
return
|
|
37
|
+
return additional
|
|
38
|
+
? Object.create(null, {
|
|
39
|
+
...Object.getOwnPropertyDescriptors(mergedConfig),
|
|
40
|
+
...Object.getOwnPropertyDescriptors(additional),
|
|
41
|
+
})
|
|
42
|
+
: mergedConfig;
|
|
31
43
|
};
|
|
32
44
|
export function clearCachedConfig() {
|
|
33
45
|
mergedConfig = null;
|
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+)$/i;
|
|
320
|
+
|
|
289
321
|
this.startAmending();
|
|
290
322
|
|
|
291
323
|
const rev = this.getCurrentRev();
|
|
@@ -303,7 +335,12 @@ export default class LandingSession extends Session {
|
|
|
303
335
|
}).trim().length !== 0;
|
|
304
336
|
|
|
305
337
|
const metadata = this.metadata.trim().split('\n');
|
|
306
|
-
|
|
338
|
+
// Filtering out metadata that we will add ourselves:
|
|
339
|
+
const isFiltered = line =>
|
|
340
|
+
(!PR_RE.test(line) || this.backport) &&
|
|
341
|
+
!BACKPORT_RE.test(line) &&
|
|
342
|
+
!REVIEW_RE.test(line);
|
|
343
|
+
const amended = original.split('\n').filter(isFiltered);
|
|
307
344
|
|
|
308
345
|
// If the original commit message already contains trailers (such as
|
|
309
346
|
// "Co-authored-by"), we simply add our own metadata after those. Otherwise,
|
|
@@ -313,35 +350,29 @@ export default class LandingSession extends Session {
|
|
|
313
350
|
amended.push('');
|
|
314
351
|
}
|
|
315
352
|
|
|
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+)/i;
|
|
320
|
-
|
|
321
353
|
let containCVETrailer = false;
|
|
322
354
|
for (const line of metadata) {
|
|
323
|
-
if (line.length !== 0 && original.includes(line)) {
|
|
324
|
-
|
|
325
|
-
containCVETrailer = true;
|
|
326
|
-
}
|
|
355
|
+
if (line.length !== 0 && original.includes(line) && !isFiltered(line)) {
|
|
356
|
+
containCVETrailer ||= CVE_RE.test(line);
|
|
327
357
|
if (originalHasTrailers) {
|
|
328
358
|
cli.warn(`Found ${line}, skipping..`);
|
|
359
|
+
continue;
|
|
329
360
|
} else {
|
|
330
361
|
cli.error('Git found no trailers in the original commit message, ' +
|
|
331
362
|
`but '${line}' is present and should be a trailer.`);
|
|
332
363
|
process.exit(1); // make it work with git rebase -x
|
|
333
364
|
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
amended.splice(prIndex
|
|
341
|
-
|
|
342
|
-
amended.push(line);
|
|
365
|
+
}
|
|
366
|
+
if (BACKPORT_RE.test(line)) {
|
|
367
|
+
const prIndex =
|
|
368
|
+
(amended.findIndex(datum => PR_RE.test(datum)) + 1) ||
|
|
369
|
+
amended.findIndex(datum => REVIEW_RE.test(datum));
|
|
370
|
+
if (prIndex !== -1) {
|
|
371
|
+
amended.splice(prIndex, 0, line);
|
|
372
|
+
continue;
|
|
343
373
|
}
|
|
344
374
|
}
|
|
375
|
+
amended.push(line);
|
|
345
376
|
}
|
|
346
377
|
|
|
347
378
|
if (!containCVETrailer && this.includeCVE) {
|
|
@@ -472,7 +503,7 @@ export default class LandingSession extends Session {
|
|
|
472
503
|
}
|
|
473
504
|
|
|
474
505
|
cli.separator();
|
|
475
|
-
cli.log('The following commits are
|
|
506
|
+
cli.log('The following commits are landable on ' +
|
|
476
507
|
`${upstream}/${branch}`);
|
|
477
508
|
cli.log(`- ${strayVerbose.join('\n- ')}`);
|
|
478
509
|
cli.separator();
|
|
@@ -483,8 +514,15 @@ export default class LandingSession extends Session {
|
|
|
483
514
|
willBeLanded = `${head}...${willBeLanded}`;
|
|
484
515
|
}
|
|
485
516
|
|
|
517
|
+
cli.startSpinner('Removing temporary files');
|
|
486
518
|
this.cleanFiles();
|
|
487
|
-
cli.
|
|
519
|
+
cli.stopSpinner('Temporary files removed.');
|
|
520
|
+
if (this.crossRepoPR) {
|
|
521
|
+
cli.warn(`The configured remote "${upstream}" does not point to the ` +
|
|
522
|
+
'Pull Request repository.');
|
|
523
|
+
cli.info(`${willBeLanded} should likely not be pushed to "${upstream}"`);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
488
526
|
cli.log('To finish landing:');
|
|
489
527
|
cli.log('1. Run: ');
|
|
490
528
|
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
|
}
|
package/lib/prepare_security.js
CHANGED
|
@@ -203,23 +203,19 @@ export default class PrepareSecurityRelease extends SecurityRelease {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
async createVulnerabilitiesJSON(reports, dependencies, releaseDate) {
|
|
206
|
-
this.cli.
|
|
207
|
-
const
|
|
206
|
+
this.cli.startSpinner('Creating vulnerabilities.json...');
|
|
207
|
+
const fileContent = JSON.stringify({
|
|
208
208
|
releaseDate,
|
|
209
209
|
reports,
|
|
210
210
|
dependencies
|
|
211
|
-
}, null, 2);
|
|
211
|
+
}, null, 2) + '\n';
|
|
212
212
|
|
|
213
|
-
const folderPath = path.
|
|
214
|
-
|
|
215
|
-
fs.accessSync(folderPath);
|
|
216
|
-
} catch (error) {
|
|
217
|
-
fs.mkdirSync(folderPath, { recursive: true });
|
|
218
|
-
}
|
|
213
|
+
const folderPath = path.resolve(NEXT_SECURITY_RELEASE_FOLDER);
|
|
214
|
+
await fs.promises.mkdir(folderPath, { recursive: true });
|
|
219
215
|
|
|
220
216
|
const fullPath = path.join(folderPath, 'vulnerabilities.json');
|
|
221
|
-
fs.
|
|
222
|
-
this.cli.
|
|
217
|
+
await fs.promises.writeFile(fullPath, fileContent);
|
|
218
|
+
this.cli.stopSpinner(`Created ${fullPath}`);
|
|
223
219
|
|
|
224
220
|
return fullPath;
|
|
225
221
|
}
|
package/lib/request.js
CHANGED
|
@@ -32,6 +32,14 @@ export default class Request {
|
|
|
32
32
|
if (url.startsWith(`https://${CI_DOMAIN}`)) {
|
|
33
33
|
options.headers = options.headers || {};
|
|
34
34
|
Object.assign(options.headers, this.getJenkinsHeaders());
|
|
35
|
+
} else if (!url.startsWith('https://')) {
|
|
36
|
+
url = new URL(url, 'https://api.github.com/').href;
|
|
37
|
+
options.headers = {
|
|
38
|
+
Authorization: `Basic ${this.credentials.github}`,
|
|
39
|
+
'User-Agent': 'node-core-utils',
|
|
40
|
+
Accept: 'application/vnd.github+json',
|
|
41
|
+
...options.headers
|
|
42
|
+
};
|
|
35
43
|
}
|
|
36
44
|
return wrappedFetch(url, options);
|
|
37
45
|
}
|
|
@@ -65,14 +73,9 @@ export default class Request {
|
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
async createIssue(title, body, { owner, repo }) {
|
|
68
|
-
const url =
|
|
76
|
+
const url = `/repos/${owner}/${repo}/issues`;
|
|
69
77
|
const options = {
|
|
70
78
|
method: 'POST',
|
|
71
|
-
headers: {
|
|
72
|
-
Authorization: `Basic ${this.credentials.github}`,
|
|
73
|
-
'User-Agent': 'node-core-utils',
|
|
74
|
-
Accept: 'application/vnd.github+json'
|
|
75
|
-
},
|
|
76
79
|
body: JSON.stringify({
|
|
77
80
|
title,
|
|
78
81
|
body
|
|
@@ -82,15 +85,9 @@ export default class Request {
|
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
async commentIssue(fullUrl, comment) {
|
|
85
|
-
const commentUrl = fullUrl.replace('https://github.com/', '
|
|
86
|
-
'/comments';
|
|
88
|
+
const commentUrl = fullUrl.replace('https://github.com/', '/repos/') + '/comments';
|
|
87
89
|
const options = {
|
|
88
90
|
method: 'POST',
|
|
89
|
-
headers: {
|
|
90
|
-
Authorization: `Basic ${this.credentials.github}`,
|
|
91
|
-
'User-Agent': 'node-core-utils',
|
|
92
|
-
Accept: 'application/vnd.github+json'
|
|
93
|
-
},
|
|
94
91
|
body: JSON.stringify({
|
|
95
92
|
body: comment,
|
|
96
93
|
})
|
|
@@ -99,27 +96,14 @@ export default class Request {
|
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
async getPullRequest(fullUrl) {
|
|
102
|
-
const prUrl = fullUrl.replace('https://github.com/', '
|
|
103
|
-
|
|
104
|
-
method: 'GET',
|
|
105
|
-
headers: {
|
|
106
|
-
Authorization: `Basic ${this.credentials.github}`,
|
|
107
|
-
'User-Agent': 'node-core-utils',
|
|
108
|
-
Accept: 'application/vnd.github+json'
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
return this.json(prUrl, options);
|
|
99
|
+
const prUrl = fullUrl.replace('https://github.com/', '/repos/').replace('pull', 'pulls');
|
|
100
|
+
return this.json(prUrl);
|
|
112
101
|
}
|
|
113
102
|
|
|
114
103
|
async createPullRequest(title, body, { owner, repo, head, base }) {
|
|
115
|
-
const url =
|
|
104
|
+
const url = `/repos/${owner}/${repo}/pulls`;
|
|
116
105
|
const options = {
|
|
117
106
|
method: 'POST',
|
|
118
|
-
headers: {
|
|
119
|
-
Authorization: `Basic ${this.credentials.github}`,
|
|
120
|
-
'User-Agent': 'node-core-utils',
|
|
121
|
-
Accept: 'application/vnd.github+json'
|
|
122
|
-
},
|
|
123
107
|
body: JSON.stringify({
|
|
124
108
|
title,
|
|
125
109
|
body,
|
|
@@ -131,13 +115,10 @@ export default class Request {
|
|
|
131
115
|
}
|
|
132
116
|
|
|
133
117
|
async closePullRequest(id, { owner, repo }) {
|
|
134
|
-
const url =
|
|
118
|
+
const url = `/repos/${owner}/${repo}/pulls/${id}`;
|
|
135
119
|
const options = {
|
|
136
120
|
method: 'POST',
|
|
137
121
|
headers: {
|
|
138
|
-
Authorization: `Basic ${this.credentials.github}`,
|
|
139
|
-
'User-Agent': 'node-core-utils',
|
|
140
|
-
Accept: 'application/vnd.github+json',
|
|
141
122
|
'Content-Type': 'application/json'
|
|
142
123
|
},
|
|
143
124
|
body: JSON.stringify({
|
|
@@ -299,13 +280,11 @@ export default class Request {
|
|
|
299
280
|
throw new Error('The request has not been ' +
|
|
300
281
|
'authenticated with a GitHub token');
|
|
301
282
|
}
|
|
302
|
-
const url = '
|
|
283
|
+
const url = '/graphql';
|
|
303
284
|
const options = {
|
|
304
285
|
agent: this.proxyAgent,
|
|
305
286
|
method: 'POST',
|
|
306
287
|
headers: {
|
|
307
|
-
Authorization: `Basic ${githubCredentials}`,
|
|
308
|
-
'User-Agent': 'node-core-utils',
|
|
309
288
|
Accept: 'application/vnd.github.antiope-preview+json'
|
|
310
289
|
},
|
|
311
290
|
body: JSON.stringify({
|
package/lib/session.js
CHANGED
|
@@ -33,10 +33,11 @@ export default class Session {
|
|
|
33
33
|
const upstreamHref = runSync('git', [
|
|
34
34
|
'config', '--get',
|
|
35
35
|
`remote.${upstream}.url`]).trim();
|
|
36
|
-
if (!
|
|
36
|
+
if (!upstreamHref.endsWith(`${owner}/${repo}.git`) &&
|
|
37
|
+
!upstreamHref.endsWith(`${owner}/${repo}`)) {
|
|
37
38
|
cli.warn('Remote repository URL does not point to the expected ' +
|
|
38
39
|
`repository ${owner}/${repo}`);
|
|
39
|
-
|
|
40
|
+
this.crossRepoPR = true;
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
}
|
package/lib/voting_session.js
CHANGED
|
@@ -72,14 +72,10 @@ export default class VotingSession extends Session {
|
|
|
72
72
|
const subPath = `${prInfo.headRef.name}/vote.yml`;
|
|
73
73
|
this.cli.startSpinner('Downloading vote file from remote...');
|
|
74
74
|
const yamlString = await this.req.text(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
'User-Agent': 'node-core-utils',
|
|
80
|
-
Accept: 'application/vnd.github.raw'
|
|
81
|
-
}
|
|
82
|
-
});
|
|
75
|
+
`/repos/${this.owner}/${this.repo}/contents/${
|
|
76
|
+
encodeURIComponent(subPath)}?ref=${prInfo.commits.nodes[0].commit.oid}`,
|
|
77
|
+
{ agent: this.req.proxyAgent }
|
|
78
|
+
);
|
|
83
79
|
this.cli.stopSpinner('Download complete');
|
|
84
80
|
|
|
85
81
|
const { shares } = yaml.load(yamlString);
|
|
@@ -114,16 +110,15 @@ export default class VotingSession extends Session {
|
|
|
114
110
|
const body = 'I would like to close this vote, and for this effect, I\'m revealing my ' +
|
|
115
111
|
`key part:\n\n${'```'}\n${keyPart}\n${'```'}\n`;
|
|
116
112
|
if (this.postComment) {
|
|
117
|
-
const { message, html_url } = await this.req.json(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
});
|
|
113
|
+
const { message, html_url } = await this.req.json(
|
|
114
|
+
`/repos/${this.owner}/${this.repo}/issues/${this.prid}/comments`,
|
|
115
|
+
{
|
|
116
|
+
agent: this.req.proxyAgent,
|
|
117
|
+
method: 'POST',
|
|
118
|
+
headers: { Accept: 'application/vnd.github.antiope-preview+json' },
|
|
119
|
+
body: JSON.stringify({ body })
|
|
120
|
+
}
|
|
121
|
+
);
|
|
127
122
|
if (html_url) {
|
|
128
123
|
this.cli.log(`Comment posted at: ${html_url}`);
|
|
129
124
|
return;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node-core/utils",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@node-core/utils",
|
|
9
|
-
"version": "6.
|
|
9
|
+
"version": "6.3.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@inquirer/prompts": "^7.4.1",
|