diff --git a/bin/cli/base.js b/bin/cli/base.js index 13a730d..9f437cd 100644 --- a/bin/cli/base.js +++ b/bin/cli/base.js @@ -6,13 +6,17 @@ const { Command, flags } = require('@oclif/command'); const Beau = require('../../src/beau'); class Base extends Command { - loadConfig(configFile) { + openConfigFile(configFile) { if (!fs.existsSync(configFile)) { this.error(`The config file, ${configFile} was not found.`); this.exit(1); } - const config = yaml.safeLoad(fs.readFileSync(configFile, 'utf-8')); + return yaml.safeLoad(fs.readFileSync(configFile, 'utf-8')); + } + + loadConfig(configFile) { + const config = this.openConfigFile(configFile); const env = dotenv.config().parsed || {}; return new Beau(config, env); diff --git a/bin/cli/commands/validate.js b/bin/cli/commands/validate.js new file mode 100644 index 0000000..04840c9 --- /dev/null +++ b/bin/cli/commands/validate.js @@ -0,0 +1,34 @@ +const clc = require('cli-color'); +const fs = require('fs'); +const yaml = require('js-yaml'); +const { flags } = require('@oclif/command'); + +const Base = require('../base'); +const { validate } = require('../../../src/schema.js'); + +class ValidateCommand extends Base { + async run() { + const { flags, args } = this.parse(ValidateCommand); + const configFile = args.alias || flags.config; + + const config = this.openConfigFile(configFile); + + let result = await validate(config); + if (result.valid) { + this.log(`${configFile} is valid.`); + } else { + this.error(result.message); + } + } +} + +ValidateCommand.description = `Validates the given configuration file against Beau's configuration schema.`; +ValidateCommand.flags = { ...Base.flags }; +ValidateCommand.args = [ + { + name: 'alias', + required: false, + description: `The configuration file to validate.` + } +]; +module.exports = ValidateCommand; diff --git a/examples/plugins.yml b/examples/plugins.yml index 61ca695..12718e2 100644 --- a/examples/plugins.yml +++ b/examples/plugins.yml @@ -1,7 +1,7 @@ endpoint: http://localhost:10080 plugins: - - beau-jwt: + - jwt: data: userId: 12 name: Sergio diff --git a/package-lock.json b/package-lock.json index 1e59c01..aa81998 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3939,7 +3939,7 @@ "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.1.2.tgz", "integrity": "sha512-zfRhJn9rFSGhzU5tGZqepRSAj3+g6oTOHxMGGriWNJZzyLPUK8H7VHpqKntegnW8KLyGA9zwuNaCoopl40LTpg==", "requires": { - "punycode": "2.1.0" + "punycode": "2.x.x" }, "dependencies": { "punycode": { @@ -4807,9 +4807,9 @@ "resolved": "https://registry.npmjs.org/joi/-/joi-13.2.0.tgz", "integrity": "sha512-VUzQwyCrmT2lIpxBCYq26dcK9veCQzDh84gQnCtaxCa8ePohX8JZVVsIb+E66kCUUcIvzeIpifa6eZuzqTZ3NA==", "requires": { - "hoek": "5.0.3", - "isemail": "3.1.2", - "topo": "3.0.0" + "hoek": "5.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" }, "dependencies": { "hoek": { @@ -7218,7 +7218,7 @@ "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.0.tgz", "integrity": "sha512-Tlu1fGlR90iCdIPURqPiufqAlCZYzLjHYVVbcFWDMcX7+tK8hdZWAfsMrD/pBul9jqHHwFjNdf1WaxA9vTRRhw==", "requires": { - "hoek": "5.0.3" + "hoek": "5.x.x" }, "dependencies": { "hoek": { diff --git a/package.json b/package.json index adc5311..32732eb 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,10 @@ "oclif": { "commands": "./bin/cli/commands", "bin": "beau", - "plugins": ["@oclif/plugin-help", "@oclif/plugin-warn-if-update-available"] + "plugins": [ + "@oclif/plugin-help", + "@oclif/plugin-warn-if-update-available" + ] }, "jest": { "testEnvironment": "node", diff --git a/src/schema.js b/src/schema.js index 7fa4ac7..1a6b933 100644 --- a/src/schema.js +++ b/src/schema.js @@ -1,6 +1,13 @@ const Joi = require('joi'); const { requestRegex } = require('./shared.js'); +const pluginSchema = [ + Joi.string(), + Joi.object() + .keys(null) + .max(1) +]; + const requestSchema = [ Joi.object() .keys({ @@ -11,7 +18,9 @@ const requestSchema = [ ALIAS: Joi.string().required(), FORMDATA: Joi.object().keys(null) }) - .or('FORM', 'PAYLOAD', 'FORMDATA') + .without('FORM', ['PAYLOAD', 'FORMDATA']) + .without('PAYLOAD', ['FORM', 'FORMDATA']) + .without('FORMDATA', ['FORM', 'PAYLOAD']) .rename(/headers/i, 'HEADERS', { override: true }) .rename(/payload/i, 'PAYLOAD', { override: true }) .rename(/params/i, 'PARAMS', { override: true }) @@ -36,7 +45,7 @@ const schema = Joi.object() .keys({ VERSION: Joi.number().integer(), ENDPOINT: Joi.string().uri(), - PLUGINS: Joi.array().items([Joi.string(), Joi.object().keys(null)]), + PLUGINS: Joi.array().items(pluginSchema), DEFAULTS: Joi.object(), ENVIRONMENT: Joi.object(), HOSTS: Joi.array().items(hostSchema), @@ -51,8 +60,20 @@ const schema = Joi.object() .rename(/environment/i, 'ENVIRONMENT', { override: true }) .rename(/cookiejar/i, 'COOKIEJAR', { override: true }); -const validate = function(config) { - return Joi.validate(config, schema, { allowUnknown: true }); +const validate = async function(config) { + try { + let results = await Joi.validate(config, schema, { + allowUnknown: true + }); + return { valid: true }; + } catch ({ name, details }) { + return { + valid: false, + message: `${name}: \n ${details + .map(d => d.message + ' @ ' + d.path) + .join(' \n ')}` + }; + } }; module.exports = { schema, validate };