Comparing version 4.0.1 to 4.1.0
# Changelog | ||
## 📦 [4.1.0](https://www.npmjs.com/package/v8r/v/4.1.0) - 2024-08-25 | ||
* v8r can now parse and validate files that contain multiple yaml documents | ||
More info: https://chris48s.github.io/v8r/usage-examples/#files-containing-multiple-documents | ||
* The `parseInputFile()` plugin hook may now tionally return an array of `Document` objects | ||
* The `ValidationResult` object now contains a `documentIndex` property. | ||
This identifies the document when a multi-doc file has been validated. | ||
## 📦 [4.0.1](https://www.npmjs.com/package/v8r/v/4.0.1) - 2024-08-19 | ||
@@ -4,0 +12,0 @@ |
{ | ||
"name": "v8r", | ||
"version": "4.0.1", | ||
"version": "4.1.0", | ||
"description": "A command-line JSON, YAML and TOML validator that's on your wavelength", | ||
@@ -5,0 +5,0 @@ "scripts": { |
129
src/cli.js
@@ -13,2 +13,3 @@ import flatCache from "flat-cache"; | ||
import logger from "./logger.js"; | ||
import { getDocumentLocation } from "./output-formatters.js"; | ||
import { parseFile } from "./parser.js"; | ||
@@ -37,7 +38,16 @@ | ||
async function validateFile(filename, config, plugins, cache) { | ||
logger.info(`Processing ${filename}`); | ||
async function validateDocument( | ||
fileLocation, | ||
documentIndex, | ||
document, | ||
schemaLocation, | ||
schema, | ||
strictMode, | ||
cache, | ||
resolver, | ||
) { | ||
let result = { | ||
fileLocation: filename, | ||
schemaLocation: null, | ||
fileLocation, | ||
documentIndex, | ||
schemaLocation, | ||
valid: null, | ||
@@ -48,2 +58,34 @@ errors: [], | ||
try { | ||
const { valid, errors } = await validate( | ||
document, | ||
schema, | ||
strictMode, | ||
cache, | ||
resolver, | ||
); | ||
result.valid = valid; | ||
result.errors = errors; | ||
const documentLocation = getDocumentLocation(result); | ||
if (valid) { | ||
logger.success(`${documentLocation} is valid\n`); | ||
} else { | ||
logger.error(`${documentLocation} is invalid\n`); | ||
} | ||
result.code = valid ? EXIT.VALID : EXIT.INVALID; | ||
return result; | ||
} catch (e) { | ||
logger.error(`${e.message}\n`); | ||
result.code = EXIT.ERROR; | ||
return result; | ||
} | ||
} | ||
async function validateFile(filename, config, plugins, cache) { | ||
logger.info(`Processing ${filename}`); | ||
let schema, schemaLocation, documents, strictMode, resolver; | ||
try { | ||
const catalogs = getCatalogs(config); | ||
@@ -53,5 +95,4 @@ const catalogMatch = config.schema | ||
: await getMatchForFilename(catalogs, filename, cache); | ||
const schemaLocation = config.schema || catalogMatch.location; | ||
result.schemaLocation = schemaLocation; | ||
const schema = await getFromUrlOrFile(schemaLocation, cache); | ||
schemaLocation = config.schema || catalogMatch.location; | ||
schema = await getFromUrlOrFile(schemaLocation, cache); | ||
logger.info( | ||
@@ -61,3 +102,3 @@ `Validating ${filename} against schema from ${schemaLocation} ...`, | ||
const data = parseFile( | ||
documents = parseFile( | ||
plugins, | ||
@@ -69,9 +110,29 @@ await fs.promises.readFile(filename, "utf8"), | ||
const strictMode = config.verbose >= 2 ? "log" : false; | ||
const resolver = isUrl(schemaLocation) | ||
strictMode = config.verbose >= 2 ? "log" : false; | ||
resolver = isUrl(schemaLocation) | ||
? (location) => getFromUrlOrFile(location, cache) | ||
: (location) => | ||
getFromUrlOrFile(location, cache, path.dirname(schemaLocation)); | ||
const { valid, errors } = await validate( | ||
data, | ||
} catch (e) { | ||
logger.error(`${e.message}\n`); | ||
return [ | ||
{ | ||
fileLocation: filename, | ||
documentIndex: null, | ||
schemaLocation: schemaLocation || null, | ||
valid: null, | ||
errors: [], | ||
code: EXIT.ERROR, | ||
}, | ||
]; | ||
} | ||
let results = []; | ||
for (let i = 0; i < documents.length; i++) { | ||
const documentIndex = documents.length === 1 ? null : i; | ||
const result = await validateDocument( | ||
filename, | ||
documentIndex, | ||
documents[i], | ||
schemaLocation, | ||
schema, | ||
@@ -82,17 +143,18 @@ strictMode, | ||
); | ||
result.valid = valid; | ||
result.errors = errors; | ||
if (valid) { | ||
logger.success(`${filename} is valid\n`); | ||
} else { | ||
logger.error(`${filename} is invalid\n`); | ||
results.push(result); | ||
for (const plugin of plugins) { | ||
const message = plugin.getSingleResultLogMessage( | ||
result, | ||
filename, | ||
config.format, | ||
); | ||
if (message != null) { | ||
logger.log(message); | ||
break; | ||
} | ||
} | ||
result.code = valid ? EXIT.VALID : EXIT.INVALID; | ||
return result; | ||
} catch (e) { | ||
logger.error(`${e.message}\n`); | ||
result.code = EXIT.ERROR; | ||
return result; | ||
} | ||
return results; | ||
} | ||
@@ -132,17 +194,4 @@ | ||
for (const filename of filenames) { | ||
const result = await validateFile(filename, config, plugins, cache); | ||
results.push(result); | ||
for (const plugin of plugins) { | ||
const message = plugin.getSingleResultLogMessage( | ||
result, | ||
filename, | ||
config.format, | ||
); | ||
if (message != null) { | ||
logger.log(message); | ||
break; | ||
} | ||
} | ||
const fileResults = await validateFile(filename, config, plugins, cache); | ||
results = results.concat(fileResults); | ||
cache.resetCounters(); | ||
@@ -149,0 +198,0 @@ } |
import Ajv from "ajv"; | ||
function formatErrors(filename, errors) { | ||
function getDocumentLocation(result) { | ||
if (result.documentIndex == null) { | ||
return result.fileLocation; | ||
} | ||
return `${result.fileLocation}[${result.documentIndex}]`; | ||
} | ||
function formatErrors(location, errors) { | ||
const ajv = new Ajv(); | ||
@@ -8,3 +15,3 @@ return ( | ||
separator: "\n", | ||
dataVar: filename + "#", | ||
dataVar: location + "#", | ||
}) + "\n" | ||
@@ -14,2 +21,2 @@ ); | ||
export { formatErrors }; | ||
export { formatErrors, getDocumentLocation }; |
@@ -7,10 +7,16 @@ import path from "path"; | ||
for (const plugin of plugins) { | ||
const result = plugin.parseInputFile(contents, filename, parser); | ||
if (result != null) { | ||
if (!(result instanceof Document)) { | ||
throw new Error( | ||
`Plugin ${plugin.constructor.name} returned an unexpected type from parseInputFile hook. Expected Document, got ${typeof result}`, | ||
); | ||
const parsedFile = plugin.parseInputFile(contents, filename, parser); | ||
if (parsedFile != null) { | ||
const maybeDocuments = Array.isArray(parsedFile) | ||
? parsedFile | ||
: [parsedFile]; | ||
for (const doc of maybeDocuments) { | ||
if (!(doc instanceof Document)) { | ||
throw new Error( | ||
`Plugin ${plugin.constructor.name} returned an unexpected type from parseInputFile hook. Expected Document, got ${typeof doc}`, | ||
); | ||
} | ||
} | ||
return result.document; | ||
return maybeDocuments.map((md) => md.document); | ||
} | ||
@@ -17,0 +23,0 @@ } |
@@ -35,3 +35,4 @@ import path from "path"; | ||
* `parseInputFile` returns undefined, v8r will move on to the next plugin in | ||
* the stack. | ||
* the stack. The result of successfully parsing a file can either be a single | ||
* Document object or an array of Document objects. | ||
* | ||
@@ -47,3 +48,3 @@ * @param {string} contents - The unparsed file content. | ||
* `parseInputFile` in the `parser` param. | ||
* @returns {Document | undefined} Parsed file contents | ||
* @returns {Document | Document[] | undefined} Parsed file contents | ||
*/ | ||
@@ -210,2 +211,8 @@ // eslint-disable-next-line no-unused-vars | ||
* filename or pattern. | ||
* @property {number | null} documentIndex - Some file formats allow multiple | ||
* documents to be embedded in one file (e.g: | ||
* [yaml](https://www.yaml.info/learn/document.html)). In these cases, | ||
* `documentIndex` identifies is used to identify the sub document within the | ||
* file. `documentIndex` will be `null` when there is a one-to-one | ||
* relationship between file and document. | ||
* @property {string | null} schemaLocation - Location of the schema used to | ||
@@ -212,0 +219,0 @@ * validate this file if one could be found. `null` if no schema was found. |
import { BasePlugin } from "../plugins.js"; | ||
import { formatErrors } from "../output-formatters.js"; | ||
import { formatErrors, getDocumentLocation } from "../output-formatters.js"; | ||
@@ -13,3 +13,3 @@ class TextOutput extends BasePlugin { | ||
if (result.valid === false && format === "text") { | ||
return formatErrors(fileLocation, result.errors); | ||
return formatErrors(getDocumentLocation(result), result.errors); | ||
} | ||
@@ -16,0 +16,0 @@ } |
@@ -13,6 +13,6 @@ import yaml from "js-yaml"; | ||
if (parser === "yaml") { | ||
return new Document(yaml.load(contents)); | ||
return yaml.loadAll(contents).map((doc) => new Document(doc)); | ||
} else if (parser == null) { | ||
if (fileLocation.endsWith(".yaml") || fileLocation.endsWith(".yml")) { | ||
return new Document(yaml.load(contents)); | ||
return yaml.loadAll(contents).map((doc) => new Document(doc)); | ||
} | ||
@@ -19,0 +19,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
54053
1339