@dojo/cli
Version:
283 lines • 10.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const chalk_1 = require("chalk");
const allCommands_1 = require("../allCommands");
const configurationHelper_1 = require("../configurationHelper");
const CommandHelper_1 = require("../CommandHelper");
const Helper_1 = require("../Helper");
const Ajv = require("ajv");
const { red, green, yellow } = chalk_1.default;
function register(options) { }
function logNoConfig() {
console.log(yellow('No config has been detected'));
}
exports.logNoConfig = logNoConfig;
function logValidateFunctionFailed(error) {
console.log(red(`The validation function for this command threw an error: ${error}`));
}
exports.logValidateFunctionFailed = logValidateFunctionFailed;
function logEmptyConfig() {
console.log(yellow('A config was found, but it has no properties'));
}
exports.logEmptyConfig = logEmptyConfig;
function logSchemaErrors(mismatch) {
console.log(` ▹ ${red(mismatch)}\n`);
}
exports.logSchemaErrors = logSchemaErrors;
function logValidationFailed(commandKey) {
console.log(red(`${commandKey} config is invalid! The following issues were found: \n`));
}
exports.logValidationFailed = logValidationFailed;
function logSchemaSuccess(commandName) {
console.log(green(`${commandName} config validation was successful!`));
}
exports.logSchemaSuccess = logSchemaSuccess;
function logConfigValidateSuccess() {
console.log(green('There were no issues with your config!'));
}
exports.logConfigValidateSuccess = logConfigValidateSuccess;
function logNoValidatableCommands() {
console.log(green('There were no commands to validate against'));
}
exports.logNoValidatableCommands = logNoValidatableCommands;
function getValidationErrors(commandKey, commandConfig, commandSchema) {
const ajv = new Ajv({ allErrors: true, verbose: true });
const validate = ajv.compile(commandSchema);
validate(commandConfig);
let errors = [];
if (validate.errors) {
errors = filterErrors(validate.errors).map((error) => {
return formatValidationErrors(commandKey, commandSchema, error);
});
}
return errors;
}
exports.getValidationErrors = getValidationErrors;
// anyOf and oneOf will also list the individual errors
// allOf is decomposed into it's individual errors so we do not need to filter
function filterCompound(errors, compound) {
let anyOfPath;
const hasAnyOf = errors.some((err) => {
if (err.keyword === compound) {
anyOfPath = err.schemaPath;
return true;
}
return false;
});
return hasAnyOf
? errors.filter((err) => {
if (err.schemaPath.startsWith(anyOfPath) && err.keyword !== compound) {
return false;
}
return true;
})
: errors;
}
function filterErrors(errors) {
errors = filterCompound(errors, 'oneOf');
errors = filterCompound(errors, 'anyOf');
return errors;
}
function formatSchema(schemaToFormat) {
if (schemaToFormat.oneOf) {
return schemaToFormat.oneOf.map(formatSchema).join(' | ');
}
if (schemaToFormat.anyOf) {
return schemaToFormat.anyOf.map(formatSchema).join(' | ');
}
if (schemaToFormat.enum) {
return schemaToFormat.enum.map((item) => JSON.stringify(item)).join(' | ');
}
if (schemaToFormat.type === 'string') {
if (schemaToFormat.minLength === 1) {
return 'non-empty string';
}
if (schemaToFormat.minLength > 1) {
return `string (min length ${schemaToFormat.minLength})`;
}
return 'string';
}
if (schemaToFormat.type === 'boolean') {
return 'boolean';
}
if (schemaToFormat.type === 'number') {
return 'number';
}
if (schemaToFormat.type === 'integer') {
return 'integer';
}
if (schemaToFormat.type === 'object') {
if (schemaToFormat.properties) {
const required = schemaToFormat.required || [];
return `\n { ${Object.keys(schemaToFormat.properties)
.map((property) => {
if (!required.includes(property)) {
return property + '?';
}
return property;
})
.concat(schemaToFormat.additionalProperties ? ['…'] : [])
.join(', ')} }`;
}
if (schemaToFormat.patternProperties) {
return formatObject(schemaToFormat.patternProperties);
}
}
return formatObject(schemaToFormat);
}
function formatObject(obj) {
return ('\n' +
JSON.stringify(obj, null, 4)
.replace('{', ' {')
.replace(new RegExp('\n', 'g'), '\n '));
}
function formatValidationErrors(commandKey, commandSchema, err) {
const dataPath = `config.${commandKey}${err.dataPath}`;
if (err.keyword === 'additionalProperties') {
return `${dataPath} has an unknown property '${err.params.additionalProperty}'.`;
}
else if (err.keyword === 'oneOf' || err.keyword === 'anyOf') {
return `${dataPath} should be one of these:\n${formatSchema(err.parentSchema)}`;
}
else if (err.keyword === 'enum') {
if (err.parentSchema && err.parentSchema.enum && err.parentSchema.enum.length === 1) {
return `${dataPath} should be ${formatSchema(err.parentSchema)}`;
}
return `${dataPath} should be one of these:\n${formatSchema(err.parentSchema)}`;
}
else if (err.keyword === 'type') {
switch (err.params.type) {
case 'object':
if (err.parentSchema.patternProperties) {
return `${dataPath} should be an object with following pattern of properties:\n${formatSchema(err.parentSchema)}`;
}
return `${dataPath} should be an object with following properties:\n${formatSchema(err.parentSchema)}`;
case 'string':
return `${dataPath} should be a string.`;
case 'boolean':
return `${dataPath} should be a boolean.`;
case 'number':
return `${dataPath} should be a number.`;
case 'array':
return `${dataPath} should be an array.`;
}
return `${dataPath} should be ${err.params.type}.`;
}
else if (err.keyword === 'required') {
const missingProperty = err.params.missingProperty.replace(/^\./, '');
const schema = err.schema[missingProperty];
let form = formatSchema(schema);
if (schema.type === 'object') {
form = `:\n${form}\n`;
}
else {
form = ` ${form}.`;
}
return `${dataPath} misses the property '${missingProperty}', which is of type${form}`;
}
else if (err.keyword === 'minimum' || err.keyword === 'maximum') {
return `${dataPath} ${err.message}.`;
}
else if (err.keyword === 'uniqueItems') {
return `${dataPath} should not contain the item '${err.data[err.params.i]}' twice.`;
}
else if (err.keyword === 'minLength' || err.keyword === 'minItems' || err.keyword === 'minProperties') {
if (err.params.limit === 1) {
return `${dataPath} should not be empty.`;
}
else {
return `${dataPath} ${err.message}`;
}
}
else {
return `${dataPath} ${err.message}`;
}
}
function getValidateable(commandMaps) {
let toValidate = new Set();
commandMaps.forEach((commandMap) => {
[...commandMap.values()].forEach((command) => {
if (typeof command.validate === 'function') {
toValidate.add(command);
}
});
});
return [...toValidate];
}
function builtInCommandValidation(validation) {
return new Promise((resolve) => {
const { commandGroup, commandName, commandSchema, commandConfig, silentSuccess } = validation;
const commandKey = `${commandGroup}-${commandName}`; // group and name are required properties
if (validation.commandConfig === undefined) {
resolve(true);
return;
}
const mismatches = getValidationErrors(commandKey, commandConfig, commandSchema);
const valid = mismatches.length === 0;
if (!valid) {
logValidationFailed(commandKey);
mismatches.forEach((mismatch) => {
logSchemaErrors(mismatch);
});
}
else {
!silentSuccess && logSchemaSuccess(commandKey);
}
resolve(valid);
});
}
exports.builtInCommandValidation = builtInCommandValidation;
function validateCommands(commands, helper) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const config = configurationHelper_1.getConfig();
const noConfig = config === undefined;
const emptyConfig = typeof config === 'object' && Object.keys(config).length === 0;
if (noConfig) {
logNoConfig();
return true;
}
else if (emptyConfig) {
logEmptyConfig();
return true;
}
const commandsToValidate = getValidateable(commands);
if (commandsToValidate.length === 0) {
logNoValidatableCommands();
return true;
}
const commandValidations = commandsToValidate.map((command) => {
const validate = command.validate;
return validate(helper.sandbox(command.group, command.name)).catch((error) => {
logValidateFunctionFailed(error);
return false;
});
});
// Wait for all validations to resolve and check if all commands are valid
return Promise.all(commandValidations).then((validations) => {
const allValid = validations.every((validation) => validation);
if (allValid) {
logConfigValidateSuccess();
}
return allValid;
});
});
}
function run(helper, args) {
return allCommands_1.loadExternalCommands().then((commands) => {
const helperContext = {};
const commandHelper = new CommandHelper_1.default(commands, helperContext, configurationHelper_1.default);
const validateHelper = { validate: builtInCommandValidation };
const helperFactory = new Helper_1.default(commandHelper, args, helperContext, configurationHelper_1.default, validateHelper);
return validateCommands(commands, helperFactory);
});
}
exports.default = {
name: '',
group: 'validate',
description: 'validate your .dojorc configuration file for installed commands',
register,
global: false,
run
};
//# sourceMappingURL=validate.js.map