yaml-validator
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -34,3 +34,3 @@ #!/usr/bin/env node | ||
const optsParser = optionator({ | ||
prepend: `${pkg.name} [options] <file>`, | ||
prepend: `${pkg.name} [options] <files>`, | ||
append: `Version ${pkg.version}`, | ||
@@ -87,4 +87,4 @@ options: [ | ||
if (opts._.length !== 1) { | ||
console.error('File not specified'); | ||
if (opts._.length === 0) { | ||
console.error('File(s) not specified'); | ||
console.log(optsParser.generateHelp()); | ||
@@ -94,12 +94,18 @@ process.exit(1); | ||
const filepath = path.resolve(opts._[0]); | ||
const resolveFilepath = (input) => { | ||
const output = path.resolve(input); | ||
try { | ||
fs.accessSync(filepath); | ||
} | ||
catch (error) { | ||
console.error(`The file "${filepath}" does not exist`); | ||
process.exit(1); | ||
} | ||
try { | ||
fs.accessSync(output); | ||
} | ||
catch (error) { | ||
console.error(`The file "${output}" does not exist`); | ||
process.exit(1); | ||
} | ||
return output; | ||
}; | ||
const files = opts._.map(resolveFilepath); | ||
const options = { | ||
@@ -115,3 +121,3 @@ writeJson: typeof opts.writeJson === 'boolean' ? | ||
const validator = new YamlValidator(options); | ||
validator.validate([filepath]); | ||
validator.validate(files); | ||
process.exitCode = validator.report(); |
348
index.js
@@ -16,213 +16,215 @@ /** | ||
const YamlValidatore = function YamlValidatore(options) { | ||
this.options = Object.assign({ | ||
log: false, | ||
structure: false, | ||
onWarning: null, | ||
writeJson: false | ||
}, options); | ||
class YamlValidatore { | ||
constructor (options) { | ||
this.options = Object.assign({ | ||
log: false, | ||
structure: false, | ||
onWarning: null, | ||
writeJson: false | ||
}, options); | ||
this.logs = []; | ||
this.nonValidPaths = []; // list of property paths | ||
this.inValidFilesCount = 0; | ||
}; | ||
this.logs = []; | ||
this.nonValidPaths = []; // list of property paths | ||
this.inValidFilesCount = 0; | ||
} | ||
/** | ||
* Store log messages | ||
* possible later use by writing a log file. | ||
* @param {string} msg Error message | ||
* @returns {void} | ||
*/ | ||
YamlValidatore.prototype.errored = function errored(msg) { | ||
this.logs.push(msg); | ||
}; | ||
/** | ||
* Store log messages | ||
* possible later use by writing a log file. | ||
* @param {string} msg Error message | ||
* @returns {void} | ||
*/ | ||
errored(msg) { | ||
this.logs.push(msg); | ||
} | ||
/** | ||
* Check that the given structure is available. | ||
* @param {Object} doc Object loaded from Yaml file | ||
* @param {Object} structure Structure requirements | ||
* @param {string} parent Address in a dot notation | ||
* @returns {Array} List of not found structure paths | ||
*/ | ||
YamlValidatore.prototype.validateStructure = function validateStructure(doc, structure, parent) { | ||
let notFound = [], | ||
current = '', | ||
notValid; // false or path | ||
/** | ||
* Check that the given structure is available. | ||
* @param {Object} doc Object loaded from Yaml file | ||
* @param {Object} structure Structure requirements | ||
* @param {string} parent Address in a dot notation | ||
* @returns {Array} List of not found structure paths | ||
*/ | ||
validateStructure(doc, structure, parent) { | ||
let notFound = [], | ||
current = '', | ||
notValid; // false or path | ||
parent = parent || ''; | ||
parent = parent || ''; | ||
Object.keys(structure).forEach(function eachKey(originKey) { | ||
const optional = originKey.endsWith('?'); | ||
const key = originKey.replace(/\?$/u, ''); | ||
Object.keys(structure).forEach((originKey) => { | ||
const optional = originKey.endsWith('?'); | ||
const key = originKey.replace(/\?$/u, ''); | ||
current = parent; | ||
if (!check(structure).is('Array')) { | ||
current += (parent.length > 0 ? | ||
'.' : | ||
'') + key; | ||
} | ||
current = parent; | ||
if (!check(structure).is('Array')) { | ||
current += (parent.length > 0 ? | ||
'.' : | ||
'') + key; | ||
} | ||
const item = structure[originKey]; | ||
if (item instanceof Array) { | ||
if (check(doc).has(key) && check(doc[key]).is('Array')) { | ||
doc[key].forEach(function eachArray(child, index) { | ||
if (item.length > 1) { | ||
notValid = validateStructure([child], [item[index]], current + '[' + index + ']'); | ||
} | ||
else { | ||
notValid = validateStructure([child], item, current + '[' + index + ']'); | ||
} | ||
notFound = notFound.concat(notValid); | ||
}); | ||
const item = structure[originKey]; | ||
if (item instanceof Array) { | ||
if (check(doc).has(key) && check(doc[key]).is('Array')) { | ||
doc[key].forEach((child, index) => { | ||
if (item.length > 1) { | ||
notValid = this.validateStructure([child], [item[index]], current + '[' + index + ']'); | ||
} | ||
else { | ||
notValid = this.validateStructure([child], item, current + '[' + index + ']'); | ||
} | ||
notFound = notFound.concat(notValid); | ||
}); | ||
} | ||
else if (!optional) { | ||
notFound.push(current); | ||
} | ||
} | ||
else if (!optional) { | ||
notFound.push(current); | ||
else if (typeof item === 'string') { | ||
if (!check(doc).has(key) && optional){ | ||
notValid = false; | ||
} | ||
else { | ||
notValid = !((check(structure).is('Array') || check(doc).has(key)) && check(doc[key]).is(item)); | ||
} | ||
// Key can be a index number when the structure is an array, but passed as a string | ||
notFound.push(notValid ? | ||
current : | ||
false); | ||
} | ||
} | ||
else if (typeof item === 'string') { | ||
if (!check(doc).has(key) && optional){ | ||
notValid = false; | ||
else if (typeof item === 'object' && item !== null) { | ||
if (!optional) { | ||
notValid = this.validateStructure(doc[key], item, current); | ||
notFound = notFound.concat(notValid); | ||
} | ||
} | ||
else { | ||
notValid = !((check(structure).is('Array') || check(doc).has(key)) && check(doc[key]).is(item)); | ||
}); | ||
return notFound.filter(function filterFalse(item) { | ||
return item !== false; | ||
}); | ||
} | ||
/** | ||
* Parse the given Yaml data. | ||
* @param {string} filepath Yaml file path | ||
* @param {string} data Yaml data | ||
* @returns {string|null} Parsed Yaml or null on failure | ||
*/ | ||
loadData(filepath, data) { | ||
const onWarning = (error) => { | ||
this.errored(filepath + ' > ' + error); | ||
if (typeof this.options.onWarning === 'function') { | ||
this.options.onWarning.call(this, error, filepath); | ||
} | ||
}; | ||
let doc; | ||
// Key can be a index number when the structure is an array, but passed as a string | ||
notFound.push(notValid ? | ||
current : | ||
false); | ||
try { | ||
doc = yaml.safeLoad(data, { | ||
onWarning: onWarning | ||
}); | ||
} | ||
else if (typeof item === 'object' && item !== null) { | ||
if (!optional) { | ||
notValid = validateStructure(doc[key], item, current); | ||
notFound = notFound.concat(notValid); | ||
} | ||
catch (error) { | ||
const lineNumber = error.message.match(/line (\d+)/u)[1]; | ||
this.errored(`Failed to load the Yaml file "${filepath}:${lineNumber}"\n${error.message}`); | ||
console.error(`${filepath}:${lineNumber}\n${error.message}`); | ||
return null; | ||
} | ||
}); | ||
return notFound.filter(function filterFalse(item) { | ||
return item !== false; | ||
}); | ||
}; | ||
return doc; | ||
} | ||
/** | ||
* Parse the given Yaml data. | ||
* @param {string} filepath Yaml file path | ||
* @param {string} data Yaml data | ||
* @returns {string|null} Parsed Yaml or null on failure | ||
*/ | ||
YamlValidatore.prototype.loadData = function loadData(filepath, data) { | ||
/** | ||
* Read and parse the given Yaml file. | ||
* @param {string} filepath Yaml file path | ||
* @returns {string|null} Parsed Yaml or null on failure | ||
*/ | ||
loadFile(filepath) { | ||
// Verbose output will tell which file is being read | ||
let data; | ||
const _self = this; | ||
const _self = this; | ||
let doc; | ||
try { | ||
data = fs.readFileSync(filepath, 'utf8'); | ||
} | ||
catch (err) { | ||
_self.errored(filepath + ' > No such file or directory'); | ||
try { | ||
doc = yaml.safeLoad(data, { | ||
onWarning: (error) => { | ||
_self.errored(filepath + ' > ' + error); | ||
if (typeof _self.options.onWarning === 'function') { | ||
_self.options.onWarning.call(_self, error, filepath); | ||
} | ||
} | ||
}); | ||
} | ||
catch (error) { | ||
const lineNumber = error.message.match(/line (\d+)/u)[1]; | ||
_self.errored(`Failed to load the Yaml file "${filepath}:${lineNumber}"\n${error.message}`); | ||
console.error(`${filepath}:${lineNumber}\n${error.message}`); | ||
return null; | ||
} | ||
return null; | ||
return this.loadData(filepath, data); | ||
} | ||
return doc; | ||
}; | ||
/** | ||
* Read the given Yaml file, load and check its structure. | ||
* @param {string} filepath Yaml file path | ||
* @returns {number} 0 when no errors, 1 when errors. | ||
*/ | ||
checkFile(filepath) { | ||
const doc = this.loadFile(filepath); | ||
/** | ||
* Read and parse the given Yaml file. | ||
* @param {string} filepath Yaml file path | ||
* @returns {string|null} Parsed Yaml or null on failure | ||
*/ | ||
YamlValidatore.prototype.loadFile = function loadFile(filepath) { | ||
// Verbose output will tell which file is being read | ||
let data; | ||
const _self = this; | ||
if (!doc) { | ||
return 1; | ||
} | ||
try { | ||
data = fs.readFileSync(filepath, 'utf8'); | ||
} | ||
catch (err) { | ||
_self.errored(filepath + ' > No such file or directory'); | ||
if (this.options.writeJson) { | ||
const json = JSON.stringify(doc, null, ' '); | ||
fs.writeFileSync(filepath.replace(/\.y(a)?ml$/iu, '.json'), json, 'utf8'); | ||
} | ||
return null; | ||
} | ||
if (this.options.structure) { | ||
const nonValidPaths = this.validateStructure(doc, this.options.structure); | ||
return this.loadData(filepath, data); | ||
}; | ||
if (nonValidPaths.length > 0) { | ||
this.errored(filepath + ' is not following the correct structure, missing:'); | ||
this.errored(nonValidPaths.join('\n')); | ||
this.nonValidPaths = this.nonValidPaths.concat(nonValidPaths); | ||
/** | ||
* Read the given Yaml file, load and check its structure. | ||
* @param {string} filepath Yaml file path | ||
* @returns {number} 0 when no errors, 1 when errors. | ||
*/ | ||
YamlValidatore.prototype.checkFile = function checkFile(filepath) { | ||
const doc = this.loadFile(filepath); | ||
return 1; | ||
} | ||
} | ||
if (!doc) { | ||
return 1; | ||
return 0; | ||
} | ||
if (this.options.writeJson) { | ||
const json = JSON.stringify(doc, null, ' '); | ||
fs.writeFileSync(filepath.replace(/\.y(a)?ml$/iu, '.json'), json, 'utf8'); | ||
/** | ||
* Create a report out of this, but in reality also run. | ||
* @param {array} files List of files that have been checked that they exist | ||
* @returns {void} | ||
*/ | ||
validate(files) { | ||
const _self = this; | ||
this.inValidFilesCount = files.map((filepath) => { | ||
return _self.checkFile(filepath); | ||
}).reduce((prev, curr) => { | ||
return prev + curr; | ||
}, _self.inValidFilesCount); | ||
} | ||
if (this.options.structure) { | ||
const nonValidPaths = this.validateStructure(doc, this.options.structure); | ||
/** | ||
* Create a report out of this, but in reality also run. | ||
* @returns {number} 0 when no errors, the count of invalid files otherwise. | ||
*/ | ||
report() { | ||
if (nonValidPaths.length > 0) { | ||
this.errored(filepath + ' is not following the correct structure, missing:'); | ||
this.errored(nonValidPaths.join('\n')); | ||
this.nonValidPaths = this.nonValidPaths.concat(nonValidPaths); | ||
return 1; | ||
if (this.inValidFilesCount > 0) { | ||
this.errored('Yaml format related errors in ' + this.inValidFilesCount + ' files'); | ||
} | ||
} | ||
return 0; | ||
}; | ||
const len = this.nonValidPaths.length; | ||
this.errored('Total of ' + len + ' structure validation error(s)'); | ||
/** | ||
* Create a report out of this, but in reality also run. | ||
* @param {array} files List of files that have been checked that they exist | ||
* @returns {void} | ||
*/ | ||
YamlValidatore.prototype.validate = function validate(files) { | ||
const _self = this; | ||
this.inValidFilesCount = files.map(function mapFiles(filepath) { | ||
return _self.checkFile(filepath); | ||
}).reduce(function reduceFiles(prev, curr) { | ||
return prev + curr; | ||
}, _self.inValidFilesCount); | ||
}; | ||
if (typeof this.options.log === 'string') { | ||
fs.writeFileSync(this.options.log, this.logs.join('\n'), 'utf8'); | ||
} | ||
/** | ||
* Create a report out of this, but in reality also run. | ||
* @returns {number} 0 when no errors, the count of invalid files otherwise. | ||
*/ | ||
YamlValidatore.prototype.report = function report() { | ||
if (this.inValidFilesCount > 0) { | ||
this.errored('Yaml format related errors in ' + this.inValidFilesCount + ' files'); | ||
return this.inValidFilesCount; | ||
} | ||
} | ||
const len = this.nonValidPaths.length; | ||
this.errored('Total of ' + len + ' structure validation error(s)'); | ||
if (typeof this.options.log === 'string') { | ||
fs.writeFileSync(this.options.log, this.logs.join('\n'), 'utf8'); | ||
} | ||
return this.inValidFilesCount; | ||
}; | ||
module.exports = YamlValidatore; |
{ | ||
"name": "yaml-validator", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "Validate Yaml files and enforce a given structure", | ||
@@ -41,10 +41,10 @@ "main": "index.js", | ||
"devDependencies": { | ||
"@types/node": "10.14.6", | ||
"codecov": "3.3.0", | ||
"@types/node": "12.0.3", | ||
"codecov": "3.5.0", | ||
"eslint": "5.16.0", | ||
"eslint-config-paazmaya": "5.2.0", | ||
"nyc": "14.0.0", | ||
"tape": "4.10.1", | ||
"typescript": "3.4.5" | ||
"eslint-config-paazmaya": "5.3.0", | ||
"nyc": "14.1.1", | ||
"tape": "4.10.2", | ||
"typescript": "3.5.1" | ||
} | ||
} |
@@ -67,3 +67,3 @@ # yaml-validator | ||
```sh | ||
yaml-validator [options] <file> | ||
yaml-validator [options] <files> | ||
@@ -156,3 +156,3 @@ -h, --help Help and usage instructions | ||
id: 'number', | ||
'location?':{ | ||
'location?':{ | ||
floor: "string", | ||
@@ -212,2 +212,5 @@ building: "string", | ||
* `v2.2.0` (2019-05-29) | ||
- Internally written as ES2015 Class, instead of ES5 way which polluted `prototype` | ||
- Allows now more than just one input file via command line | ||
* `v2.1.0` (2019-04-27) | ||
@@ -214,0 +217,0 @@ - Use [`npm-shrinkwrap.json`](https://docs.npmjs.com/files/shrinkwrap.json) for locking the working set of 3rd party dependencies |
Sorry, the diff of this file is too big to display
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
108777
2731
250
0