v8r 4.4.0 → 5.1.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 +31 -0
- package/config-schema.json +0 -12
- package/package.json +10 -8
- package/src/bootstrap.js +4 -27
- package/src/cache-prewarm.js +87 -0
- package/src/cache.js +42 -17
- package/src/catalogs.js +3 -3
- package/src/cli.js +8 -2
- package/src/glob.js +0 -8
- package/src/parser.js +3 -0
- package/src/plugins/output-text.js +1 -1
- package/src/plugins.js +7 -46
- package/src/test-helpers.js +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 📦 [5.1.0](https://www.npmjs.com/package/v8r/v/5.1.0) - 2025-07-20
|
|
4
|
+
|
|
5
|
+
* v8r now pre-warms the cache and fetches schemas in parallel.
|
|
6
|
+
This will improve decrease total run time for any run that involves fetching
|
|
7
|
+
more than one remote schema, or involves a schema with remote `$ref`s.
|
|
8
|
+
* Improve handling of empty yaml files.
|
|
9
|
+
|
|
10
|
+
## 📦 [5.0.0](https://www.npmjs.com/package/v8r/v/5.0.0) - 2025-05-10
|
|
11
|
+
|
|
12
|
+
Following on from the deprecations in version 4.4.0,
|
|
13
|
+
version 5.0.0 contains a number of breaking changes:
|
|
14
|
+
|
|
15
|
+
* The `--format` CLI argument and `format` config file key have been removed.
|
|
16
|
+
Switch to using `--output-format` and `outputFormat`.
|
|
17
|
+
* v8r now ignores patterns in `.gitignore` by default.
|
|
18
|
+
* The `fileLocation` argument of `getSingleResultLogMessage` has been removed.
|
|
19
|
+
The signature is now `getSingleResultLogMessage(result, format)`.
|
|
20
|
+
Plugins implementing the `getSingleResultLogMessage` hook will need to to update
|
|
21
|
+
the signature.
|
|
22
|
+
If you are using `fileLocation` in the `getSingleResultLogMessage` function body,
|
|
23
|
+
switch to using `result.fileLocation`.
|
|
24
|
+
* File paths are no longer passed to plugins in dot-relative notation.
|
|
25
|
+
Plugins implementing the `getSingleResultLogMessage`, `getAllResultsLogMessage` and `parseInputFile`
|
|
26
|
+
plugin hooks may need to be updated.
|
|
27
|
+
* The minimum compatible node version is now Node 20.
|
|
28
|
+
|
|
29
|
+
Other changes in this release:
|
|
30
|
+
|
|
31
|
+
* v8r is now tested on node 24
|
|
32
|
+
* Upgrade to latest major versions of core packages (got, glob, minimatch, etc)
|
|
33
|
+
|
|
3
34
|
## 📦 [4.4.0](https://www.npmjs.com/package/v8r/v/4.4.0) - 2025-04-26
|
|
4
35
|
|
|
5
36
|
Version 4.4.0 is a deprecation release. This release adds deprecation warnings for
|
package/config-schema.json
CHANGED
|
@@ -3,13 +3,6 @@
|
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"additionalProperties": false,
|
|
6
|
-
"allOf": [
|
|
7
|
-
{
|
|
8
|
-
"not": {
|
|
9
|
-
"required": ["format", "outputFormat"]
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
6
|
"properties": {
|
|
14
7
|
"cacheTtl": {
|
|
15
8
|
"description": "Remove cached HTTP responses older than cacheTtl seconds old. Specifying 0 clears and disables cache completely",
|
|
@@ -68,11 +61,6 @@
|
|
|
68
61
|
"description": "Output format for validation results. 'text' and 'json' are always valid. Plugins may define additional values which are valid here.",
|
|
69
62
|
"type": "string"
|
|
70
63
|
},
|
|
71
|
-
"format": {
|
|
72
|
-
"description": "Output format for validation results. 'text' and 'json' are always valid. Plugins may define additional values which are valid here.",
|
|
73
|
-
"type": "string",
|
|
74
|
-
"deprecated": true
|
|
75
|
-
},
|
|
76
64
|
"ignoreErrors": {
|
|
77
65
|
"description": "Exit with code 0 even if an error was encountered. True means a non-zero exit code is only issued if validation could be completed successfully and one or more files were invalid",
|
|
78
66
|
"type": "boolean"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "v8r",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.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\"",
|
|
@@ -34,23 +34,25 @@
|
|
|
34
34
|
"cosmiconfig": "^9.0.0",
|
|
35
35
|
"decamelize": "^6.0.0",
|
|
36
36
|
"flat-cache": "^6.1.4",
|
|
37
|
-
"glob": "^
|
|
37
|
+
"glob": "^11.0.0",
|
|
38
38
|
"global-agent": "^3.0.0",
|
|
39
|
-
"got": "^
|
|
39
|
+
"got": "^14.0.0",
|
|
40
40
|
"ignore": "^7.0.0",
|
|
41
41
|
"is-url": "^1.2.4",
|
|
42
42
|
"js-yaml": "^4.0.0",
|
|
43
43
|
"json5": "^2.2.0",
|
|
44
|
-
"minimatch": "^
|
|
44
|
+
"minimatch": "^10.0.0",
|
|
45
|
+
"p-limit": "^6.2.0",
|
|
46
|
+
"p-mutex": "^1.0.0",
|
|
45
47
|
"smol-toml": "^1.0.1",
|
|
46
|
-
"yargs": "^
|
|
48
|
+
"yargs": "^18.0.0"
|
|
47
49
|
},
|
|
48
50
|
"devDependencies": {
|
|
49
51
|
"c8": "^10.1.2",
|
|
50
52
|
"eslint": "^9.9.0",
|
|
51
53
|
"eslint-config-prettier": "^10.1.2",
|
|
52
|
-
"eslint-plugin-jsdoc": "^
|
|
53
|
-
"eslint-plugin-mocha": "^
|
|
54
|
+
"eslint-plugin-jsdoc": "^51.4.1",
|
|
55
|
+
"eslint-plugin-mocha": "^11.0.0",
|
|
54
56
|
"eslint-plugin-prettier": "^5.0.0",
|
|
55
57
|
"mocha": "^11.0.1",
|
|
56
58
|
"mock-cwd": "^1.0.0",
|
|
@@ -59,7 +61,7 @@
|
|
|
59
61
|
"prettier-plugin-jsdoc": "^1.3.0"
|
|
60
62
|
},
|
|
61
63
|
"engines": {
|
|
62
|
-
"node": ">=
|
|
64
|
+
"node": ">=20"
|
|
63
65
|
},
|
|
64
66
|
"type": "module",
|
|
65
67
|
"keywords": [
|
package/src/bootstrap.js
CHANGED
|
@@ -53,8 +53,8 @@ function mergeConfigs(args, config) {
|
|
|
53
53
|
if (config.filepath) {
|
|
54
54
|
mergedConfig.configFileRelativePath = getRelativeFilePath(config);
|
|
55
55
|
}
|
|
56
|
-
//
|
|
57
|
-
|
|
56
|
+
// hard-coded - this can't be set via CLI or config file
|
|
57
|
+
mergedConfig.cachePrewarm = true;
|
|
58
58
|
return mergedConfig;
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -81,7 +81,7 @@ function parseArgs(argv, config, documentFormats, outputFormats) {
|
|
|
81
81
|
const ignoreFilesOpts = {
|
|
82
82
|
describe: "A list of files containing glob patterns to ignore",
|
|
83
83
|
};
|
|
84
|
-
let ignoreFilesDefault = [".v8rignore"];
|
|
84
|
+
let ignoreFilesDefault = [".v8rignore", ".gitignore"];
|
|
85
85
|
ignoreFilesOpts.defaultDescription = `${JSON.stringify(ignoreFilesDefault)}`;
|
|
86
86
|
if (Object.keys(config.config).includes("ignorePatternFiles")) {
|
|
87
87
|
ignoreFilesDefault = config.config.ignorePatternFiles;
|
|
@@ -122,13 +122,6 @@ function parseArgs(argv, config, documentFormats, outputFormats) {
|
|
|
122
122
|
if (args.ignore === undefined) {
|
|
123
123
|
args.ignore = true;
|
|
124
124
|
}
|
|
125
|
-
|
|
126
|
-
// https://github.com/chris48s/v8r/issues/494
|
|
127
|
-
if (process.argv.includes("--format")) {
|
|
128
|
-
logger.warning(
|
|
129
|
-
"In v8r version 5 the --format argument will be removed. Switch to using --output-format",
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
125
|
},
|
|
133
126
|
)
|
|
134
127
|
.version(
|
|
@@ -190,10 +183,7 @@ function parseArgs(argv, config, documentFormats, outputFormats) {
|
|
|
190
183
|
type: "string",
|
|
191
184
|
choices: outputFormats,
|
|
192
185
|
default: "text",
|
|
193
|
-
|
|
194
|
-
describe:
|
|
195
|
-
"Output format for validation results. The '--format' alias is deprecated.",
|
|
196
|
-
alias: "format",
|
|
186
|
+
describe: "Output format for validation results",
|
|
197
187
|
})
|
|
198
188
|
.example([
|
|
199
189
|
["$0 file.json", "Validate a single file"],
|
|
@@ -251,14 +241,6 @@ async function bootstrap(argv, config, cosmiconfigOptions = {}) {
|
|
|
251
241
|
const configFile = await getCosmiConfig(cosmiconfigOptions);
|
|
252
242
|
validateConfigAgainstSchema(configFile);
|
|
253
243
|
|
|
254
|
-
// https://github.com/chris48s/v8r/issues/494
|
|
255
|
-
if (configFile.config.format) {
|
|
256
|
-
logger.warning(
|
|
257
|
-
"In v8r version 5 the 'format' config file key will be removed. Switch to using 'outputFormat'",
|
|
258
|
-
);
|
|
259
|
-
configFile.config.outputFormat = configFile.config.format;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
244
|
// load both core and user plugins
|
|
263
245
|
let plugins = resolveUserPlugins(configFile.config.plugins || []);
|
|
264
246
|
const { allLoadedPlugins, loadedCorePlugins, loadedUserPlugins } =
|
|
@@ -274,11 +256,6 @@ async function bootstrap(argv, config, cosmiconfigOptions = {}) {
|
|
|
274
256
|
// parse command line arguments
|
|
275
257
|
const args = parseArgs(argv, configFile, documentFormats, outputFormats);
|
|
276
258
|
|
|
277
|
-
// https://github.com/chris48s/v8r/issues/599
|
|
278
|
-
logger.warning(
|
|
279
|
-
"Starting from v8r version 5, v8r will ignore patterns in .gitignore by default.",
|
|
280
|
-
);
|
|
281
|
-
|
|
282
259
|
return {
|
|
283
260
|
config: mergeConfigs(args, configFile),
|
|
284
261
|
allLoadedPlugins,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import isUrl from "is-url";
|
|
2
|
+
import pLimit from "p-limit";
|
|
3
|
+
import { getCatalogs, getMatchForFilename } from "./catalogs.js";
|
|
4
|
+
|
|
5
|
+
const limit = pLimit(10);
|
|
6
|
+
|
|
7
|
+
async function fetch(location, cache) {
|
|
8
|
+
return await cache.fetch(location, false);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function fetchWithLimit(url, cache) {
|
|
12
|
+
return limit(() => fetch(url, cache));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeUrl(ref) {
|
|
16
|
+
try {
|
|
17
|
+
const url = new URL(ref);
|
|
18
|
+
url.hash = "";
|
|
19
|
+
return url.toString();
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getRemoteRefs(node) {
|
|
26
|
+
let refs = [];
|
|
27
|
+
if (Array.isArray(node)) {
|
|
28
|
+
for (const v of node) {
|
|
29
|
+
if (typeof v === "object" && v !== null) {
|
|
30
|
+
refs = refs.concat(getRemoteRefs(v));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} else if (typeof node === "object" && node !== null) {
|
|
34
|
+
for (const [k, v] of Object.entries(node)) {
|
|
35
|
+
if (k === "$ref" && typeof v === "string") {
|
|
36
|
+
const resolved = normalizeUrl(v);
|
|
37
|
+
if (resolved && isUrl(resolved)) {
|
|
38
|
+
refs.push(resolved);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (typeof v === "object" && v !== null) {
|
|
42
|
+
refs = refs.concat(getRemoteRefs(v));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return Array.from(new Set(refs));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function fetchAndRecurse(url, cache) {
|
|
50
|
+
const schema = await fetchWithLimit(url, cache);
|
|
51
|
+
const refs = getRemoteRefs(schema).filter(
|
|
52
|
+
(ref) => cache.get(ref) === undefined,
|
|
53
|
+
);
|
|
54
|
+
await Promise.all(refs.map((ref) => fetchAndRecurse(ref, cache)));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function prewarmSchemaCache(filenames, config, cache) {
|
|
58
|
+
const catalogs = getCatalogs(config);
|
|
59
|
+
const schemaLocations = new Set();
|
|
60
|
+
|
|
61
|
+
for (const filename of filenames) {
|
|
62
|
+
let catalogMatch;
|
|
63
|
+
try {
|
|
64
|
+
catalogMatch = config.schema
|
|
65
|
+
? {}
|
|
66
|
+
: await getMatchForFilename(catalogs, filename, "debug", cache);
|
|
67
|
+
} catch {
|
|
68
|
+
catalogMatch = {};
|
|
69
|
+
}
|
|
70
|
+
const schemaLocation = config.schema || catalogMatch.location;
|
|
71
|
+
|
|
72
|
+
if (schemaLocation) {
|
|
73
|
+
schemaLocations.add(schemaLocation);
|
|
74
|
+
}
|
|
75
|
+
cache.resetCounters();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await Promise.all(
|
|
79
|
+
Array.from(schemaLocations)
|
|
80
|
+
.filter((schemaLocation) => isUrl(schemaLocation))
|
|
81
|
+
.map((url) => fetchAndRecurse(url, cache)),
|
|
82
|
+
);
|
|
83
|
+
cache.persist();
|
|
84
|
+
cache.resetCounters();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { getRemoteRefs, prewarmSchemaCache, normalizeUrl };
|
package/src/cache.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import got from "got";
|
|
2
|
+
import Mutex from "p-mutex";
|
|
2
3
|
import logger from "./logger.js";
|
|
3
4
|
import { parseSchema } from "./parser.js";
|
|
4
5
|
|
|
@@ -7,39 +8,53 @@ class Cache {
|
|
|
7
8
|
this.cache = flatCache;
|
|
8
9
|
this.ttl = this.cache._cache.ttl || 0;
|
|
9
10
|
this.callCounter = {};
|
|
11
|
+
this.locks = {};
|
|
10
12
|
this.callLimit = 10;
|
|
11
13
|
if (this.ttl === 0) {
|
|
12
14
|
this.cache.clear();
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
getMutex(url) {
|
|
19
|
+
if (!(url in this.locks)) {
|
|
20
|
+
this.locks[url] = new Mutex();
|
|
21
|
+
}
|
|
22
|
+
return this.locks[url];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async limitDepth(url) {
|
|
17
26
|
/*
|
|
18
27
|
It is possible to create cyclic dependencies with external references
|
|
19
|
-
in JSON schema.
|
|
28
|
+
in JSON schema.
|
|
29
|
+
We try to mitigate this issue during cache pre-warming.
|
|
30
|
+
Ajv doesn't detect this when resolving external references,
|
|
20
31
|
so we keep a count of how many times we've called the same URL.
|
|
21
32
|
If we are calling the same URL over and over we've probably hit a circular
|
|
22
33
|
external reference and we need to break the loop.
|
|
23
34
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.callCounter
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
|
|
35
|
+
const mutex = this.getMutex(url);
|
|
36
|
+
|
|
37
|
+
await mutex.withLock(async () => {
|
|
38
|
+
if (url in this.callCounter) {
|
|
39
|
+
this.callCounter[url]++;
|
|
40
|
+
} else {
|
|
41
|
+
this.callCounter[url] = 1;
|
|
42
|
+
}
|
|
43
|
+
if (this.callCounter[url] > this.callLimit) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Called ${url} >${this.callLimit} times. Possible circular reference.`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
resetCounters() {
|
|
37
52
|
this.callCounter = {};
|
|
38
53
|
}
|
|
39
54
|
|
|
40
|
-
async fetch(url) {
|
|
41
|
-
this.limitDepth(url);
|
|
42
|
-
const cachedResponse = this.cache.
|
|
55
|
+
async fetch(url, persist = true) {
|
|
56
|
+
await this.limitDepth(url);
|
|
57
|
+
const cachedResponse = this.cache.get(url);
|
|
43
58
|
if (cachedResponse !== undefined) {
|
|
44
59
|
logger.debug(`Cache hit: using cached response from ${url}`);
|
|
45
60
|
return cachedResponse.body;
|
|
@@ -50,8 +65,10 @@ class Cache {
|
|
|
50
65
|
const resp = await got(url);
|
|
51
66
|
const parsedBody = parseSchema(resp.body, url);
|
|
52
67
|
if (this.ttl > 0) {
|
|
53
|
-
this.cache.
|
|
54
|
-
|
|
68
|
+
this.cache.set(url, { body: parsedBody });
|
|
69
|
+
if (persist) {
|
|
70
|
+
this.cache.save(true);
|
|
71
|
+
}
|
|
55
72
|
}
|
|
56
73
|
return parsedBody;
|
|
57
74
|
} catch (error) {
|
|
@@ -61,6 +78,14 @@ class Cache {
|
|
|
61
78
|
throw new Error(`Failed fetching ${url}`);
|
|
62
79
|
}
|
|
63
80
|
}
|
|
81
|
+
|
|
82
|
+
persist() {
|
|
83
|
+
this.cache.save(true);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get(key) {
|
|
87
|
+
return this.cache.get(key);
|
|
88
|
+
}
|
|
64
89
|
}
|
|
65
90
|
|
|
66
91
|
export { Cache };
|
package/src/catalogs.js
CHANGED
|
@@ -74,7 +74,7 @@ function getMultipleMatchesLogMessage(matches) {
|
|
|
74
74
|
.join("\n");
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
async function getMatchForFilename(catalogs, filename, cache) {
|
|
77
|
+
async function getMatchForFilename(catalogs, filename, logLevel, cache) {
|
|
78
78
|
for (const [i, rec] of catalogs.entries()) {
|
|
79
79
|
const catalogLocation = rec.location;
|
|
80
80
|
const catalog =
|
|
@@ -107,7 +107,7 @@ async function getMatchForFilename(catalogs, filename, cache) {
|
|
|
107
107
|
(matches.length === 1 && matches[0].versions == null) ||
|
|
108
108
|
(matches.length === 1 && Object.keys(matches[0].versions).length === 1)
|
|
109
109
|
) {
|
|
110
|
-
logger
|
|
110
|
+
logger[logLevel](`Found schema in ${catalogLocation} ...`);
|
|
111
111
|
return coerceMatch(matches[0]); // Exactly one match found. We're done.
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -122,7 +122,7 @@ async function getMatchForFilename(catalogs, filename, cache) {
|
|
|
122
122
|
) {
|
|
123
123
|
// We found >1 matches in the same catalog. This is always a hard error.
|
|
124
124
|
const matchesLog = getMultipleMatchesLogMessage(matches);
|
|
125
|
-
logger
|
|
125
|
+
logger[logLevel](
|
|
126
126
|
`Found multiple possible matches for ${filename}. Possible matches:\n\n${matchesLog}`,
|
|
127
127
|
);
|
|
128
128
|
throw new Error(
|
package/src/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import { getFromUrlOrFile } from "./io.js";
|
|
|
12
12
|
import logger from "./logger.js";
|
|
13
13
|
import { getDocumentLocation } from "./output-formatters.js";
|
|
14
14
|
import { parseFile } from "./parser.js";
|
|
15
|
+
import { prewarmSchemaCache } from "./cache-prewarm.js";
|
|
15
16
|
|
|
16
17
|
const EXIT = {
|
|
17
18
|
VALID: 0,
|
|
@@ -92,7 +93,7 @@ async function validateFile(filename, config, plugins, cache) {
|
|
|
92
93
|
const catalogs = getCatalogs(config);
|
|
93
94
|
const catalogMatch = config.schema
|
|
94
95
|
? {}
|
|
95
|
-
: await getMatchForFilename(catalogs, filename, cache);
|
|
96
|
+
: await getMatchForFilename(catalogs, filename, "info", cache);
|
|
96
97
|
schemaLocation = config.schema || catalogMatch.location;
|
|
97
98
|
schema = await getFromUrlOrFile(schemaLocation, cache);
|
|
98
99
|
logger.info(
|
|
@@ -144,7 +145,6 @@ async function validateFile(filename, config, plugins, cache) {
|
|
|
144
145
|
for (const plugin of plugins) {
|
|
145
146
|
const message = plugin.getSingleResultLogMessage(
|
|
146
147
|
result,
|
|
147
|
-
filename,
|
|
148
148
|
config.outputFormat,
|
|
149
149
|
);
|
|
150
150
|
if (message != null) {
|
|
@@ -183,6 +183,12 @@ function Validator() {
|
|
|
183
183
|
const ttl = secondsToMilliseconds(config.cacheTtl || 0);
|
|
184
184
|
const cache = new Cache(getFlatCache(ttl));
|
|
185
185
|
|
|
186
|
+
if (config.cachePrewarm && ttl > 5000) {
|
|
187
|
+
logger.info("Pre-warming the cache");
|
|
188
|
+
await prewarmSchemaCache(filenames, config, cache);
|
|
189
|
+
logger.debug("Cache pre-warming complete");
|
|
190
|
+
}
|
|
191
|
+
|
|
186
192
|
let results = [];
|
|
187
193
|
for (const filename of filenames) {
|
|
188
194
|
const fileResults = await validateFile(filename, config, plugins, cache);
|
package/src/glob.js
CHANGED
|
@@ -43,10 +43,6 @@ async function readIgnoreFiles(filenames) {
|
|
|
43
43
|
return content;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function getSeparator() {
|
|
47
|
-
return process.platform == "win32" ? "\\" : "/";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
46
|
async function getFiles(patterns, ignorePatternFiles) {
|
|
51
47
|
let filenames = [];
|
|
52
48
|
|
|
@@ -80,10 +76,6 @@ async function getFiles(patterns, ignorePatternFiles) {
|
|
|
80
76
|
throw new NotFound(`Could not find any files to validate`);
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
const sep = getSeparator();
|
|
84
|
-
filteredFilenames = filteredFilenames.map((fn) =>
|
|
85
|
-
!fn.startsWith(".." + sep) ? "." + sep + fn : fn,
|
|
86
|
-
);
|
|
87
79
|
return filteredFilenames;
|
|
88
80
|
}
|
|
89
81
|
|
package/src/parser.js
CHANGED
|
@@ -10,6 +10,9 @@ function parseFile(plugins, contents, filename, parser) {
|
|
|
10
10
|
const maybeDocuments = Array.isArray(parsedFile)
|
|
11
11
|
? parsedFile
|
|
12
12
|
: [parsedFile];
|
|
13
|
+
if (maybeDocuments.length === 0) {
|
|
14
|
+
throw new Error(`No documents to validate found in ${filename}`);
|
|
15
|
+
}
|
|
13
16
|
for (const doc of maybeDocuments) {
|
|
14
17
|
if (!(doc instanceof Document)) {
|
|
15
18
|
throw new Error(
|
|
@@ -8,7 +8,7 @@ class TextOutput extends BasePlugin {
|
|
|
8
8
|
return ["text"];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
getSingleResultLogMessage(result,
|
|
11
|
+
getSingleResultLogMessage(result, format) {
|
|
12
12
|
if (result.valid === false && format === "text") {
|
|
13
13
|
return formatErrors(getDocumentLocation(result), result.errors);
|
|
14
14
|
}
|
package/src/plugins.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import logger from "./logger.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Base class for all v8r plugins.
|
|
@@ -37,10 +36,7 @@ class BasePlugin {
|
|
|
37
36
|
* Document object or an array of Document objects.
|
|
38
37
|
*
|
|
39
38
|
* @param {string} contents - The unparsed file content.
|
|
40
|
-
* @param {string} fileLocation - The file path.
|
|
41
|
-
* normalised using dot-relative notation. This means relative paths in the
|
|
42
|
-
* current directory will be prefixed with `./` (or `.\` on Windows) even if
|
|
43
|
-
* this was not present in the input filename or pattern.
|
|
39
|
+
* @param {string} fileLocation - The file path.
|
|
44
40
|
* @param {string | undefined} parser - If the user has specified a parser to
|
|
45
41
|
* use for this file in a custom schema, this will be passed to
|
|
46
42
|
* `parseInputFile` in the `parser` param.
|
|
@@ -76,16 +72,12 @@ class BasePlugin {
|
|
|
76
72
|
*
|
|
77
73
|
* @param {ValidationResult} result - Result of attempting to validate this
|
|
78
74
|
* document.
|
|
79
|
-
* @param {string} fileLocation - The document file path. Filenames are
|
|
80
|
-
* resolved and normalised using dot-relative notation. This means relative
|
|
81
|
-
* paths in the current directory will be prefixed with `./` (or `.\` on
|
|
82
|
-
* Windows) even if this was not present in the input filename or pattern.
|
|
83
75
|
* @param {string} format - The user's requested output format as specified in
|
|
84
76
|
* the config file or via the `--output-format` command line argument.
|
|
85
77
|
* @returns {string | undefined} Log message
|
|
86
78
|
*/
|
|
87
79
|
// eslint-disable-next-line no-unused-vars
|
|
88
|
-
getSingleResultLogMessage(result,
|
|
80
|
+
getSingleResultLogMessage(result, format) {
|
|
89
81
|
return undefined;
|
|
90
82
|
}
|
|
91
83
|
|
|
@@ -124,11 +116,7 @@ class Document {
|
|
|
124
116
|
}
|
|
125
117
|
}
|
|
126
118
|
|
|
127
|
-
function
|
|
128
|
-
return Object.prototype.hasOwnProperty.call(plugin.prototype, prop);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function validatePlugin(plugin, warnings) {
|
|
119
|
+
function validatePlugin(plugin) {
|
|
132
120
|
if (
|
|
133
121
|
typeof plugin.name !== "string" ||
|
|
134
122
|
!plugin.name.startsWith("v8r-plugin-")
|
|
@@ -155,29 +143,6 @@ function validatePlugin(plugin, warnings) {
|
|
|
155
143
|
);
|
|
156
144
|
}
|
|
157
145
|
}
|
|
158
|
-
|
|
159
|
-
if (warnings === true) {
|
|
160
|
-
// https://github.com/chris48s/v8r/issues/500
|
|
161
|
-
if (hasProperty(plugin, "getSingleResultLogMessage")) {
|
|
162
|
-
logger.warning(
|
|
163
|
-
"In v8r version 5 the fileLocation argument of getSingleResultLogMessage will be removed.\n" +
|
|
164
|
-
" The signature will become getSingleResultLogMessage(result, format).\n" +
|
|
165
|
-
` ${plugin.name} will need to be updated`,
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// https://github.com/chris48s/v8r/issues/600
|
|
170
|
-
if (
|
|
171
|
-
hasProperty(plugin, "getSingleResultLogMessage") ||
|
|
172
|
-
hasProperty(plugin, "getAllResultsLogMessage") ||
|
|
173
|
-
hasProperty(plugin, "parseInputFile")
|
|
174
|
-
) {
|
|
175
|
-
logger.warning(
|
|
176
|
-
"Starting from v8r version 5 file paths will no longer be passed to plugins in dot-relative notation.\n" +
|
|
177
|
-
` ${plugin.name} may need to be updated`,
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
146
|
}
|
|
182
147
|
|
|
183
148
|
function resolveUserPlugins(userPlugins) {
|
|
@@ -193,19 +158,19 @@ function resolveUserPlugins(userPlugins) {
|
|
|
193
158
|
return plugins;
|
|
194
159
|
}
|
|
195
160
|
|
|
196
|
-
async function loadPlugins(plugins
|
|
161
|
+
async function loadPlugins(plugins) {
|
|
197
162
|
let loadedPlugins = [];
|
|
198
163
|
for (const plugin of plugins) {
|
|
199
164
|
loadedPlugins.push(await import(plugin));
|
|
200
165
|
}
|
|
201
166
|
loadedPlugins = loadedPlugins.map((plugin) => plugin.default);
|
|
202
|
-
loadedPlugins.forEach((plugin) => validatePlugin(plugin
|
|
167
|
+
loadedPlugins.forEach((plugin) => validatePlugin(plugin));
|
|
203
168
|
loadedPlugins = loadedPlugins.map((plugin) => new plugin());
|
|
204
169
|
return loadedPlugins;
|
|
205
170
|
}
|
|
206
171
|
|
|
207
172
|
async function loadAllPlugins(userPlugins) {
|
|
208
|
-
const loadedUserPlugins = await loadPlugins(userPlugins
|
|
173
|
+
const loadedUserPlugins = await loadPlugins(userPlugins);
|
|
209
174
|
|
|
210
175
|
const corePlugins = [
|
|
211
176
|
"./plugins/parser-json.js",
|
|
@@ -215,7 +180,7 @@ async function loadAllPlugins(userPlugins) {
|
|
|
215
180
|
"./plugins/output-text.js",
|
|
216
181
|
"./plugins/output-json.js",
|
|
217
182
|
];
|
|
218
|
-
const loadedCorePlugins = await loadPlugins(corePlugins
|
|
183
|
+
const loadedCorePlugins = await loadPlugins(corePlugins);
|
|
219
184
|
|
|
220
185
|
return {
|
|
221
186
|
allLoadedPlugins: loadedUserPlugins.concat(loadedCorePlugins),
|
|
@@ -227,10 +192,6 @@ async function loadAllPlugins(userPlugins) {
|
|
|
227
192
|
/**
|
|
228
193
|
* @typedef {object} ValidationResult
|
|
229
194
|
* @property {string} fileLocation - Path of the document that was validated.
|
|
230
|
-
* Filenames are resolved and normalised using dot-relative notation. This
|
|
231
|
-
* means relative paths in the current directory will be prefixed with `./`
|
|
232
|
-
* (or `.\` on Windows) even if this was not present in the input filename or
|
|
233
|
-
* pattern.
|
|
234
195
|
* @property {number | null} documentIndex - Some file formats allow multiple
|
|
235
196
|
* documents to be embedded in one file (e.g:
|
|
236
197
|
* [yaml](https://www.yaml.info/learn/document.html)). In these cases,
|
package/src/test-helpers.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { clearCacheById } from "flat-cache";
|
|
2
1
|
import logger from "./logger.js";
|
|
3
2
|
|
|
4
3
|
const origWriteOut = logger.writeOut;
|
|
@@ -7,7 +6,6 @@ const testCacheName = process.env.V8R_CACHE_NAME;
|
|
|
7
6
|
const env = process.env;
|
|
8
7
|
|
|
9
8
|
function setUp() {
|
|
10
|
-
clearCacheById(testCacheName);
|
|
11
9
|
logger.resetStdout();
|
|
12
10
|
logger.resetStderr();
|
|
13
11
|
logger.writeOut = function () {};
|
|
@@ -16,7 +14,6 @@ function setUp() {
|
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
function tearDown() {
|
|
19
|
-
clearCacheById(testCacheName);
|
|
20
17
|
logger.resetStdout();
|
|
21
18
|
logger.resetStderr();
|
|
22
19
|
logger.writeOut = origWriteOut;
|