v8r 0.13.0 → 0.14.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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 📦 [0.14.0](https://www.npmjs.com/package/v8r/v/0.14.0) - 2023-01-28
4
+
5
+ * Throw an error if multiple versions of a schema are found in the catalog,
6
+ instead of assuming the latest version
7
+
8
+ ## 📦 [0.13.1](https://www.npmjs.com/package/v8r/v/0.13.1) - 2022-12-10
9
+
10
+ * Resolve external `$ref`s in local schemas
11
+
3
12
  ## 📦 [0.13.0](https://www.npmjs.com/package/v8r/v/0.13.0) - 2022-06-11
4
13
 
5
14
  * Overhaul of CLI output/machine-readable output. Validation results are sent to stdout. Log messages are now sent to stderr only. Pass `--format [text|json] (default: text)` to specify what is sent to stdout.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  ![license](https://raw.githubusercontent.com/chris48s/v8r/badges/.badges/main/package-license.svg)
7
7
  ![node](https://raw.githubusercontent.com/chris48s/v8r/badges/.badges/main/package-node-version.svg)
8
8
 
9
- A command-line JSON and YAML validator that's on your wavelength.
9
+ v8r is a command-line JSON and YAML validator that uses [Schema Store](https://www.schemastore.org/) to detect a suitable schema for your input files based on the filename.
10
10
 
11
11
  ## Getting Started
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "v8r",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "A command-line JSON and YAML validator that's on your wavelength",
5
5
  "scripts": {
6
6
  "test": "V8R_CACHE_NAME=v8r-test c8 --reporter=text mocha \"src/**/*.spec.js\"",
@@ -15,6 +15,11 @@
15
15
  },
16
16
  "main": "src/index.js",
17
17
  "exports": "./src/index.js",
18
+ "files": [
19
+ "src/**/!(*.spec).js",
20
+ "config-schema.json",
21
+ "CHANGELOG.md"
22
+ ],
18
23
  "repository": {
19
24
  "type": "git",
20
25
  "url": "git+https://github.com/chris48s/v8r.git"
@@ -27,7 +32,7 @@
27
32
  "ajv-draft-04": "^1.0.0",
28
33
  "ajv-formats": "^2.1.1",
29
34
  "chalk": "^5.0.0",
30
- "cosmiconfig": "^7.0.1",
35
+ "cosmiconfig": "^8.0.0",
31
36
  "decamelize": "^6.0.0",
32
37
  "flat-cache": "^3.0.4",
33
38
  "glob": "^8.0.1",
@@ -35,7 +40,7 @@
35
40
  "is-url": "^1.2.4",
36
41
  "js-yaml": "^4.0.0",
37
42
  "json5": "^2.2.0",
38
- "minimatch": "^5.0.1",
43
+ "minimatch": "^6.1.6",
39
44
  "yargs": "^17.0.1"
40
45
  },
41
46
  "devDependencies": {
package/src/ajv.js CHANGED
@@ -9,8 +9,12 @@ import Ajv2019 from "ajv/dist/2019.js";
9
9
  import Ajv2020 from "ajv/dist/2020.js";
10
10
  import addFormats from "ajv-formats";
11
11
 
12
- function _ajvFactory(schema, strictMode, cache) {
13
- const resolver = (url) => cache.fetch(url);
12
+ function _ajvFactory(
13
+ schema,
14
+ strictMode,
15
+ cache,
16
+ resolver = (url) => cache.fetch(url)
17
+ ) {
14
18
  const opts = { allErrors: true, loadSchema: resolver, strict: strictMode };
15
19
 
16
20
  if (
@@ -53,8 +57,8 @@ function _ajvFactory(schema, strictMode, cache) {
53
57
  */
54
58
  }
55
59
 
56
- async function validate(data, schema, strictMode, cache) {
57
- const ajv = _ajvFactory(schema, strictMode, cache);
60
+ async function validate(data, schema, strictMode, cache, resolver) {
61
+ const ajv = _ajvFactory(schema, strictMode, cache, resolver);
58
62
  addFormats(ajv);
59
63
  const validateFn = await ajv.compileAsync(schema);
60
64
  const valid = validateFn(data);
package/src/catalogs.js CHANGED
@@ -39,6 +39,41 @@ function getCatalogs(config) {
39
39
  return catalogs;
40
40
  }
41
41
 
42
+ function getMatchLogMessage(match) {
43
+ let outStr = "";
44
+ outStr += ` ${match.name}\n`;
45
+ if (match.description) {
46
+ outStr += ` ${match.description}\n`;
47
+ }
48
+ outStr += ` ${match.url || match.location}\n`;
49
+ return outStr;
50
+ }
51
+
52
+ function getVersionLogMessage(match, versionId, versionSchemaUrl) {
53
+ let outStr = "";
54
+ outStr += ` ${match.name} (${versionId})\n`;
55
+ if (match.description) {
56
+ outStr += ` ${match.description}\n`;
57
+ }
58
+ outStr += ` ${versionSchemaUrl}\n`;
59
+ return outStr;
60
+ }
61
+
62
+ function getMultipleMatchesLogMessage(matches) {
63
+ return matches
64
+ .map(function (match) {
65
+ if (Object.keys(match.versions || {}).length > 1) {
66
+ return Object.entries(match.versions)
67
+ .map(function ([versionId, versionSchemaUrl]) {
68
+ return getVersionLogMessage(match, versionId, versionSchemaUrl);
69
+ })
70
+ .join("\n");
71
+ }
72
+ return getMatchLogMessage(match);
73
+ })
74
+ .join("\n");
75
+ }
76
+
42
77
  async function getMatchForFilename(catalogs, filename, cache) {
43
78
  for (const [i, rec] of catalogs.entries()) {
44
79
  const catalogLocation = rec.location;
@@ -67,32 +102,36 @@ async function getMatchForFilename(catalogs, filename, cache) {
67
102
  const { schemas } = catalog;
68
103
  const matches = getSchemaMatchesForFilename(schemas, filename);
69
104
  logger.debug(`Searching for schema in ${catalogLocation} ...`);
70
- if (matches.length === 1) {
105
+
106
+ if (
107
+ (matches.length === 1 && matches[0].versions == null) ||
108
+ (matches.length === 1 && Object.keys(matches[0].versions).length === 1)
109
+ ) {
71
110
  logger.info(`Found schema in ${catalogLocation} ...`);
72
- return coerceMatch(matches[0]); // Match found. We're done.
111
+ return coerceMatch(matches[0]); // Exactly one match found. We're done.
73
112
  }
113
+
74
114
  if (matches.length === 0 && i < catalogs.length - 1) {
75
- continue; // No match found. Try the next catalog in the array.
115
+ continue; // No matches found. Try the next catalog in the array.
76
116
  }
77
- if (matches.length > 1) {
117
+
118
+ if (
119
+ matches.length > 1 ||
120
+ (matches.length === 1 &&
121
+ Object.keys(matches[0].versions || {}).length > 1)
122
+ ) {
78
123
  // We found >1 matches in the same catalog. This is always a hard error.
79
- const matchesLog = matches
80
- .map(function (match) {
81
- let outStr = "";
82
- outStr += ` ${match.name}\n`;
83
- if (match.description) {
84
- outStr += ` ${match.description}\n`;
85
- }
86
- outStr += ` ${match.url || match.location}\n`;
87
- return outStr;
88
- })
89
- .join("\n");
124
+ const matchesLog = getMultipleMatchesLogMessage(matches);
90
125
  logger.info(
91
- `Found multiple possible schemas for ${filename}. Possible matches:\n${matchesLog}`
126
+ `Found multiple possible matches for ${filename}. Possible matches:\n\n${matchesLog}`
127
+ );
128
+ throw new Error(
129
+ `Found multiple possible schemas to validate ${filename}`
92
130
  );
93
131
  }
94
- // Either we found >1 matches in the same catalog or we found 0 matches
95
- // in the last catalog and there are no more catalogs left to try.
132
+
133
+ // We found 0 matches in the last catalog
134
+ // and there are no more catalogs left to try
96
135
  throw new Error(`Could not find a schema to validate ${filename}`);
97
136
  }
98
137
  }
package/src/cli.js CHANGED
@@ -2,6 +2,7 @@ import flatCache from "flat-cache";
2
2
  import fs from "fs";
3
3
  import os from "os";
4
4
  import path from "path";
5
+ import isUrl from "is-url";
5
6
  import { validate } from "./ajv.js";
6
7
  import { Cache } from "./cache.js";
7
8
  import { getCatalogs, getMatchForFilename } from "./catalogs.js";
@@ -60,7 +61,17 @@ async function validateFile(filename, config, cache) {
60
61
  );
61
62
 
62
63
  const strictMode = config.verbose >= 2 ? "log" : false;
63
- const { valid, errors } = await validate(data, schema, strictMode, cache);
64
+ const resolver = isUrl(schemaLocation)
65
+ ? (location) => getFromUrlOrFile(location, cache)
66
+ : (location) =>
67
+ getFromUrlOrFile(location, cache, path.dirname(schemaLocation));
68
+ const { valid, errors } = await validate(
69
+ data,
70
+ schema,
71
+ strictMode,
72
+ cache,
73
+ resolver
74
+ );
64
75
  result.valid = valid;
65
76
  result.errors = errors;
66
77
  if (valid) {
package/src/io.js CHANGED
@@ -1,10 +1,18 @@
1
1
  import fs from "fs";
2
+ import path from "path";
2
3
  import isUrl from "is-url";
3
4
 
4
- async function getFromUrlOrFile(location, cache) {
5
- return isUrl(location)
6
- ? await cache.fetch(location)
7
- : JSON.parse(await fs.promises.readFile(location, "utf8"));
5
+ async function getFromUrlOrFile(location, cache, base = null) {
6
+ if (isUrl(location)) {
7
+ return await cache.fetch(location);
8
+ } else {
9
+ if (base != null) {
10
+ return JSON.parse(
11
+ await fs.promises.readFile(path.join(base, location), "utf8")
12
+ );
13
+ }
14
+ }
15
+ return JSON.parse(await fs.promises.readFile(location, "utf8"));
8
16
  }
9
17
 
10
18
  export { getFromUrlOrFile };
@@ -0,0 +1,58 @@
1
+ import chai from "chai";
2
+ import chaiAsPromised from "chai-as-promised";
3
+ import flatCache from "flat-cache";
4
+ import logger from "./logger.js";
5
+
6
+ chai.use(chaiAsPromised);
7
+
8
+ const origWriteOut = logger.writeOut;
9
+ const origWriteErr = logger.writeErr;
10
+ const testCacheName = process.env.V8R_CACHE_NAME;
11
+
12
+ function setUp() {
13
+ flatCache.clearCacheById(testCacheName);
14
+ logger.resetStdout();
15
+ logger.resetStderr();
16
+ logger.writeOut = function () {};
17
+ logger.writeErr = function () {};
18
+ }
19
+
20
+ function tearDown() {
21
+ flatCache.clearCacheById(testCacheName);
22
+ logger.resetStdout();
23
+ logger.resetStderr();
24
+ logger.writeOut = origWriteOut;
25
+ logger.writeErr = origWriteErr;
26
+ }
27
+
28
+ function isString(el) {
29
+ return typeof el === "string" || el instanceof String;
30
+ }
31
+
32
+ function logContainsSuccess(expectedString, expectedCount = 1) {
33
+ const counter = (count, el) =>
34
+ count + (isString(el) && el.includes("✔") && el.includes(expectedString));
35
+ return logger.stderr.reduce(counter, 0) === expectedCount;
36
+ }
37
+
38
+ function logContainsInfo(expectedString, expectedCount = 1) {
39
+ const counter = (count, el) =>
40
+ count + (isString(el) && el.includes("ℹ") && el.includes(expectedString));
41
+ return logger.stderr.reduce(counter, 0) === expectedCount;
42
+ }
43
+
44
+ function logContainsError(expectedString, expectedCount = 1) {
45
+ const counter = (count, el) =>
46
+ count + (isString(el) && el.includes("✖") && el.includes(expectedString));
47
+ return logger.stderr.reduce(counter, 0) === expectedCount;
48
+ }
49
+
50
+ export {
51
+ chai,
52
+ testCacheName,
53
+ setUp,
54
+ tearDown,
55
+ logContainsSuccess,
56
+ logContainsInfo,
57
+ logContainsError,
58
+ };