v8r 5.1.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -1
- package/package.json +11 -7
- package/src/ajv.js +3 -9
- package/src/bootstrap.js +3 -7
- package/src/cache.js +4 -2
- package/src/cli.js +1 -1
- package/src/config-validators.js +2 -7
- package/src/index.js +1 -2
- package/src/plugin-loader.js +99 -0
- package/src/plugins.js +3 -76
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 📦 [6.0.0](https://www.npmjs.com/package/v8r/v/6.0.0) - 2026-02-21
|
|
4
|
+
|
|
5
|
+
* **Breaking:** Drop compatibility with node 20
|
|
6
|
+
* This release adds a new installation option: standalone binaries.
|
|
7
|
+
Moving forwards, standalone binaries for Linux, Windows and MacOS available
|
|
8
|
+
for download from https://github.com/chris48s/v8r/releases/latest .
|
|
9
|
+
* Upgrade to latest major versions of core packages (glob, minimatch, p-limit)
|
|
10
|
+
|
|
3
11
|
## 📦 [5.1.0](https://www.npmjs.com/package/v8r/v/5.1.0) - 2025-07-20
|
|
4
12
|
|
|
5
13
|
* v8r now pre-warms the cache and fetches schemas in parallel.
|
|
6
|
-
This will
|
|
14
|
+
This will decrease total run time for any run that involves fetching
|
|
7
15
|
more than one remote schema, or involves a schema with remote `$ref`s.
|
|
8
16
|
* Improve handling of empty yaml files.
|
|
9
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "v8r",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "A command-line JSON, YAML and TOML validator that's on your wavelength",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "V8R_CACHE_NAME=v8r-test c8 --reporter=text mocha \"src/**/*.spec.js\"",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"coverage": "c8 report --reporter=cobertura",
|
|
9
9
|
"prettier": "prettier --write \"**/*.{js,cjs,mjs}\"",
|
|
10
10
|
"prettier:check": "prettier --check \"**/*.{js,cjs,mjs}\"",
|
|
11
|
-
"v8r": "src/index.js"
|
|
11
|
+
"v8r": "src/index.js",
|
|
12
|
+
"build:bin": "node sea/build.js && $npm_node_execpath --build-sea sea/config.json",
|
|
13
|
+
"build:exe": "node sea/build.js && %npm_node_execpath% --build-sea sea/config-win.json"
|
|
12
14
|
},
|
|
13
15
|
"bin": {
|
|
14
16
|
"v8r": "src/index.js"
|
|
@@ -34,7 +36,7 @@
|
|
|
34
36
|
"cosmiconfig": "^9.0.0",
|
|
35
37
|
"decamelize": "^6.0.0",
|
|
36
38
|
"flat-cache": "^6.1.4",
|
|
37
|
-
"glob": "^
|
|
39
|
+
"glob": "^13.0.0",
|
|
38
40
|
"global-agent": "^3.0.0",
|
|
39
41
|
"got": "^14.0.0",
|
|
40
42
|
"ignore": "^7.0.0",
|
|
@@ -42,16 +44,18 @@
|
|
|
42
44
|
"js-yaml": "^4.0.0",
|
|
43
45
|
"json5": "^2.2.0",
|
|
44
46
|
"minimatch": "^10.0.0",
|
|
45
|
-
"p-limit": "^
|
|
47
|
+
"p-limit": "^7.0.0",
|
|
46
48
|
"p-mutex": "^1.0.0",
|
|
47
49
|
"smol-toml": "^1.0.1",
|
|
48
50
|
"yargs": "^18.0.0"
|
|
49
51
|
},
|
|
50
52
|
"devDependencies": {
|
|
53
|
+
"@eslint/js": "^10.0.1",
|
|
51
54
|
"c8": "^10.1.2",
|
|
52
|
-
"
|
|
55
|
+
"esbuild": "^0.27.3",
|
|
56
|
+
"eslint": "^10.0.1",
|
|
53
57
|
"eslint-config-prettier": "^10.1.2",
|
|
54
|
-
"eslint-plugin-jsdoc": "^
|
|
58
|
+
"eslint-plugin-jsdoc": "^62.7.0",
|
|
55
59
|
"eslint-plugin-mocha": "^11.0.0",
|
|
56
60
|
"eslint-plugin-prettier": "^5.0.0",
|
|
57
61
|
"mocha": "^11.0.1",
|
|
@@ -61,7 +65,7 @@
|
|
|
61
65
|
"prettier-plugin-jsdoc": "^1.3.0"
|
|
62
66
|
},
|
|
63
67
|
"engines": {
|
|
64
|
-
"node": ">=
|
|
68
|
+
"node": ">=22"
|
|
65
69
|
},
|
|
66
70
|
"type": "module",
|
|
67
71
|
"keywords": [
|
package/src/ajv.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
// TODO: once JSON modules is stable these requires could become imports
|
|
3
|
-
// https://nodejs.org/api/esm.html#esm_experimental_json_modules
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
-
|
|
6
1
|
import AjvDraft4 from "ajv-draft-04";
|
|
7
2
|
import Ajv from "ajv";
|
|
8
3
|
import Ajv2019 from "ajv/dist/2019.js";
|
|
9
4
|
import Ajv2020 from "ajv/dist/2020.js";
|
|
10
5
|
import addFormats from "ajv-formats";
|
|
6
|
+
import draft06 from "ajv/lib/refs/json-schema-draft-06.json" with { type: "json" };
|
|
11
7
|
|
|
12
8
|
function _ajvFactory(
|
|
13
9
|
schema,
|
|
@@ -26,9 +22,7 @@ function _ajvFactory(
|
|
|
26
22
|
return new AjvDraft4(opts);
|
|
27
23
|
} else if (schema["$schema"].includes("json-schema.org/draft-06/schema")) {
|
|
28
24
|
const ajvDraft06 = new Ajv(opts);
|
|
29
|
-
ajvDraft06.addMetaSchema(
|
|
30
|
-
require("ajv/lib/refs/json-schema-draft-06.json"),
|
|
31
|
-
);
|
|
25
|
+
ajvDraft06.addMetaSchema(draft06);
|
|
32
26
|
return ajvDraft06;
|
|
33
27
|
} else if (schema["$schema"].includes("json-schema.org/draft-07/schema")) {
|
|
34
28
|
return new Ajv(opts);
|
|
@@ -45,7 +39,7 @@ function _ajvFactory(
|
|
|
45
39
|
|
|
46
40
|
// hedge our bets as best we can
|
|
47
41
|
const ajv = new Ajv(opts);
|
|
48
|
-
ajv.addMetaSchema(
|
|
42
|
+
ajv.addMetaSchema(draft06);
|
|
49
43
|
return ajv;
|
|
50
44
|
|
|
51
45
|
/* TODO:
|
package/src/bootstrap.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
// TODO: once JSON modules is stable these requires could become imports
|
|
3
|
-
// https://nodejs.org/api/esm.html#esm_experimental_json_modules
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
-
|
|
6
1
|
import fs from "node:fs";
|
|
7
2
|
import path from "node:path";
|
|
8
3
|
import { cosmiconfig } from "cosmiconfig";
|
|
@@ -15,7 +10,8 @@ import {
|
|
|
15
10
|
validateConfigOutputFormats,
|
|
16
11
|
} from "./config-validators.js";
|
|
17
12
|
import logger from "./logger.js";
|
|
18
|
-
import { loadAllPlugins, resolveUserPlugins } from "./
|
|
13
|
+
import { loadAllPlugins, resolveUserPlugins } from "./plugin-loader.js";
|
|
14
|
+
import manifest from "../package.json" with { type: "json" };
|
|
19
15
|
|
|
20
16
|
async function getCosmiConfig(cosmiconfigOptions) {
|
|
21
17
|
let configFile;
|
|
@@ -127,7 +123,7 @@ function parseArgs(argv, config, documentFormats, outputFormats) {
|
|
|
127
123
|
.version(
|
|
128
124
|
// Workaround for https://github.com/yargs/yargs/issues/1934
|
|
129
125
|
// TODO: remove once fixed
|
|
130
|
-
|
|
126
|
+
manifest.version,
|
|
131
127
|
)
|
|
132
128
|
.option("verbose", {
|
|
133
129
|
alias: "v",
|
package/src/cache.js
CHANGED
|
@@ -73,9 +73,11 @@ class Cache {
|
|
|
73
73
|
return parsedBody;
|
|
74
74
|
} catch (error) {
|
|
75
75
|
if (error.response) {
|
|
76
|
-
throw new Error(`Failed fetching ${url}\n${error.response.body}
|
|
76
|
+
throw new Error(`Failed fetching ${url}\n${error.response.body}`, {
|
|
77
|
+
cause: error,
|
|
78
|
+
});
|
|
77
79
|
}
|
|
78
|
-
throw new Error(`Failed fetching ${url}
|
|
80
|
+
throw new Error(`Failed fetching ${url}`, { cause: error });
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
|
package/src/cli.js
CHANGED
|
@@ -169,7 +169,7 @@ function resultsToStatusCode(results, ignoreErrors) {
|
|
|
169
169
|
|
|
170
170
|
function Validator() {
|
|
171
171
|
return async function (config, plugins) {
|
|
172
|
-
let filenames
|
|
172
|
+
let filenames;
|
|
173
173
|
try {
|
|
174
174
|
filenames = await getFiles(config.patterns, config.ignorePatternFiles);
|
|
175
175
|
} catch (e) {
|
package/src/config-validators.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
// TODO: once JSON modules is stable these requires could become imports
|
|
3
|
-
// https://nodejs.org/api/esm.html#esm_experimental_json_modules
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
-
|
|
6
1
|
import Ajv2019 from "ajv/dist/2019.js";
|
|
7
2
|
import logger from "./logger.js";
|
|
8
3
|
import { formatErrors } from "./output-formatters.js";
|
|
4
|
+
import configSchema from "../config-schema.json" with { type: "json" };
|
|
9
5
|
|
|
10
6
|
function validateConfigAgainstSchema(configFile) {
|
|
11
7
|
const ajv = new Ajv2019({ allErrors: true, strict: false });
|
|
12
|
-
const
|
|
13
|
-
const validateFn = ajv.compile(schema);
|
|
8
|
+
const validateFn = ajv.compile(configSchema);
|
|
14
9
|
const valid = validateFn(configFile.config);
|
|
15
10
|
if (!valid) {
|
|
16
11
|
logger.log(
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { isSea } from "node:sea";
|
|
3
|
+
import logger from "./logger.js";
|
|
4
|
+
import { BasePlugin } from "./plugins.js";
|
|
5
|
+
import ParserJson from "./plugins/parser-json.js";
|
|
6
|
+
import ParserJson5 from "./plugins/parser-json5.js";
|
|
7
|
+
import ParserToml from "./plugins/parser-toml.js";
|
|
8
|
+
import ParserYaml from "./plugins/parser-yaml.js";
|
|
9
|
+
import OutputText from "./plugins/output-text.js";
|
|
10
|
+
import OutputJson from "./plugins/output-json.js";
|
|
11
|
+
|
|
12
|
+
function validatePlugin(plugin) {
|
|
13
|
+
if (
|
|
14
|
+
typeof plugin.name !== "string" ||
|
|
15
|
+
!plugin.name.startsWith("v8r-plugin-")
|
|
16
|
+
) {
|
|
17
|
+
throw new Error(`Plugin ${plugin.name} does not declare a valid name`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!(plugin.prototype instanceof BasePlugin)) {
|
|
21
|
+
throw new Error(`Plugin ${plugin.name} does not extend BasePlugin`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const prop of Object.getOwnPropertyNames(BasePlugin.prototype)) {
|
|
25
|
+
const method = plugin.prototype[prop];
|
|
26
|
+
const argCount = plugin.prototype[prop].length;
|
|
27
|
+
if (typeof method !== "function") {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Error loading plugin ${plugin.name}: must have a method called ${method}`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const expectedArgs = BasePlugin.prototype[prop].length;
|
|
33
|
+
if (expectedArgs !== argCount) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Error loading plugin ${plugin.name}: ${prop} must take exactly ${expectedArgs} arguments`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveUserPlugins(userPlugins) {
|
|
42
|
+
let plugins = [];
|
|
43
|
+
for (let plugin of userPlugins) {
|
|
44
|
+
if (plugin.startsWith("package:")) {
|
|
45
|
+
plugins.push(plugin.slice(8));
|
|
46
|
+
}
|
|
47
|
+
if (plugin.startsWith("file:")) {
|
|
48
|
+
plugins.push(path.resolve(process.cwd(), plugin.slice(5)));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return plugins;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function loadCorePlugins(plugins) {
|
|
55
|
+
plugins.forEach((plugin) => validatePlugin(plugin));
|
|
56
|
+
const loadedPlugins = plugins.map((plugin) => new plugin());
|
|
57
|
+
return loadedPlugins;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function loadUserPlugins(plugins) {
|
|
61
|
+
let loadedPlugins = [];
|
|
62
|
+
for (const plugin of plugins) {
|
|
63
|
+
loadedPlugins.push(await import(plugin));
|
|
64
|
+
}
|
|
65
|
+
loadedPlugins = loadedPlugins.map((plugin) => plugin.default);
|
|
66
|
+
|
|
67
|
+
loadedPlugins.forEach((plugin) => validatePlugin(plugin));
|
|
68
|
+
loadedPlugins = loadedPlugins.map((plugin) => new plugin());
|
|
69
|
+
return loadedPlugins;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function loadAllPlugins(userPlugins) {
|
|
73
|
+
let loadedUserPlugins = [];
|
|
74
|
+
if (isSea() && userPlugins.length > 0) {
|
|
75
|
+
logger.warning(
|
|
76
|
+
"loading plugins is not supported when running as a standalone binary",
|
|
77
|
+
);
|
|
78
|
+
} else {
|
|
79
|
+
loadedUserPlugins = await loadUserPlugins(userPlugins);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const corePlugins = [
|
|
83
|
+
ParserJson,
|
|
84
|
+
ParserJson5,
|
|
85
|
+
ParserToml,
|
|
86
|
+
ParserYaml,
|
|
87
|
+
OutputText,
|
|
88
|
+
OutputJson,
|
|
89
|
+
];
|
|
90
|
+
const loadedCorePlugins = loadCorePlugins(corePlugins);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
allLoadedPlugins: loadedUserPlugins.concat(loadedCorePlugins),
|
|
94
|
+
loadedCorePlugins,
|
|
95
|
+
loadedUserPlugins,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { loadAllPlugins, resolveUserPlugins };
|
package/src/plugins.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Base class for all v8r plugins.
|
|
5
3
|
*
|
|
@@ -105,90 +103,19 @@ class BasePlugin {
|
|
|
105
103
|
}
|
|
106
104
|
|
|
107
105
|
class Document {
|
|
106
|
+
/* eslint-disable jsdoc/reject-any-type */
|
|
108
107
|
/**
|
|
109
108
|
* Document is a thin wrapper class for a document we want to validate after
|
|
110
109
|
* parsing a file
|
|
111
110
|
*
|
|
112
111
|
* @param {any} document - The object to be wrapped
|
|
113
112
|
*/
|
|
113
|
+
/* eslint-enable jsdoc/reject-any-type */
|
|
114
114
|
constructor(document) {
|
|
115
115
|
this.document = document;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
function validatePlugin(plugin) {
|
|
120
|
-
if (
|
|
121
|
-
typeof plugin.name !== "string" ||
|
|
122
|
-
!plugin.name.startsWith("v8r-plugin-")
|
|
123
|
-
) {
|
|
124
|
-
throw new Error(`Plugin ${plugin.name} does not declare a valid name`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (!(plugin.prototype instanceof BasePlugin)) {
|
|
128
|
-
throw new Error(`Plugin ${plugin.name} does not extend BasePlugin`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
for (const prop of Object.getOwnPropertyNames(BasePlugin.prototype)) {
|
|
132
|
-
const method = plugin.prototype[prop];
|
|
133
|
-
const argCount = plugin.prototype[prop].length;
|
|
134
|
-
if (typeof method !== "function") {
|
|
135
|
-
throw new Error(
|
|
136
|
-
`Error loading plugin ${plugin.name}: must have a method called ${method}`,
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
const expectedArgs = BasePlugin.prototype[prop].length;
|
|
140
|
-
if (expectedArgs !== argCount) {
|
|
141
|
-
throw new Error(
|
|
142
|
-
`Error loading plugin ${plugin.name}: ${prop} must take exactly ${expectedArgs} arguments`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function resolveUserPlugins(userPlugins) {
|
|
149
|
-
let plugins = [];
|
|
150
|
-
for (let plugin of userPlugins) {
|
|
151
|
-
if (plugin.startsWith("package:")) {
|
|
152
|
-
plugins.push(plugin.slice(8));
|
|
153
|
-
}
|
|
154
|
-
if (plugin.startsWith("file:")) {
|
|
155
|
-
plugins.push(path.resolve(process.cwd(), plugin.slice(5)));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return plugins;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function loadPlugins(plugins) {
|
|
162
|
-
let loadedPlugins = [];
|
|
163
|
-
for (const plugin of plugins) {
|
|
164
|
-
loadedPlugins.push(await import(plugin));
|
|
165
|
-
}
|
|
166
|
-
loadedPlugins = loadedPlugins.map((plugin) => plugin.default);
|
|
167
|
-
loadedPlugins.forEach((plugin) => validatePlugin(plugin));
|
|
168
|
-
loadedPlugins = loadedPlugins.map((plugin) => new plugin());
|
|
169
|
-
return loadedPlugins;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async function loadAllPlugins(userPlugins) {
|
|
173
|
-
const loadedUserPlugins = await loadPlugins(userPlugins);
|
|
174
|
-
|
|
175
|
-
const corePlugins = [
|
|
176
|
-
"./plugins/parser-json.js",
|
|
177
|
-
"./plugins/parser-json5.js",
|
|
178
|
-
"./plugins/parser-toml.js",
|
|
179
|
-
"./plugins/parser-yaml.js",
|
|
180
|
-
"./plugins/output-text.js",
|
|
181
|
-
"./plugins/output-json.js",
|
|
182
|
-
];
|
|
183
|
-
const loadedCorePlugins = await loadPlugins(corePlugins);
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
allLoadedPlugins: loadedUserPlugins.concat(loadedCorePlugins),
|
|
187
|
-
loadedCorePlugins,
|
|
188
|
-
loadedUserPlugins,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
119
|
/**
|
|
193
120
|
* @typedef {object} ValidationResult
|
|
194
121
|
* @property {string} fileLocation - Path of the document that was validated.
|
|
@@ -213,4 +140,4 @@ async function loadAllPlugins(userPlugins) {
|
|
|
213
140
|
* @see https://ajv.js.org/api.html#error-objects
|
|
214
141
|
*/
|
|
215
142
|
|
|
216
|
-
export { BasePlugin, Document
|
|
143
|
+
export { BasePlugin, Document };
|