@node-core/utils 5.2.1 → 5.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/git/security.js +26 -12
- package/lib/github/templates/security-post-release.md +18 -0
- package/lib/github/templates/security-pre-release.md +4 -4
- package/lib/prepare_release.js +2 -2
- package/lib/security-announcement.js +2 -2
- package/lib/security-release/security-release.js +5 -2
- package/lib/security_blog.js +186 -13
- package/package.json +10 -10
@@ -39,6 +39,10 @@ const securityOptions = {
|
|
39
39
|
'request-cve': {
|
40
40
|
describe: 'Request CVEs for a security release',
|
41
41
|
type: 'boolean'
|
42
|
+
},
|
43
|
+
'post-release': {
|
44
|
+
describe: 'Create the post-release announcement',
|
45
|
+
type: 'boolean'
|
42
46
|
}
|
43
47
|
};
|
44
48
|
|
@@ -49,7 +53,8 @@ export function builder(yargs) {
|
|
49
53
|
return yargs.options(securityOptions)
|
50
54
|
.example(
|
51
55
|
'git node security --start',
|
52
|
-
'Prepare a security release of Node.js'
|
56
|
+
'Prepare a security release of Node.js'
|
57
|
+
)
|
53
58
|
.example(
|
54
59
|
'git node security --sync',
|
55
60
|
'Synchronize an ongoing security release with HackerOne'
|
@@ -57,26 +62,25 @@ export function builder(yargs) {
|
|
57
62
|
.example(
|
58
63
|
'git node security --update-date=YYYY/MM/DD',
|
59
64
|
'Updates the target date of the security release'
|
60
|
-
)
|
61
|
-
.example(
|
65
|
+
).example(
|
62
66
|
'git node security --add-report=H1-ID',
|
63
67
|
'Fetches HackerOne report based on ID provided and adds it into vulnerabilities.json'
|
64
|
-
)
|
65
|
-
.example(
|
68
|
+
).example(
|
66
69
|
'git node security --remove-report=H1-ID',
|
67
70
|
'Removes the Hackerone report based on ID provided from vulnerabilities.json'
|
68
|
-
)
|
69
|
-
|
70
|
-
'git node security --pre-release' +
|
71
|
+
).example(
|
72
|
+
'git node security --pre-release',
|
71
73
|
'Create the pre-release announcement on the Nodejs.org repo'
|
72
74
|
).example(
|
73
|
-
'git node security --notify-pre-release'
|
75
|
+
'git node security --notify-pre-release',
|
74
76
|
'Notifies the community about the security release'
|
75
|
-
)
|
76
|
-
.example(
|
77
|
+
).example(
|
77
78
|
'git node security --request-cve',
|
78
79
|
'Request CVEs for a security release of Node.js based on' +
|
79
80
|
' the next-security-release/vulnerabilities.json'
|
81
|
+
).example(
|
82
|
+
'git node security --post-release',
|
83
|
+
'Create the post-release announcement on the Nodejs.org repo'
|
80
84
|
);
|
81
85
|
}
|
82
86
|
|
@@ -105,6 +109,9 @@ export function handler(argv) {
|
|
105
109
|
if (argv['request-cve']) {
|
106
110
|
return requestCVEs(argv);
|
107
111
|
}
|
112
|
+
if (argv['post-release']) {
|
113
|
+
return createPostRelease(argv);
|
114
|
+
}
|
108
115
|
yargsInstance.showHelp();
|
109
116
|
}
|
110
117
|
|
@@ -146,7 +153,14 @@ async function requestCVEs() {
|
|
146
153
|
return hackerOneCve.requestCVEs();
|
147
154
|
}
|
148
155
|
|
149
|
-
async function
|
156
|
+
async function createPostRelease() {
|
157
|
+
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
158
|
+
const cli = new CLI(logStream);
|
159
|
+
const blog = new SecurityBlog(cli);
|
160
|
+
return blog.createPostRelease();
|
161
|
+
}
|
162
|
+
|
163
|
+
async function startSecurityRelease() {
|
150
164
|
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
151
165
|
const cli = new CLI(logStream);
|
152
166
|
const release = new PrepareSecurityRelease(cli);
|
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
date: %ANNOUNCEMENT_DATE%
|
3
|
+
category: vulnerability
|
4
|
+
title: %RELEASE_DATE% Security Releases
|
5
|
+
slug: %SLUG%
|
6
|
+
layout: blog-post
|
7
|
+
author: %AUTHOR%
|
8
|
+
---
|
9
|
+
|
10
|
+
## Security releases available
|
11
|
+
|
12
|
+
Updates are now available for the %AFFECTED_VERSIONS% Node.js release lines for the
|
13
|
+
following issues.
|
14
|
+
%DEPENDENCY_UPDATES%
|
15
|
+
%REPORTS%
|
16
|
+
## Downloads and release details
|
17
|
+
|
18
|
+
%DOWNLOADS%
|
@@ -13,7 +13,7 @@ The Node.js project will release new versions of the %AFFECTED_VERSIONS%
|
|
13
13
|
releases lines on or shortly after, %RELEASE_DATE% in order to address:
|
14
14
|
|
15
15
|
%VULNERABILITIES%
|
16
|
-
|
16
|
+
|
17
17
|
## Impact
|
18
18
|
|
19
19
|
%IMPACT%
|
@@ -28,7 +28,7 @@ Releases will be available on, or shortly after, %RELEASE_DATE%.
|
|
28
28
|
|
29
29
|
## Contact and future updates
|
30
30
|
|
31
|
-
The current Node.js security policy can be found at https://nodejs.org/en/security
|
32
|
-
Please follow the process outlined in https://github.com/nodejs/node/blob/master/SECURITY.md if you wish to report a vulnerability in Node.js.
|
31
|
+
The current Node.js security policy can be found at <https://nodejs.org/en/security/>.
|
32
|
+
Please follow the process outlined in <https://github.com/nodejs/node/blob/master/SECURITY.md> if you wish to report a vulnerability in Node.js.
|
33
33
|
|
34
|
-
Subscribe to the low-volume announcement-only nodejs-sec mailing list at https://groups.google.com/forum/#!forum/nodejs-sec to stay up to date on security vulnerabilities and security-related releases of Node.js and the projects maintained in the nodejs GitHub organization.
|
34
|
+
Subscribe to the low-volume announcement-only nodejs-sec mailing list at <https://groups.google.com/forum/#!forum/nodejs-sec> to stay up to date on security vulnerabilities and security-related releases of Node.js and the projects maintained in the nodejs GitHub organization.
|
package/lib/prepare_release.js
CHANGED
@@ -2,7 +2,7 @@ import path from 'node:path';
|
|
2
2
|
import { promises as fs } from 'node:fs';
|
3
3
|
|
4
4
|
import semver from 'semver';
|
5
|
-
import
|
5
|
+
import { replaceInFile } from 'replace-in-file';
|
6
6
|
|
7
7
|
import { getMergedConfig } from './config.js';
|
8
8
|
import { runAsync, runSync } from './run.js';
|
@@ -427,7 +427,7 @@ export default class ReleasePreparation {
|
|
427
427
|
async updateREPLACEMEs() {
|
428
428
|
const { newVersion } = this;
|
429
429
|
|
430
|
-
await
|
430
|
+
await replaceInFile({
|
431
431
|
files: 'doc/api/*.md',
|
432
432
|
from: /REPLACEME/g,
|
433
433
|
to: `v${newVersion}`
|
@@ -53,7 +53,7 @@ export default class SecurityAnnouncement {
|
|
53
53
|
};
|
54
54
|
|
55
55
|
const { title, content } = this.createPreleaseAnnouncementIssue(releaseDate, 'build');
|
56
|
-
await
|
56
|
+
await createIssue(title, content, repository, { cli: this.cli, req: this.req });
|
57
57
|
}
|
58
58
|
|
59
59
|
createPreleaseAnnouncementIssue(releaseDate, team) {
|
@@ -71,6 +71,6 @@ export default class SecurityAnnouncement {
|
|
71
71
|
};
|
72
72
|
|
73
73
|
const { title, content } = this.createPreleaseAnnouncementIssue(releaseDate, 'docker');
|
74
|
-
await createIssue(title, content, repository, { cli: this.cli,
|
74
|
+
await createIssue(title, content, repository, { cli: this.cli, req: this.req });
|
75
75
|
}
|
76
76
|
}
|
@@ -20,9 +20,12 @@ export const PLACEHOLDERS = {
|
|
20
20
|
annoucementDate: '%ANNOUNCEMENT_DATE%',
|
21
21
|
slug: '%SLUG%',
|
22
22
|
affectedVersions: '%AFFECTED_VERSIONS%',
|
23
|
-
openSSLUpdate: '%OPENSSL_UPDATES%',
|
24
23
|
impact: '%IMPACT%',
|
25
|
-
vulnerabilities: '%VULNERABILITIES%'
|
24
|
+
vulnerabilities: '%VULNERABILITIES%',
|
25
|
+
reports: '%REPORTS%',
|
26
|
+
author: '%AUTHOR%',
|
27
|
+
dependencyUpdates: '%DEPENDENCY_UPDATES%',
|
28
|
+
downloads: '%DOWNLOADS%'
|
26
29
|
};
|
27
30
|
|
28
31
|
export function checkRemote(cli, repository) {
|
package/lib/security_blog.js
CHANGED
@@ -1,16 +1,24 @@
|
|
1
1
|
import fs from 'node:fs';
|
2
2
|
import path from 'node:path';
|
3
3
|
import _ from 'lodash';
|
4
|
+
import nv from '@pkgjs/nv';
|
4
5
|
import {
|
5
6
|
PLACEHOLDERS,
|
6
7
|
getVulnerabilitiesJSON,
|
7
8
|
checkoutOnSecurityReleaseBranch,
|
8
9
|
NEXT_SECURITY_RELEASE_REPOSITORY,
|
9
|
-
validateDate
|
10
|
+
validateDate,
|
11
|
+
commitAndPushVulnerabilitiesJSON,
|
12
|
+
NEXT_SECURITY_RELEASE_FOLDER
|
10
13
|
} from './security-release/security-release.js';
|
14
|
+
import auth from './auth.js';
|
15
|
+
import Request from './request.js';
|
16
|
+
|
17
|
+
const kChanged = Symbol('changed');
|
11
18
|
|
12
19
|
export default class SecurityBlog {
|
13
20
|
repository = NEXT_SECURITY_RELEASE_REPOSITORY;
|
21
|
+
req;
|
14
22
|
constructor(cli) {
|
15
23
|
this.cli = cli;
|
16
24
|
}
|
@@ -40,8 +48,7 @@ export default class SecurityBlog {
|
|
40
48
|
affectedVersions: this.getAffectedVersions(content),
|
41
49
|
vulnerabilities: this.getVulnerabilities(content),
|
42
50
|
slug: this.getSlug(releaseDate),
|
43
|
-
impact: this.getImpact(content)
|
44
|
-
openSSLUpdate: await this.promptOpenSSLUpdate(cli)
|
51
|
+
impact: this.getImpact(content)
|
45
52
|
};
|
46
53
|
const month = releaseDate.toLocaleString('en-US', { month: 'long' }).toLowerCase();
|
47
54
|
const year = releaseDate.getFullYear();
|
@@ -52,9 +59,94 @@ export default class SecurityBlog {
|
|
52
59
|
cli.ok(`Pre-release announcement file created at ${file}`);
|
53
60
|
}
|
54
61
|
|
55
|
-
|
56
|
-
|
57
|
-
|
62
|
+
async createPostRelease() {
|
63
|
+
const { cli } = this;
|
64
|
+
const credentials = await auth({
|
65
|
+
github: true,
|
66
|
+
h1: true
|
67
|
+
});
|
68
|
+
|
69
|
+
this.req = new Request(credentials);
|
70
|
+
|
71
|
+
// checkout on security release branch
|
72
|
+
checkoutOnSecurityReleaseBranch(cli, this.repository);
|
73
|
+
|
74
|
+
// read vulnerabilities JSON file
|
75
|
+
const content = getVulnerabilitiesJSON(cli);
|
76
|
+
if (!content.releaseDate) {
|
77
|
+
cli.error('Release date is not set in vulnerabilities.json,' +
|
78
|
+
' run `git node security --update-date=YYYY/MM/DD` to set the release date.');
|
79
|
+
process.exit(1);
|
80
|
+
}
|
81
|
+
|
82
|
+
validateDate(content.releaseDate);
|
83
|
+
const releaseDate = new Date(content.releaseDate);
|
84
|
+
const template = this.getSecurityPostReleaseTemplate();
|
85
|
+
const data = {
|
86
|
+
// TODO: read from pre-sec-release
|
87
|
+
annoucementDate: await this.getAnnouncementDate(cli),
|
88
|
+
releaseDate: this.formatReleaseDate(releaseDate),
|
89
|
+
affectedVersions: this.getAffectedVersions(content),
|
90
|
+
vulnerabilities: this.getVulnerabilities(content),
|
91
|
+
slug: this.getSlug(releaseDate),
|
92
|
+
author: await this.promptAuthor(cli),
|
93
|
+
dependencyUpdates: content.dependencies
|
94
|
+
};
|
95
|
+
const postReleaseContent = await this.buildPostRelease(template, data, content);
|
96
|
+
|
97
|
+
const pathPreRelease = await this.promptExistingPreRelease(cli);
|
98
|
+
// read the existing pre-release announcement
|
99
|
+
let preReleaseContent = fs.readFileSync(pathPreRelease, 'utf-8');
|
100
|
+
// cut the part before summary
|
101
|
+
const preSummary = preReleaseContent.indexOf('# Summary');
|
102
|
+
if (preSummary !== -1) {
|
103
|
+
preReleaseContent = preReleaseContent.substring(preSummary);
|
104
|
+
}
|
105
|
+
|
106
|
+
const updatedContent = postReleaseContent + preReleaseContent;
|
107
|
+
|
108
|
+
fs.writeFileSync(pathPreRelease, updatedContent);
|
109
|
+
cli.ok(`Post-release announcement file updated at ${pathPreRelease}`);
|
110
|
+
|
111
|
+
// if the vulnerabilities.json has been changed, update the file
|
112
|
+
if (!content[kChanged]) return;
|
113
|
+
this.updateVulnerabilitiesJSON(content);
|
114
|
+
}
|
115
|
+
|
116
|
+
updateVulnerabilitiesJSON(content) {
|
117
|
+
try {
|
118
|
+
this.cli.info('Updating vulnerabilities.json');
|
119
|
+
const vulnerabilitiesJSONPath = path.join(process.cwd(),
|
120
|
+
NEXT_SECURITY_RELEASE_FOLDER, 'vulnerabilities.json');
|
121
|
+
fs.writeFileSync(vulnerabilitiesJSONPath, JSON.stringify(content, null, 2));
|
122
|
+
const commitMessage = 'chore: updated vulnerabilities.json';
|
123
|
+
commitAndPushVulnerabilitiesJSON(vulnerabilitiesJSONPath,
|
124
|
+
commitMessage,
|
125
|
+
{ cli: this.cli, repository: this.repository });
|
126
|
+
} catch (error) {
|
127
|
+
this.cli.error('Error updating vulnerabilities.json');
|
128
|
+
this.cli.error(error);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
async promptExistingPreRelease(cli) {
|
133
|
+
const pathPreRelease = await cli.prompt(
|
134
|
+
'Please provide the path of the existing pre-release announcement:', {
|
135
|
+
questionType: 'input',
|
136
|
+
defaultAnswer: ''
|
137
|
+
});
|
138
|
+
|
139
|
+
if (!pathPreRelease || !fs.existsSync(path.resolve(pathPreRelease))) {
|
140
|
+
return this.promptExistingPreRelease(cli);
|
141
|
+
}
|
142
|
+
return pathPreRelease;
|
143
|
+
}
|
144
|
+
|
145
|
+
promptAuthor(cli) {
|
146
|
+
return cli.prompt('Who is the author of this security release? If multiple' +
|
147
|
+
' use & as separator', {
|
148
|
+
questionType: 'input',
|
149
|
+
defaultAnswer: PLACEHOLDERS.author
|
58
150
|
});
|
59
151
|
}
|
60
152
|
|
@@ -69,6 +161,23 @@ export default class SecurityBlog {
|
|
69
161
|
}
|
70
162
|
|
71
163
|
buildPreRelease(template, data) {
|
164
|
+
const {
|
165
|
+
annoucementDate,
|
166
|
+
releaseDate,
|
167
|
+
affectedVersions,
|
168
|
+
vulnerabilities,
|
169
|
+
slug,
|
170
|
+
impact
|
171
|
+
} = data;
|
172
|
+
return template.replaceAll(PLACEHOLDERS.annoucementDate, annoucementDate)
|
173
|
+
.replaceAll(PLACEHOLDERS.slug, slug)
|
174
|
+
.replaceAll(PLACEHOLDERS.affectedVersions, affectedVersions)
|
175
|
+
.replaceAll(PLACEHOLDERS.vulnerabilities, vulnerabilities)
|
176
|
+
.replaceAll(PLACEHOLDERS.releaseDate, releaseDate)
|
177
|
+
.replaceAll(PLACEHOLDERS.impact, impact);
|
178
|
+
}
|
179
|
+
|
180
|
+
async buildPostRelease(template, data, content) {
|
72
181
|
const {
|
73
182
|
annoucementDate,
|
74
183
|
releaseDate,
|
@@ -76,7 +185,8 @@ export default class SecurityBlog {
|
|
76
185
|
vulnerabilities,
|
77
186
|
slug,
|
78
187
|
impact,
|
79
|
-
|
188
|
+
author,
|
189
|
+
dependencyUpdates
|
80
190
|
} = data;
|
81
191
|
return template.replaceAll(PLACEHOLDERS.annoucementDate, annoucementDate)
|
82
192
|
.replaceAll(PLACEHOLDERS.slug, slug)
|
@@ -84,15 +194,68 @@ export default class SecurityBlog {
|
|
84
194
|
.replaceAll(PLACEHOLDERS.vulnerabilities, vulnerabilities)
|
85
195
|
.replaceAll(PLACEHOLDERS.releaseDate, releaseDate)
|
86
196
|
.replaceAll(PLACEHOLDERS.impact, impact)
|
87
|
-
.replaceAll(PLACEHOLDERS.
|
197
|
+
.replaceAll(PLACEHOLDERS.author, author)
|
198
|
+
.replaceAll(PLACEHOLDERS.reports, await this.getReportsTemplate(content))
|
199
|
+
.replaceAll(PLACEHOLDERS.dependencyUpdates,
|
200
|
+
this.getDependencyUpdatesTemplate(dependencyUpdates))
|
201
|
+
.replaceAll(PLACEHOLDERS.downloads, await this.getDownloadsTemplate(affectedVersions));
|
202
|
+
}
|
203
|
+
|
204
|
+
async getReportsTemplate(content) {
|
205
|
+
const reports = content.reports;
|
206
|
+
let template = '';
|
207
|
+
for (const report of reports) {
|
208
|
+
const cveId = report.cveIds?.join(', ');
|
209
|
+
if (!cveId) {
|
210
|
+
this.cli.error(`CVE ID for vulnerability ${report.link} ${report.title} not found`);
|
211
|
+
process.exit(1);
|
212
|
+
}
|
213
|
+
template += `## ${report.title} (${cveId}) - (${report.severity.rating})\n\n`;
|
214
|
+
if (!report.summary) {
|
215
|
+
this.cli.error(`Summary missing for vulnerability ${report.link} ` +
|
216
|
+
`${report.title}. Please create it before continuing.`);
|
217
|
+
process.exit(1);
|
218
|
+
}
|
219
|
+
|
220
|
+
template += `${report.summary}\n\n`;
|
221
|
+
const releaseLines = report.affectedVersions.join(', ');
|
222
|
+
template += `Impact:\n\n- This vulnerability affects all users\
|
223
|
+
in active release lines: ${releaseLines}\n\n`;
|
224
|
+
if (!report.patchAuthors) {
|
225
|
+
this.cli.error(`Missing patch author for vulnerability ${report.link} ${report.title}`);
|
226
|
+
process.exit(1);
|
227
|
+
}
|
228
|
+
template += `Thank you, to ${report.reporter} for reporting this vulnerability\
|
229
|
+
and thank you ${report.patchAuthors.join(' and ')} for fixing it.\n\n`;
|
230
|
+
}
|
231
|
+
return template;
|
88
232
|
}
|
89
233
|
|
90
|
-
|
91
|
-
if (
|
92
|
-
|
93
|
-
|
234
|
+
getDependencyUpdatesTemplate(dependencyUpdates) {
|
235
|
+
if (!dependencyUpdates) return '';
|
236
|
+
let template = 'This security release includes the following dependency' +
|
237
|
+
' updates to address public vulnerabilities:\n\n';
|
238
|
+
for (const dependencyUpdate of Object.values(dependencyUpdates)) {
|
239
|
+
for (const dependency of dependencyUpdate) {
|
240
|
+
const title = dependency.title.substring(dependency.title.indexOf(':') + ':'.length).trim();
|
241
|
+
template += `- ${title}\
|
242
|
+
on ${dependency.affectedVersions.join(', ')}\n`;
|
243
|
+
}
|
94
244
|
}
|
95
|
-
return
|
245
|
+
return template;
|
246
|
+
}
|
247
|
+
|
248
|
+
async getDownloadsTemplate(affectedVersions) {
|
249
|
+
let template = '';
|
250
|
+
const versionsToBeReleased = (await nv('supported')).filter(
|
251
|
+
(v) => affectedVersions.split(', ').includes(`${v.major}.x`)
|
252
|
+
);
|
253
|
+
for (const version of versionsToBeReleased) {
|
254
|
+
const v = `v${version.major}.${version.minor}.${Number(version.patch) + 1}`;
|
255
|
+
template += `- [Node.js ${v}](/blog/release/${v}/)\n`;
|
256
|
+
}
|
257
|
+
|
258
|
+
return template;
|
96
259
|
}
|
97
260
|
|
98
261
|
getSlug(releaseDate) {
|
@@ -179,4 +342,14 @@ export default class SecurityBlog {
|
|
179
342
|
'utf-8'
|
180
343
|
);
|
181
344
|
}
|
345
|
+
|
346
|
+
getSecurityPostReleaseTemplate() {
|
347
|
+
return fs.readFileSync(
|
348
|
+
new URL(
|
349
|
+
'./github/templates/security-post-release.md',
|
350
|
+
import.meta.url
|
351
|
+
),
|
352
|
+
'utf-8'
|
353
|
+
);
|
354
|
+
}
|
182
355
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@node-core/utils",
|
3
|
-
"version": "5.
|
3
|
+
"version": "5.3.1",
|
4
4
|
"description": "Utilities for Node.js core collaborators",
|
5
5
|
"type": "module",
|
6
6
|
"engines": {
|
@@ -34,8 +34,8 @@
|
|
34
34
|
],
|
35
35
|
"license": "MIT",
|
36
36
|
"dependencies": {
|
37
|
-
"@listr2/prompt-adapter-enquirer": "^2.0.
|
38
|
-
"@node-core/caritat": "^1.
|
37
|
+
"@listr2/prompt-adapter-enquirer": "^2.0.10",
|
38
|
+
"@node-core/caritat": "^1.6.0",
|
39
39
|
"@pkgjs/nv": "^0.2.2",
|
40
40
|
"branch-diff": "^3.0.4",
|
41
41
|
"chalk": "^5.3.0",
|
@@ -44,26 +44,26 @@
|
|
44
44
|
"clipboardy": "^4.0.0",
|
45
45
|
"core-validate-commit": "^4.0.0",
|
46
46
|
"figures": "^6.1.0",
|
47
|
-
"ghauth": "^6.0.
|
48
|
-
"inquirer": "^9.2
|
47
|
+
"ghauth": "^6.0.5",
|
48
|
+
"inquirer": "^9.3.2",
|
49
49
|
"js-yaml": "^4.1.0",
|
50
|
-
"listr2": "^8.2.
|
50
|
+
"listr2": "^8.2.3",
|
51
51
|
"lodash": "^4.17.21",
|
52
52
|
"log-symbols": "^6.0.0",
|
53
53
|
"ora": "^8.0.1",
|
54
|
-
"replace-in-file": "^
|
55
|
-
"undici": "^6.
|
54
|
+
"replace-in-file": "^8.0.2",
|
55
|
+
"undici": "^6.19.2",
|
56
56
|
"which": "^4.0.0",
|
57
57
|
"yargs": "^17.7.2"
|
58
58
|
},
|
59
59
|
"devDependencies": {
|
60
60
|
"@reporters/github": "^1.7.0",
|
61
|
-
"c8": "^
|
61
|
+
"c8": "^10.1.2",
|
62
62
|
"eslint": "^8.57.0",
|
63
63
|
"eslint-config-standard": "^17.1.0",
|
64
64
|
"eslint-plugin-import": "^2.29.1",
|
65
65
|
"eslint-plugin-n": "^16.6.2",
|
66
|
-
"eslint-plugin-promise": "^6.
|
66
|
+
"eslint-plugin-promise": "^6.4.0",
|
67
67
|
"sinon": "^18.0.0"
|
68
68
|
}
|
69
69
|
}
|