@scalvert/bin-tester 0.0.4
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 +100 -0
- package/dist/index.cjs +126 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +92 -0
- package/package.json +90 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @scalvert/bin-tester
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://badge.fury.io/js/%40scalvert%2Fbin-tester)
|
|
5
|
+
[](https://github.com/scalvert/bin-tester/blob/master/package.json)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
[](#badge)
|
|
9
|
+
|
|
10
|
+
> Provides a test harness for node CLIs that allow you to run tests against a real project.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
npm add @scalvert/bin-tester --save-dev
|
|
16
|
+
|
|
17
|
+
# or
|
|
18
|
+
|
|
19
|
+
yarn add @scalvert/bin-tester --dev
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
`@scalvert/bin-tester` uses two libraries to provide the test harness:
|
|
25
|
+
|
|
26
|
+
- [`fixturify-project`](https://github.com/stefanpenner/node-fixturify-project): Allows you to dynamically create test fixtures using real directories and files in a tmp directory
|
|
27
|
+
- [`execa`](https://github.com/sindresorhus/execa): A better replacement for `child_process.exec`
|
|
28
|
+
|
|
29
|
+
It combines the above and provides an API for running a binary with a set of arguments against a real project structure, thus mimicking testing a real environment.
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { createBinTester } from '@scalvert/bin-tester';
|
|
33
|
+
|
|
34
|
+
describe('Some tests', () => {
|
|
35
|
+
let project;
|
|
36
|
+
let { setupProject, teardownProject, runBin } = createBinTester({
|
|
37
|
+
binPath: 'node_modules/.bin/someBin',
|
|
38
|
+
staticArgs: ['--some-arg'], // pass some args to the bin that will be used for each invocation
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
project = await setupProject();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
await teardownProject();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Run the bin and do something with the result
|
|
50
|
+
test('a test', () => {
|
|
51
|
+
const result = await runBin();
|
|
52
|
+
|
|
53
|
+
expect(result.stdout).toBe('Did some stuff');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Write a file with contents to the tmp directory
|
|
57
|
+
test('another test', () => {
|
|
58
|
+
project.write({
|
|
59
|
+
'some/file.txt': 'some content',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const result = await runBin({
|
|
63
|
+
args: ['--path', 'some/file.txt'], // pass some args to the bin that will be used for only this invocation
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(result.stdout).toBe('Read "some/file.txt"');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
class BinTesterProject extends Project {
|
|
75
|
+
private _dirChanged;
|
|
76
|
+
constructor(name: string, version?: string, cb?: (project: Project) => void);
|
|
77
|
+
gitInit(): execa.ExecaChildProcess<string>;
|
|
78
|
+
chdir(): void;
|
|
79
|
+
dispose(): void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface BinTesterOptions {
|
|
83
|
+
binPath: string;
|
|
84
|
+
projectConstructor?: any;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface RunOptions {
|
|
88
|
+
args?: string[];
|
|
89
|
+
execaOptions?: execa.Options<string>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface createBinTesterResult<TProject extends BinTesterProject> {
|
|
93
|
+
runBin: (runOptions?: RunOptions) => execa.ExecaChildProcess<string>;
|
|
94
|
+
setupProject: () => Promise<TProject>;
|
|
95
|
+
setupTmpDir: () => Promise<string>;
|
|
96
|
+
teardownProject: () => void;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createBinTester<TProject extends BinTesterProject>(options: BinTesterOptions): createBinTesterResult<TProject>;
|
|
100
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
9
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
10
|
+
var __spreadValues = (a, b) => {
|
|
11
|
+
for (var prop in b || (b = {}))
|
|
12
|
+
if (__hasOwnProp.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
if (__getOwnPropSymbols)
|
|
15
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
16
|
+
if (__propIsEnum.call(b, prop))
|
|
17
|
+
__defNormalProp(a, prop, b[prop]);
|
|
18
|
+
}
|
|
19
|
+
return a;
|
|
20
|
+
};
|
|
21
|
+
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
|
|
22
|
+
var __export = (target, all) => {
|
|
23
|
+
for (var name in all)
|
|
24
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
25
|
+
};
|
|
26
|
+
var __reExport = (target, module2, copyDefault, desc) => {
|
|
27
|
+
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
|
|
28
|
+
for (let key of __getOwnPropNames(module2))
|
|
29
|
+
if (!__hasOwnProp.call(target, key) && (copyDefault || key !== "default"))
|
|
30
|
+
__defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
|
|
31
|
+
}
|
|
32
|
+
return target;
|
|
33
|
+
};
|
|
34
|
+
var __toESM = (module2, isNodeMode) => {
|
|
35
|
+
return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", !isNodeMode && module2 && module2.__esModule ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
|
|
36
|
+
};
|
|
37
|
+
var __toCommonJS = /* @__PURE__ */ ((cache) => {
|
|
38
|
+
return (module2, temp) => {
|
|
39
|
+
return cache && cache.get(module2) || (temp = __reExport(__markAsModule({}), module2, 1), cache && cache.set(module2, temp), temp);
|
|
40
|
+
};
|
|
41
|
+
})(typeof WeakMap !== "undefined" ? /* @__PURE__ */ new WeakMap() : 0);
|
|
42
|
+
|
|
43
|
+
// src/index.ts
|
|
44
|
+
var src_exports = {};
|
|
45
|
+
__export(src_exports, {
|
|
46
|
+
BinTesterProject: () => BinTesterProject,
|
|
47
|
+
createBinTester: () => createBinTester
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// src/create-bin-tester.ts
|
|
51
|
+
var import_execa2 = __toESM(require("execa"), 1);
|
|
52
|
+
|
|
53
|
+
// src/project.ts
|
|
54
|
+
var import_execa = __toESM(require("execa"), 1);
|
|
55
|
+
var import_fixturify_project = require("fixturify-project");
|
|
56
|
+
var ROOT = process.cwd();
|
|
57
|
+
var BinTesterProject = class extends import_fixturify_project.Project {
|
|
58
|
+
constructor(name = "fake-project", version, cb) {
|
|
59
|
+
super(name, version, cb);
|
|
60
|
+
this._dirChanged = false;
|
|
61
|
+
this.pkg = Object.assign({}, this.pkg, {
|
|
62
|
+
license: "MIT",
|
|
63
|
+
description: "Fake project",
|
|
64
|
+
repository: "http://fakerepo.com"
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
gitInit() {
|
|
68
|
+
return (0, import_execa.default)(`git init -q ${this.baseDir}`);
|
|
69
|
+
}
|
|
70
|
+
chdir() {
|
|
71
|
+
this._dirChanged = true;
|
|
72
|
+
process.chdir(this.baseDir);
|
|
73
|
+
}
|
|
74
|
+
dispose() {
|
|
75
|
+
if (this._dirChanged) {
|
|
76
|
+
process.chdir(ROOT);
|
|
77
|
+
}
|
|
78
|
+
return super.dispose();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/create-bin-tester.ts
|
|
83
|
+
var DEFAULT_BIN_TESTER_OPTIONS = {
|
|
84
|
+
staticArgs: [],
|
|
85
|
+
projectConstructor: BinTesterProject
|
|
86
|
+
};
|
|
87
|
+
var DEFAULT_RUN_OPTIONS = {
|
|
88
|
+
args: [],
|
|
89
|
+
execaOptions: {}
|
|
90
|
+
};
|
|
91
|
+
function createBinTester(options) {
|
|
92
|
+
let project;
|
|
93
|
+
const mergedOptions = __spreadValues(__spreadValues({}, DEFAULT_BIN_TESTER_OPTIONS), options);
|
|
94
|
+
function runBin(runOptions = DEFAULT_RUN_OPTIONS) {
|
|
95
|
+
return (0, import_execa2.default)(process.execPath, [mergedOptions.binPath, ...mergedOptions.staticArgs, ...runOptions.args], __spreadValues({
|
|
96
|
+
reject: false,
|
|
97
|
+
cwd: project.baseDir
|
|
98
|
+
}, runOptions.execaOptions));
|
|
99
|
+
}
|
|
100
|
+
async function setupProject() {
|
|
101
|
+
project = new mergedOptions.projectConstructor();
|
|
102
|
+
await project.write();
|
|
103
|
+
return project;
|
|
104
|
+
}
|
|
105
|
+
async function setupTmpDir() {
|
|
106
|
+
if (typeof project === "undefined") {
|
|
107
|
+
await setupProject();
|
|
108
|
+
}
|
|
109
|
+
return project.baseDir;
|
|
110
|
+
}
|
|
111
|
+
function teardownProject() {
|
|
112
|
+
project.dispose();
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
runBin,
|
|
116
|
+
setupProject,
|
|
117
|
+
teardownProject,
|
|
118
|
+
setupTmpDir
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
module.exports = __toCommonJS(src_exports);
|
|
122
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
123
|
+
0 && (module.exports = {
|
|
124
|
+
BinTesterProject,
|
|
125
|
+
createBinTester
|
|
126
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import execa from 'execa';
|
|
2
|
+
import { Project } from 'fixturify-project';
|
|
3
|
+
|
|
4
|
+
declare class BinTesterProject extends Project {
|
|
5
|
+
private _dirChanged;
|
|
6
|
+
constructor(name?: string, version?: string, cb?: (project: Project) => void);
|
|
7
|
+
gitInit(): execa.ExecaChildProcess<string>;
|
|
8
|
+
chdir(): void;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface BinTesterOptions {
|
|
13
|
+
/**
|
|
14
|
+
* The absolute path to the bin to invoke
|
|
15
|
+
*/
|
|
16
|
+
binPath: string;
|
|
17
|
+
/**
|
|
18
|
+
* An array of static arguments that will be used every time when running the bin
|
|
19
|
+
*/
|
|
20
|
+
staticArgs?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* An optional class to use to create the project
|
|
23
|
+
*/
|
|
24
|
+
projectConstructor?: any;
|
|
25
|
+
}
|
|
26
|
+
interface RunOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Arguments to provide to the bin script.
|
|
29
|
+
*/
|
|
30
|
+
args: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Options to provide to execa. @see https://github.com/sindresorhus/execa#options
|
|
33
|
+
*/
|
|
34
|
+
execaOptions?: execa.Options<string>;
|
|
35
|
+
}
|
|
36
|
+
interface CreateBinTesterResult<TProject extends BinTesterProject> {
|
|
37
|
+
runBin: (runOptions?: RunOptions) => execa.ExecaChildProcess<string>;
|
|
38
|
+
setupProject: () => Promise<TProject>;
|
|
39
|
+
setupTmpDir: () => Promise<string>;
|
|
40
|
+
teardownProject: () => void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates the bin tester API functions to use within tests.
|
|
44
|
+
*
|
|
45
|
+
* @param options - An object of bin tester options
|
|
46
|
+
* @returns - A project instance.
|
|
47
|
+
*/
|
|
48
|
+
declare function createBinTester<TProject extends BinTesterProject>(options: BinTesterOptions): CreateBinTesterResult<TProject>;
|
|
49
|
+
|
|
50
|
+
export { BinTesterProject, createBinTester };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/create-bin-tester.ts
|
|
19
|
+
import execa2 from "execa";
|
|
20
|
+
|
|
21
|
+
// src/project.ts
|
|
22
|
+
import execa from "execa";
|
|
23
|
+
import { Project } from "fixturify-project";
|
|
24
|
+
var ROOT = process.cwd();
|
|
25
|
+
var BinTesterProject = class extends Project {
|
|
26
|
+
constructor(name = "fake-project", version, cb) {
|
|
27
|
+
super(name, version, cb);
|
|
28
|
+
this._dirChanged = false;
|
|
29
|
+
this.pkg = Object.assign({}, this.pkg, {
|
|
30
|
+
license: "MIT",
|
|
31
|
+
description: "Fake project",
|
|
32
|
+
repository: "http://fakerepo.com"
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
gitInit() {
|
|
36
|
+
return execa(`git init -q ${this.baseDir}`);
|
|
37
|
+
}
|
|
38
|
+
chdir() {
|
|
39
|
+
this._dirChanged = true;
|
|
40
|
+
process.chdir(this.baseDir);
|
|
41
|
+
}
|
|
42
|
+
dispose() {
|
|
43
|
+
if (this._dirChanged) {
|
|
44
|
+
process.chdir(ROOT);
|
|
45
|
+
}
|
|
46
|
+
return super.dispose();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/create-bin-tester.ts
|
|
51
|
+
var DEFAULT_BIN_TESTER_OPTIONS = {
|
|
52
|
+
staticArgs: [],
|
|
53
|
+
projectConstructor: BinTesterProject
|
|
54
|
+
};
|
|
55
|
+
var DEFAULT_RUN_OPTIONS = {
|
|
56
|
+
args: [],
|
|
57
|
+
execaOptions: {}
|
|
58
|
+
};
|
|
59
|
+
function createBinTester(options) {
|
|
60
|
+
let project;
|
|
61
|
+
const mergedOptions = __spreadValues(__spreadValues({}, DEFAULT_BIN_TESTER_OPTIONS), options);
|
|
62
|
+
function runBin(runOptions = DEFAULT_RUN_OPTIONS) {
|
|
63
|
+
return execa2(process.execPath, [mergedOptions.binPath, ...mergedOptions.staticArgs, ...runOptions.args], __spreadValues({
|
|
64
|
+
reject: false,
|
|
65
|
+
cwd: project.baseDir
|
|
66
|
+
}, runOptions.execaOptions));
|
|
67
|
+
}
|
|
68
|
+
async function setupProject() {
|
|
69
|
+
project = new mergedOptions.projectConstructor();
|
|
70
|
+
await project.write();
|
|
71
|
+
return project;
|
|
72
|
+
}
|
|
73
|
+
async function setupTmpDir() {
|
|
74
|
+
if (typeof project === "undefined") {
|
|
75
|
+
await setupProject();
|
|
76
|
+
}
|
|
77
|
+
return project.baseDir;
|
|
78
|
+
}
|
|
79
|
+
function teardownProject() {
|
|
80
|
+
project.dispose();
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
runBin,
|
|
84
|
+
setupProject,
|
|
85
|
+
teardownProject,
|
|
86
|
+
setupTmpDir
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
BinTesterProject,
|
|
91
|
+
createBinTester
|
|
92
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scalvert/bin-tester",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "A test harness to invoke a CLI in a tmp directory",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"fake",
|
|
8
|
+
"project",
|
|
9
|
+
"test",
|
|
10
|
+
"harness"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/scalvert/bin-tester.git"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Steve Calvert <steve.calvert@gmail.com>",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"require": "./dist/index.cjs",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"main": "./dist/index.cjs",
|
|
27
|
+
"module": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
31
|
+
"docs:generate": "npm run build && readme-api-generator dist/index.js",
|
|
32
|
+
"lint": "eslint .",
|
|
33
|
+
"prepublishOnly": "npm run build",
|
|
34
|
+
"test": "npm run lint && vitest run",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"watch": "npm run build -- --watch src"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"execa": "^5.1.1",
|
|
43
|
+
"fixturify-project": "^4.1.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@scalvert/readme-api-generator": "^0.1.0",
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "^5.14.0",
|
|
48
|
+
"@typescript-eslint/parser": "^5.14.0",
|
|
49
|
+
"eslint": "^8.10.0",
|
|
50
|
+
"eslint-config-prettier": "^8.5.0",
|
|
51
|
+
"eslint-plugin-node": "^11.1.0",
|
|
52
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
53
|
+
"eslint-plugin-tsdoc": "^0.2.14",
|
|
54
|
+
"eslint-plugin-unicorn": "^41.0.0",
|
|
55
|
+
"prettier": "^2.5.1",
|
|
56
|
+
"release-it": "^14.2.1",
|
|
57
|
+
"release-it-lerna-changelog": "^3.1.0",
|
|
58
|
+
"tsup": "^5.12.0",
|
|
59
|
+
"type-fest": "^2.12.0",
|
|
60
|
+
"typescript": "^4.6.2",
|
|
61
|
+
"vite": "^2.8.6",
|
|
62
|
+
"vitest": "^0.6.0"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=14"
|
|
66
|
+
},
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"registry": "https://registry.npmjs.org",
|
|
69
|
+
"access": "public"
|
|
70
|
+
},
|
|
71
|
+
"release-it": {
|
|
72
|
+
"plugins": {
|
|
73
|
+
"release-it-lerna-changelog": {
|
|
74
|
+
"infile": "CHANGELOG.md",
|
|
75
|
+
"launchEditor": true
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"git": {
|
|
79
|
+
"tagName": "v${version}"
|
|
80
|
+
},
|
|
81
|
+
"github": {
|
|
82
|
+
"release": true,
|
|
83
|
+
"tokenRef": "GITHUB_AUTH"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"volta": {
|
|
87
|
+
"node": "14.19.0",
|
|
88
|
+
"npm": "8.5.3"
|
|
89
|
+
}
|
|
90
|
+
}
|