@skills17/task-config
Advanced tools
Comparing version 2.2.1 to 3.0.0
@@ -7,12 +7,12 @@ import { TestRun } from '@skills17/test-result'; | ||
private id?; | ||
private type?; | ||
private source; | ||
private tests; | ||
private localHistory; | ||
private showPoints; | ||
private displayPoints; | ||
private serve; | ||
private points; | ||
private groups; | ||
private metadata; | ||
/** | ||
* Load groups from the config.json file and create instances of @skills17/test-result | ||
* Load groups from the config.yaml file and create instances of @skills17/test-result | ||
* | ||
@@ -33,3 +33,2 @@ * @param groups Groups | ||
getId(): string | undefined; | ||
getType(): string | undefined; | ||
getSource(): string[]; | ||
@@ -40,4 +39,5 @@ getTests(): string[]; | ||
getGroups(): RawGroup[]; | ||
getMetadata(): Record<string, unknown>; | ||
isLocalHistoryEnabled(): boolean; | ||
arePointsShown(): boolean; | ||
arePointsDisplayed(): boolean; | ||
} |
@@ -12,5 +12,5 @@ "use strict"; | ||
source = ['./src/**']; | ||
tests = ['./tests/**/*.@(spec|test).@(js|ts)']; | ||
tests = ['./tests/**/*.spec.*', './tests/**/*.test.*']; | ||
localHistory = false; | ||
showPoints = true; | ||
displayPoints = true; | ||
serve = { | ||
@@ -29,4 +29,5 @@ enabled: false, | ||
groups = []; | ||
metadata = {}; | ||
/** | ||
* Load groups from the config.json file and create instances of @skills17/test-result | ||
* Load groups from the config.yaml file and create instances of @skills17/test-result | ||
* | ||
@@ -39,3 +40,3 @@ * @param groups Groups | ||
if (!groupConfig.match) { | ||
throw new Error(`config.json validation error: group #${groupIndex} does not contain a 'match' property`); | ||
throw new Error(`config.yaml validation error: group #${groupIndex} does not contain a 'match' property`); | ||
} | ||
@@ -47,3 +48,3 @@ | ||
if (typeof groupConfig.maxPoints !== 'undefined' && strategy !== _testResult.Strategy.Deduct) { | ||
throw new Error(`config.json validation error: property 'maxPoints' can only be set for strategy 'deduct'. Found in group #${groupIndex} (${groupConfig.match})`); | ||
throw new Error(`config.yaml validation error: property 'maxPoints' can only be set for strategy 'deduct'. Found in group #${groupIndex} (${groupConfig.match})`); | ||
} // create group instance | ||
@@ -57,3 +58,3 @@ | ||
if (!testConfig.match) { | ||
throw new Error(`config.json validation error: test #${testIndex} in group #${groupIndex} (${groupConfig.match}) does not contain a 'match' property`); | ||
throw new Error(`config.yaml validation error: test #${testIndex} in group #${groupIndex} (${groupConfig.match}) does not contain a 'match' property`); | ||
} | ||
@@ -79,7 +80,6 @@ | ||
this.id = config.id; | ||
this.type = config.type; | ||
this.source = config.source ?? this.source; | ||
this.tests = config.tests ?? this.tests; | ||
this.localHistory = config.localHistory ?? this.localHistory; | ||
this.showPoints = config.showPoints ?? this.showPoints; | ||
this.displayPoints = config.displayPoints ?? this.displayPoints; | ||
this.serve = { ...this.serve, | ||
@@ -92,2 +92,3 @@ ...config.serve | ||
this.groups = config.groups ?? this.groups; | ||
this.metadata = config.metadata ?? this.metadata; | ||
} | ||
@@ -109,6 +110,2 @@ /** | ||
getType() { | ||
return this.type; | ||
} | ||
getSource() { | ||
@@ -134,2 +131,6 @@ return this.source; | ||
getMetadata() { | ||
return this.metadata; | ||
} | ||
isLocalHistoryEnabled() { | ||
@@ -139,4 +140,4 @@ return this.localHistory; | ||
arePointsShown() { | ||
return this.showPoints; | ||
arePointsDisplayed() { | ||
return this.displayPoints; | ||
} | ||
@@ -143,0 +144,0 @@ |
@@ -6,8 +6,2 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function () { | ||
return _Config.default; | ||
} | ||
}); | ||
Object.defineProperty(exports, "Points", { | ||
@@ -37,2 +31,8 @@ enumerable: true, | ||
}); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function () { | ||
return _Config.default; | ||
} | ||
}); | ||
@@ -39,0 +39,0 @@ var _Config = _interopRequireDefault(require("./Config")); |
@@ -6,8 +6,2 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function () { | ||
return _NodeConfig.default; | ||
} | ||
}); | ||
Object.defineProperty(exports, "Points", { | ||
@@ -37,2 +31,8 @@ enumerable: true, | ||
}); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function () { | ||
return _NodeConfig.default; | ||
} | ||
}); | ||
@@ -39,0 +39,0 @@ var _NodeConfig = _interopRequireDefault(require("./NodeConfig")); |
@@ -5,3 +5,3 @@ import Config from './Config'; | ||
/** | ||
* Detect the path of the config.json file. | ||
* Detect the path of the config.yaml file. | ||
* First, try it from the current file. | ||
@@ -12,3 +12,3 @@ * If that can't be found which is the case when installed using a symlink, try it from the cwd. | ||
/** | ||
* Detect the path of the config.json file synchronously. | ||
* Detect the path of the config.yaml file synchronously. | ||
* First, try it from the current file. | ||
@@ -21,3 +21,3 @@ * If that can't be found which is the case when installed using a symlink, try it from the cwd. | ||
* | ||
* @param configPath Path of the config.json file, will be determined automatically if omitted | ||
* @param configPath Path of the config.yaml file, will be determined automatically if omitted | ||
*/ | ||
@@ -28,6 +28,12 @@ loadFromFile(configPath?: string): Promise<void>; | ||
* | ||
* @param configPath Path of the config.json file, will be determined automatically if omitted | ||
* @param configPath Path of the config.yaml file, will be determined automatically if omitted | ||
*/ | ||
loadFromFileSync(configPath?: string): void; | ||
getProjectRoot(): string; | ||
/** | ||
* Validate the config against the schema. | ||
* | ||
* @param config Config object | ||
*/ | ||
private validateSchema; | ||
} |
@@ -14,7 +14,11 @@ "use strict"; | ||
var _jsYaml = _interopRequireDefault(require("js-yaml")); | ||
var _ajv = _interopRequireDefault(require("ajv")); | ||
var _Config = _interopRequireDefault(require("./Config")); | ||
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } | ||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } | ||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } | ||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } | ||
@@ -25,3 +29,3 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* Detect the path of the config.json file. | ||
* Detect the path of the config.yaml file. | ||
* First, try it from the current file. | ||
@@ -31,3 +35,3 @@ * If that can't be found which is the case when installed using a symlink, try it from the cwd. | ||
static async detectPath() { | ||
let configPath = await (0, _findUp.default)('config.json', { | ||
let configPath = await (0, _findUp.default)('config.yaml', { | ||
cwd: __dirname | ||
@@ -37,3 +41,3 @@ }); | ||
if (!configPath) { | ||
configPath = await (0, _findUp.default)('config.json', { | ||
configPath = await (0, _findUp.default)('config.yaml', { | ||
cwd: process.cwd() | ||
@@ -50,3 +54,3 @@ }); | ||
/** | ||
* Detect the path of the config.json file synchronously. | ||
* Detect the path of the config.yaml file synchronously. | ||
* First, try it from the current file. | ||
@@ -58,3 +62,3 @@ * If that can't be found which is the case when installed using a symlink, try it from the cwd. | ||
static detectPathSync() { | ||
let configPath = _findUp.default.sync('config.json', { | ||
let configPath = _findUp.default.sync('config.yaml', { | ||
cwd: __dirname | ||
@@ -64,3 +68,3 @@ }); | ||
if (!configPath) { | ||
configPath = _findUp.default.sync('config.json', { | ||
configPath = _findUp.default.sync('config.yaml', { | ||
cwd: process.cwd() | ||
@@ -79,3 +83,3 @@ }); | ||
* | ||
* @param configPath Path of the config.json file, will be determined automatically if omitted | ||
* @param configPath Path of the config.yaml file, will be determined automatically if omitted | ||
*/ | ||
@@ -85,6 +89,10 @@ | ||
async loadFromFile(configPath) { | ||
this.configPath = configPath ?? (await NodeConfig.detectPath()); // load json file | ||
this.configPath = configPath ?? (await NodeConfig.detectPath()); // load yaml file | ||
const fileContent = await _fs.promises.readFile(this.configPath); | ||
this.load(JSON.parse(fileContent.toString())); | ||
const config = _jsYaml.default.load(fileContent.toString()); | ||
this.validateSchema(config); | ||
this.load(config); | ||
} | ||
@@ -94,3 +102,3 @@ /** | ||
* | ||
* @param configPath Path of the config.json file, will be determined automatically if omitted | ||
* @param configPath Path of the config.yaml file, will be determined automatically if omitted | ||
*/ | ||
@@ -100,7 +108,10 @@ | ||
loadFromFileSync(configPath) { | ||
this.configPath = configPath ?? NodeConfig.detectPathSync(); // load json file | ||
this.configPath = configPath ?? NodeConfig.detectPathSync(); // load yaml file | ||
const fileContent = _fs.default.readFileSync(this.configPath); | ||
this.load(JSON.parse(fileContent.toString())); | ||
const config = _jsYaml.default.load(fileContent.toString()); | ||
this.validateSchema(config); | ||
this.load(config); | ||
} | ||
@@ -115,5 +126,30 @@ | ||
} | ||
/** | ||
* Validate the config against the schema. | ||
* | ||
* @param config Config object | ||
*/ | ||
validateSchema(config) { | ||
// eslint-disable-line | ||
const schemaPath = _path.default.join(__dirname, '..', 'config.schema.yaml'); | ||
const schema = _jsYaml.default.load(_fs.default.readFileSync(schemaPath).toString()); | ||
const validator = new _ajv.default({ | ||
allErrors: true, | ||
strict: true, | ||
strictSchema: true, | ||
strictNumbers: true | ||
}); | ||
const validate = validator.compile(schema); | ||
if (!validate(config)) { | ||
throw new Error(`config.yaml did not pass validation: ${validator.errorsText(validate.errors)}`); | ||
} | ||
} | ||
} | ||
exports.default = NodeConfig; |
{ | ||
"name": "@skills17/task-config", | ||
"version": "2.2.1", | ||
"version": "3.0.0", | ||
"description": "Parses and validates a task configuration file.", | ||
@@ -8,3 +8,4 @@ "main": "lib/index.js", | ||
"files": [ | ||
"lib" | ||
"lib", | ||
"config.schema.yaml" | ||
], | ||
@@ -33,23 +34,26 @@ "scripts": { | ||
"devDependencies": { | ||
"@babel/cli": "^7.12.10", | ||
"@babel/core": "^7.12.10", | ||
"@babel/preset-env": "^7.12.11", | ||
"@babel/preset-typescript": "^7.12.7", | ||
"@types/jest": "^26.0.19", | ||
"@typescript-eslint/eslint-plugin": "^4.12.0", | ||
"@typescript-eslint/parser": "^4.12.0", | ||
"babel-jest": "^26.6.3", | ||
"eslint": "^7.17.0", | ||
"eslint-config-airbnb-base": "^14.2.1", | ||
"eslint-config-prettier": "^7.1.0", | ||
"eslint-plugin-import": "^2.22.1", | ||
"eslint-plugin-prettier": "^3.3.1", | ||
"jest": "^26.6.3", | ||
"prettier": "^2.2.1", | ||
"typescript": "^4.1.3" | ||
"@babel/cli": "^7.16.0", | ||
"@babel/core": "^7.16.0", | ||
"@babel/preset-env": "^7.16.4", | ||
"@babel/preset-typescript": "^7.16.0", | ||
"@types/jest": "^27.0.3", | ||
"@types/js-yaml": "^4.0.5", | ||
"@typescript-eslint/eslint-plugin": "^5.4.0", | ||
"@typescript-eslint/parser": "^5.4.0", | ||
"babel-jest": "^27.3.1", | ||
"eslint": "^8.3.0", | ||
"eslint-config-airbnb-base": "^15.0.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-plugin-import": "^2.25.3", | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"jest": "^27.3.1", | ||
"prettier": "^2.4.1", | ||
"typescript": "^4.5.2" | ||
}, | ||
"dependencies": { | ||
"@skills17/test-result": "^2.0.4", | ||
"find-up": "^5.0.0" | ||
"@skills17/test-result": "^2.1.0", | ||
"ajv": "^8.8.2", | ||
"find-up": "^5.0.0", | ||
"js-yaml": "^4.1.0" | ||
} | ||
} |
220
README.md
@@ -21,3 +21,3 @@ # skills17/task-config | ||
Create a `config.json` file for your task in the root folder of the task. | ||
Create a `config.yaml` file for your task in the root folder of the task. | ||
See the [configuration](#configuration) section below for a detailed overview of all possible configuration values. | ||
@@ -38,3 +38,2 @@ | ||
- `getId()` | ||
- `getType()` | ||
- `getSource()` | ||
@@ -45,4 +44,5 @@ - `getServe()` | ||
- `getProjectRoot()` | ||
- `getMetadata()` | ||
- `isLocalHistoryEnabled()` | ||
- `arePointsShown()` | ||
- `arePointsDisplayed()` | ||
@@ -64,3 +64,3 @@ Or directly create a new test run instance (from [`@skills17/test-result`](https://github.com/skills17/test-result)) where you can start recording the tests: | ||
Since the browser does not have access to the filesytem, it cannot load the `config.json` automatically. | ||
Since the browser does not have access to the filesytem, it cannot load the `config.yaml` automatically. | ||
Instead, you have to pass the configuration object directly to the load method: | ||
@@ -80,3 +80,3 @@ | ||
The following properties are available and can be set in the `config.json` file. | ||
The following properties are available and can be set in the `config.yaml` file. | ||
@@ -87,7 +87,2 @@ #### `id: string` | ||
#### `type: string` | ||
Defines the task type so they can later be grouped. | ||
For example, task types can be their programming language (`js`, `php`, ...). | ||
#### `source: string[]` | ||
@@ -100,7 +95,7 @@ | ||
The files can be specified by using [minimatch globs](https://www.npmjs.com/package/minimatch). | ||
The files can be specified by using globs. | ||
#### `tests: string[]` | ||
Default: `["./tests/**/*.@(spec|test).@(js|ts)"]` | ||
Default: `["./tests/**/*.spec.*", "./tests/**/*.test.*"]` | ||
@@ -110,3 +105,3 @@ Some skills17 packages require all test files to be specified. | ||
The files can be specified by using [minimatch globs](https://www.npmjs.com/package/minimatch). | ||
The files can be specified by using globs. | ||
@@ -116,11 +111,10 @@ #### `database: Database` | ||
Default: | ||
```json | ||
{ | ||
"enabled": false, | ||
"dump": "./database.sql", | ||
"name": "skills17", | ||
"user": "root", | ||
"password": "", | ||
"host": "127.0.0.1" | ||
}, | ||
```yaml | ||
database: | ||
enabled: false | ||
dump: ./database.sql | ||
name: skills17 | ||
user: root | ||
password: '' | ||
host: 127.0.0.1 | ||
``` | ||
@@ -134,11 +128,9 @@ | ||
Default: | ||
```json | ||
{ | ||
"enabled": false, | ||
"port": 3000, | ||
"bind": "127.0.0.1", | ||
"mapping": { | ||
"/": "./src" | ||
} | ||
} | ||
```yaml | ||
serve: | ||
enabled: false | ||
port: 3000 | ||
bind: 127.0.0.1 | ||
mapping: | ||
/: ./src | ||
``` | ||
@@ -157,7 +149,7 @@ | ||
#### `showPoints: boolean` | ||
#### `displayPoints: boolean` | ||
Default: `true` | ||
If false, points will not be shown in the normal output. | ||
If false, points will not be displayed in the normal output. | ||
For JSON outputs, they will still be available. | ||
@@ -168,7 +160,6 @@ | ||
Default: | ||
```json | ||
{ | ||
"defaultPoints": 1, | ||
"strategy": "add" | ||
} | ||
```yaml | ||
points: | ||
defaultPoints: 1 | ||
strategy: add | ||
``` | ||
@@ -188,97 +179,86 @@ | ||
Each test group can have the following configuration: | ||
```json5 | ||
{ | ||
// A regex to match tests of this group. For JS, groups are determined | ||
// by `describe` statements, for PHP, it is specified as a test method prefix. | ||
"match": "CountriesIndex.+", | ||
```yaml | ||
groups: | ||
# A regex to match tests of this group. For JS, groups are determined | ||
# by `describe` statements, for PHP, it is specified as a test method prefix. | ||
match: CountriesIndex.+ | ||
// An optional display name will be used in all outputs. | ||
"displayName": "CountriesController::index", | ||
# An optional display name will be used in all outputs. | ||
displayName: CountriesController::index | ||
// Optionally sets the default points tests will award in this group. | ||
// Only needed when overwriting the global default value. | ||
"defaultPoints": 1, | ||
# Optionally sets the default points tests will award in this group. | ||
# Only needed when overwriting the global default value. | ||
defaultPoints: 1 | ||
// Optionally sets the strategy used in this group. | ||
// Only needed when overwriting the global default value. | ||
"strategy": "deduct", | ||
# Optionally sets the strategy used in this group. | ||
# Only needed when overwriting the global default value. | ||
strategy: deduct | ||
// Optionally sets the maximum number of points that can be scored in this group. | ||
// This can only be set when the strategy "deduct" is used and the maximum points | ||
// should not equal the sum of all tests. | ||
"maxPoints": 3, | ||
# Optionally sets the maximum number of points that can be scored in this group. | ||
# This can only be set when the strategy "deduct" is used and the maximum points | ||
# should not equal the sum of all tests. | ||
maxPoints: 3 | ||
// Optionally define overrides for single tests. | ||
"tests": [ | ||
{ | ||
// A regex to match the test inside this group. | ||
// If the default values are okay for a test, it does not need to be specified here. | ||
"match": "CountriesIndexJson", | ||
# Optionally define overrides for single tests. | ||
tests: | ||
# A regex to match the test inside this group. | ||
# If the default values are okay for a test, it does not need to be specified here. | ||
- match: CountriesIndexJson | ||
// Optionally specify points per test if they should be different from the default points. | ||
"points": 0, | ||
# Optionally specify points per test if they should be different from the default points. | ||
points: 0 | ||
// Optionally set this as a required test. | ||
// If a required test does not pass, the whole group will award 0 points. | ||
"required": true | ||
} | ||
] | ||
} | ||
# Optionally set this as a required test. | ||
# If a required test does not pass, the whole group will award 0 points. | ||
required: true | ||
``` | ||
#### `metadata: Record<string, string>` | ||
Default: `{}` | ||
Can contain any key and value pair which can be used lated in other libraries. | ||
### Full example | ||
Many of the values in this example are default values and can be left out. | ||
But it shows how a full `config.json` can look like and what settings are available. | ||
But it shows how a full `config.yaml` can look like and what settings are available. | ||
```json | ||
{ | ||
"id": "js-task-1", | ||
"type": "js", | ||
"source": [ | ||
"./src/**" | ||
], | ||
"database": { | ||
"enabled": true, | ||
"dump": "./database.sql", | ||
"name": "skills17", | ||
"user": "root", | ||
"password": "", | ||
"host": "127.0.0.1" | ||
}, | ||
"serve": { | ||
"enabled": true, | ||
"port": 3000, | ||
"bind": "127.0.0.1", | ||
"mapping": { | ||
"/": "./src" | ||
} | ||
}, | ||
"localHistory": false, | ||
"showPoints": true, | ||
"points": { | ||
"defaultPoints": 1, | ||
"strategy": "add" | ||
}, | ||
"groups": [ | ||
{ | ||
"match": "CountriesIndex.+", | ||
"displayName": "CountriesController::index", | ||
"defaultPoints": 1, | ||
"strategy": "deduct", | ||
"maxPoints": 2, | ||
"tests": [ | ||
{ | ||
"match": "CountriesIndexJson", | ||
"points": 0, | ||
"required": true | ||
}, | ||
{ | ||
"match": "CountriesIndexSearch", | ||
"points": 2 | ||
} | ||
] | ||
} | ||
] | ||
} | ||
```yaml | ||
id: js-task-1 | ||
source: | ||
- ./src/** | ||
tests: | ||
- ./tests/**/*.spec.* | ||
- ./tests/**/*.test.* | ||
database: | ||
enabled: true | ||
dump: ./database.sql | ||
name: skills17 | ||
user: root | ||
password: '' | ||
host: 127.0.0.1 | ||
serve: | ||
enabled: true | ||
port: 3000 | ||
bind: 127.0.0.1 | ||
mapping: | ||
/: ./src | ||
localHistory: false | ||
displayPoints: true | ||
points: | ||
defaultPoints: 1 | ||
strategy: add | ||
groups: | ||
- match: CountriesIndex.+ | ||
displayName: CountriesController::index | ||
defaultPoints: 1 | ||
strategy: deduct | ||
maxPoints: 2 | ||
tests: | ||
- match: CountriesIndexJson | ||
points: 0 | ||
required: true | ||
- match: CountriesIndexSearch | ||
points: 2 | ||
``` | ||
@@ -285,0 +265,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
25305
20
405
4
17
256
+ Addedajv@^8.8.2
+ Addedjs-yaml@^4.1.0
+ Addedajv@8.17.1(transitive)
+ Addedargparse@2.0.1(transitive)
+ Addedfast-deep-equal@3.1.3(transitive)
+ Addedfast-uri@3.0.3(transitive)
+ Addedjs-yaml@4.1.0(transitive)
+ Addedjson-schema-traverse@1.0.0(transitive)
+ Addedrequire-from-string@2.0.2(transitive)
Updated@skills17/test-result@^2.1.0