UNPKG

v8r

Version:

A command-line JSON, YAML and TOML validator that's on your wavelength

159 lines (142 loc) 4.58 kB
import path from "node:path"; import { minimatch } from "minimatch"; import { validate } from "./ajv.js"; import { getFromUrlOrFile } from "./io.js"; import logger from "./logger.js"; const SCHEMASTORE_CATALOG_URL = "https://www.schemastore.org/api/json/catalog.json"; const SCHEMASTORE_CATALOG_SCHEMA_URL = "https://json.schemastore.org/schema-catalog.json"; function coerceMatch(inMatch) { const outMatch = {}; outMatch.location = inMatch.url || inMatch.location; for (const [key, value] of Object.entries(inMatch)) { if (!["location", "url"].includes(key)) { outMatch[key] = value; } } return outMatch; } function getCatalogs(config) { let catalogs = []; if (config.customCatalog) { catalogs.push({ location: config.configFileRelativePath, catalog: config.customCatalog, }); } if (config.catalogs) { catalogs = catalogs.concat( config.catalogs.map(function (loc) { return { location: loc }; }), ); } catalogs.push({ location: SCHEMASTORE_CATALOG_URL }); return catalogs; } function getMatchLogMessage(match) { let outStr = ""; outStr += ` ${match.name}\n`; if (match.description) { outStr += ` ${match.description}\n`; } outStr += ` ${match.url || match.location}\n`; return outStr; } function getVersionLogMessage(match, versionId, versionSchemaUrl) { let outStr = ""; outStr += ` ${match.name} (${versionId})\n`; if (match.description) { outStr += ` ${match.description}\n`; } outStr += ` ${versionSchemaUrl}\n`; return outStr; } function getMultipleMatchesLogMessage(matches) { return matches .map(function (match) { if (Object.keys(match.versions || {}).length > 1) { return Object.entries(match.versions) .map(function ([versionId, versionSchemaUrl]) { return getVersionLogMessage(match, versionId, versionSchemaUrl); }) .join("\n"); } return getMatchLogMessage(match); }) .join("\n"); } async function getMatchForFilename(catalogs, filename, cache) { for (const [i, rec] of catalogs.entries()) { const catalogLocation = rec.location; const catalog = rec.catalog || (await getFromUrlOrFile(catalogLocation, cache)); if (!rec.catalog) { const catalogSchema = await getFromUrlOrFile( SCHEMASTORE_CATALOG_SCHEMA_URL, cache, ); // Validate the catalog const strictMode = false; const { valid } = await validate( catalog, catalogSchema, strictMode, cache, ); if (!valid || catalog.schemas === undefined) { throw new Error(`Malformed catalog at ${catalogLocation}`); } } const { schemas } = catalog; const matches = getSchemaMatchesForFilename(schemas, filename); logger.debug(`Searching for schema in ${catalogLocation} ...`); if ( (matches.length === 1 && matches[0].versions == null) || (matches.length === 1 && Object.keys(matches[0].versions).length === 1) ) { logger.info(`Found schema in ${catalogLocation} ...`); return coerceMatch(matches[0]); // Exactly one match found. We're done. } if (matches.length === 0 && i < catalogs.length - 1) { continue; // No matches found. Try the next catalog in the array. } if ( matches.length > 1 || (matches.length === 1 && Object.keys(matches[0].versions || {}).length > 1) ) { // We found >1 matches in the same catalog. This is always a hard error. const matchesLog = getMultipleMatchesLogMessage(matches); logger.info( `Found multiple possible matches for ${filename}. Possible matches:\n\n${matchesLog}`, ); throw new Error( `Found multiple possible schemas to validate ${filename}`, ); } // We found 0 matches in the last catalog // and there are no more catalogs left to try throw new Error(`Could not find a schema to validate ${filename}`); } } function getSchemaMatchesForFilename(schemas, filename) { const matches = []; schemas.forEach(function (schema) { if ("fileMatch" in schema) { if (schema.fileMatch.includes(path.basename(filename))) { matches.push(schema); return; } for (const glob of schema.fileMatch) { if (minimatch(path.normalize(filename), glob, { dot: true })) { matches.push(schema); break; } } } }); return matches; } export { getCatalogs, getMatchForFilename, getSchemaMatchesForFilename };