181 lines
4.9 KiB
JavaScript
181 lines
4.9 KiB
JavaScript
const gulp = require('gulp');
|
|
const { taskArgs } = require('./_util');
|
|
const through2 = require('through2');
|
|
const yaml = require('js-yaml');
|
|
const Ajv = require('ajv');
|
|
const addFormats = require('ajv-formats');
|
|
const addErrors = require('ajv-errors');
|
|
|
|
const defaultGlobs = ['data/registry/*.yml'];
|
|
|
|
let numFilesProcessed = 0,
|
|
numFilesWithIssues = 0;
|
|
let debugFlag = false;
|
|
|
|
const schema = require('../data/registry-schema.json');
|
|
delete schema['cspell:ignore'];
|
|
const ajv = new Ajv({
|
|
allErrors: true,
|
|
});
|
|
addFormats(ajv);
|
|
addErrors(ajv);
|
|
const validate = ajv.compile(schema);
|
|
|
|
function logFiles(debug) {
|
|
return through2.obj(function (file, enc, cb) {
|
|
if (debug) {
|
|
console.log('Processing file:', file.path);
|
|
}
|
|
cb(null, file);
|
|
});
|
|
}
|
|
|
|
function createSourceMap(yamlText, yamlData) {
|
|
const lines = yamlText.split('\n');
|
|
const sourceMap = {};
|
|
|
|
function traverse(node, path = '', startLine = 0) {
|
|
if (Array.isArray(node)) {
|
|
node.forEach((item, index) => {
|
|
const newPath = `${path}/${index}`;
|
|
const line = findArrayElementLine(lines, path, index, startLine);
|
|
if (line !== -1) {
|
|
sourceMap['/' + newPath] = line + 1; // line numbers are 1-based
|
|
traverse(item, newPath, line);
|
|
}
|
|
});
|
|
} else if (typeof node === 'object' && node !== null) {
|
|
for (const key in node) {
|
|
const newPath = path ? `${path}/${key}` : key;
|
|
const line = findKeyLine(lines, key, startLine);
|
|
if (line !== -1) {
|
|
sourceMap['/' + newPath] = line + 1; // line numbers are 1-based
|
|
traverse(node[key], newPath, line + 1);
|
|
}
|
|
}
|
|
} else if (node === null) {
|
|
sourceMap['/' + path] = startLine;
|
|
}
|
|
}
|
|
|
|
function findKeyLine(lines, key, startLine) {
|
|
const regex = new RegExp(`^\\s*-?\\s*${key}:`);
|
|
for (let i = startLine; i < lines.length; i++) {
|
|
if (regex.test(lines[i])) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function findArrayElementLine(lines, path, index, startLine) {
|
|
// const parentPath = path ? path.split('.').join('\\.') : '';
|
|
const regex = new RegExp(`^\\s*-`);
|
|
let currentIndex = -1;
|
|
for (let i = startLine; i < lines.length; i++) {
|
|
const trimmedLine = lines[i].trim();
|
|
if (regex.test(trimmedLine)) {
|
|
currentIndex++;
|
|
if (currentIndex === index) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
traverse(yamlData);
|
|
return sourceMap;
|
|
}
|
|
|
|
function getLineNumber(sourceMap, yamlPath) {
|
|
return sourceMap[yamlPath] || null;
|
|
}
|
|
|
|
function validateRegistryEntry(file, enc, cb) {
|
|
const registryEntry = yaml.load(file.contents.toString(), {
|
|
schema: yaml.JSON_SCHEMA,
|
|
});
|
|
|
|
const sourceMap = createSourceMap(file.contents.toString(), registryEntry);
|
|
|
|
const valid = validate(registryEntry);
|
|
if (!valid) {
|
|
// some validation issues let to warning/notices not errors, so we need to check for those
|
|
let hasErrors = false;
|
|
for (const error of validate.errors) {
|
|
const lineNumber = getLineNumber(sourceMap, error.instancePath);
|
|
|
|
if (!lineNumber) {
|
|
console.log(registryEntry);
|
|
console.log(yaml.load(file.contents.toString()));
|
|
console.log(sourceMap);
|
|
console.log(error.instancePath);
|
|
}
|
|
|
|
let logLevel = 'error';
|
|
|
|
hasErrors = true;
|
|
|
|
if (process.env.GITHUB_ACTIONS) {
|
|
console.log(
|
|
`::${logLevel} file=${file.path},line=${lineNumber},endLine=${lineNumber},title=Registry Schema Validation::${error.message}`,
|
|
);
|
|
} else if (debugFlag) {
|
|
console.log(error);
|
|
console.error(
|
|
`${logLevel} in ${file.path}:${lineNumber}: ${error.message}`,
|
|
);
|
|
}
|
|
}
|
|
if (hasErrors) {
|
|
numFilesWithIssues++;
|
|
}
|
|
}
|
|
numFilesProcessed++;
|
|
cb(null, file);
|
|
}
|
|
|
|
function validateRegistry() {
|
|
const argv = taskArgs().options({
|
|
glob: {
|
|
alias: 'g',
|
|
type: 'array',
|
|
description:
|
|
'Globs of files to run through json schema validation. List flag more than once for multiple values.',
|
|
default: defaultGlobs,
|
|
},
|
|
debug: {
|
|
type: 'boolean',
|
|
description: 'Output debugging information.',
|
|
default: false,
|
|
},
|
|
}).argv;
|
|
|
|
if (argv.info) {
|
|
// Info about options was already displayed by yargs.help().
|
|
return Promise.resolve();
|
|
}
|
|
|
|
const globs = argv.glob;
|
|
if (argv.debug) {
|
|
console.log('Globs being used:', globs);
|
|
}
|
|
debugFlag = argv.debug;
|
|
|
|
return gulp
|
|
.src(globs, { followSymlinks: false })
|
|
.pipe(logFiles(argv.debug))
|
|
.pipe(through2.obj(validateRegistryEntry))
|
|
.on('end', () => {
|
|
const fileOrFiles = 'file' + (numFilesProcessed == 1 ? '' : 's');
|
|
const msg = `Processed ${numFilesProcessed} ${fileOrFiles}, ${numFilesWithIssues} had issues.`;
|
|
if (numFilesWithIssues > 0) {
|
|
throw new Error(msg);
|
|
} else {
|
|
console.log(msg);
|
|
}
|
|
});
|
|
}
|
|
|
|
gulp.task('validate-registry', validateRegistry);
|