diff --git a/.prettierrc b/.prettierrc index e6bb537..743686c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,7 +5,6 @@ useTabs: false trailingComma: none bracketSpacing: true jsxBracketSameLine: true -parser: babylon -semi: true +semi: false requirePragma: false proseWrap: always diff --git a/bin/cli/__mocks__/base.js b/bin/cli/__mocks__/base.js new file mode 100644 index 0000000..d054911 --- /dev/null +++ b/bin/cli/__mocks__/base.js @@ -0,0 +1,30 @@ +const Beau = require('../../../src/beau') + +const original = require.requireActual('../base') + +const config = { + environment: { + params: { + name: 'David' + } + }, + endpoint: 'https://example.org', + version: 1, + 'GET /anything': { + alias: 'alias', + payload: { + name: '$env.params.name' + } + }, + 'GET /status/418': { + alias: 'teapot' + } +} + +class Base extends original { + loadConfig(configFile, params = []) { + return new Beau(config, {}) + } +} + +module.exports = Base diff --git a/bin/cli/__mocks__/utils.js b/bin/cli/__mocks__/utils.js deleted file mode 100644 index 2978ff1..0000000 --- a/bin/cli/__mocks__/utils.js +++ /dev/null @@ -1,41 +0,0 @@ -const Beau = require('../../../src/beau'); -const original = require.requireActual('../utils'); - -const utils = {}; - -const config = { - environment: { - params: { - name: 'David' - } - }, - endpoint: 'https://example.org', - version: 1, - 'GET /anything': { - alias: 'alias', - payload: { - name: '$env.params.name' - } - }, - 'GET /status/418': { - alias: 'teapot' - } -}; - -utils.loadConfig = function() { - return new Beau(config, {}); -}; - -utils.openConfigFile = function(filename) { - if (filename === 'beau.yml') { - return config; - } - - if (filename === 'invalid-conf.yml') { - return { plugins: [{ hello: 1, world: 2 }] }; - } -}; - -utils.baseFlags = original.baseFlags; - -module.exports = utils; diff --git a/bin/cli/__tests__/__snapshots__/utils.spec.js.snap b/bin/cli/__tests__/__snapshots__/utils.spec.js.snap deleted file mode 100644 index 6041913..0000000 --- a/bin/cli/__tests__/__snapshots__/utils.spec.js.snap +++ /dev/null @@ -1,74 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`utils loadConfig should load load the config onto Beau 1`] = ` -Config { - "COOKIEJAR": false, - "DEFAULTS": Object {}, - "ENDPOINT": "https://example.org/", - "ENVIRONMENT": Object { - "_": Object {}, - }, - "HOSTS": Array [], - "PLUGINS": Plugins { - "autoload": Array [ - "std", - ], - "context": Object { - "createReadStream": [Function], - }, - "registry": Object { - "dynamicValues": Array [ - Object { - "fn": [Function], - "name": "createReadStream", - }, - ], - "postRequestModifiers": Array [], - "preRequestModifiers": Array [], - }, - }, - "REQUESTS": Array [ - Object { - "ALIAS": "anything", - "COOKIEJAR": false, - "ENDPOINT": "https://example.org/", - "PAYLOAD": Object { - "name": "$env.params.name", - }, - "REQUEST": "GET /anything", - }, - ], - "VERSION": 1, - "configKeys": Array [ - "VERSION", - "ENDPOINT", - "PLUGINS", - "DEFAULTS", - "ENVIRONMENT", - "HOSTS", - "COOKIEJAR", - ], -} -`; - -exports[`utils loadConfig should load params onto the environment 1`] = ` -Object { - "_": Object { - "BYE": "MARS", - "HELLO": "WORLD", - }, -} -`; - -exports[`utils openConfigFile should read and parse the given configuration file. 1`] = ` -Object { - "GET /anything": Object { - "alias": "anything", - "payload": Object { - "name": "$env.params.name", - }, - }, - "endpoint": "https://example.org/", - "version": 1, -} -`; diff --git a/bin/cli/__tests__/__snapshots__/validate.spec.js.snap b/bin/cli/__tests__/__snapshots__/validate.spec.js.snap deleted file mode 100644 index f7f7fbb..0000000 --- a/bin/cli/__tests__/__snapshots__/validate.spec.js.snap +++ /dev/null @@ -1,8 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Validate Command should validate the configuration file 1`] = ` -Array [ - "beau.yml is valid. -", -] -`; diff --git a/bin/cli/__tests__/list.spec.js b/bin/cli/__tests__/list.spec.js index 367aa3a..582fee5 100644 --- a/bin/cli/__tests__/list.spec.js +++ b/bin/cli/__tests__/list.spec.js @@ -2,7 +2,7 @@ const ListCommand = require('../commands/list'); jest.mock('../../../src/shared'); -jest.mock('../utils'); +jest.mock('../base'); describe('List Command', () => { let result; diff --git a/bin/cli/__tests__/request.spec.js b/bin/cli/__tests__/request.spec.js index b09e9a2..85bdda3 100644 --- a/bin/cli/__tests__/request.spec.js +++ b/bin/cli/__tests__/request.spec.js @@ -3,7 +3,7 @@ const requestPromiseNativeMock = require('request-promise-native'); jest.mock('../../../src/shared'); -jest.mock('../utils'); +jest.mock('../base'); describe('Request Command', () => { let result; diff --git a/bin/cli/__tests__/utils.spec.js b/bin/cli/__tests__/utils.spec.js deleted file mode 100644 index 69877c0..0000000 --- a/bin/cli/__tests__/utils.spec.js +++ /dev/null @@ -1,32 +0,0 @@ -const utils = require('../utils.js'); - -jest.mock('../../../src/shared'); - -jest.mock('fs'); - -describe('utils', () => { - describe('openConfigFile', () => { - it('should read and parse the given configuration file.', () => { - expect(utils.openConfigFile('beau.yml')).toMatchSnapshot(); - }); - - it('should throw if given not given a file', () => { - expect(() => utils.openConfigFile('not-a-file.yml')).toThrow(); - }); - }); - - describe('loadConfig', () => { - it('should load load the config onto Beau', () => { - let beau = utils.loadConfig('beau.yml'); - expect(beau.config).toMatchSnapshot(); - }); - - it('should load params onto the environment', () => { - let beau = utils.loadConfig('beau.yml', [ - 'HELLO=WORLD', - 'BYE=MARS' - ]); - expect(beau.config.ENVIRONMENT).toMatchSnapshot(); - }); - }); -}); diff --git a/bin/cli/__tests__/validate.spec.js b/bin/cli/__tests__/validate.spec.js deleted file mode 100644 index 8f99821..0000000 --- a/bin/cli/__tests__/validate.spec.js +++ /dev/null @@ -1,27 +0,0 @@ -const ValidateCommand = require('../commands/validate'); - -jest.mock('../utils'); - -describe('Validate Command', () => { - let result; - - beforeEach(() => { - result = []; - jest.spyOn(process.stdout, 'write').mockImplementation(val => - result.push(require('strip-ansi')(val.toString('utf8'))) - ); - }); - - afterEach(() => jest.restoreAllMocks()); - - it('should validate the configuration file', async () => { - await ValidateCommand.run([]); - expect(result).toMatchSnapshot(); - }); - - it('should show schema errors', async () => { - await expect( - ValidateCommand.run(['invalid-conf.yml']) - ).rejects.toThrow(); - }); -}); diff --git a/bin/cli/base.js b/bin/cli/base.js new file mode 100644 index 0000000..353654c --- /dev/null +++ b/bin/cli/base.js @@ -0,0 +1,76 @@ +const { Command, flags } = require('@oclif/command') +const yaml = require('js-yaml') +const fs = require('fs') +const path = require('path') +const dotenv = require('dotenv') +const Beau = require('../../src/beau') +const Ajv = require('ajv').default +const betterAjvErrors = require('better-ajv-errors') + +const schema = require('../../schema.json') +const ajv = new Ajv() +const validate = ajv.compile(schema) + +class Base extends Command { + openConfigFile(configFile) { + if (!fs.existsSync(configFile)) { + throw new Error(`The config file, ${configFile} was not found.`) + } + + let config + yaml.safeLoadAll(fs.readFileSync(configFile, 'utf-8'), (doc) => { + const valid = validate(doc) + + if (!valid) { + this.log(`The configuration file is not valid.`) + this.error( + betterAjvErrors(schema, doc, validate.errors, { indent: 2 }) + ) + } + + if (typeof config === 'undefined') { + config = doc + } else { + if (typeof config.hosts === 'undefined') { + config.hosts = [] + } + + config.hosts.push(doc) + } + }) + + return config + } + loadConfig(configFile, params = []) { + const config = this.openConfigFile(configFile) + const env = dotenv.config().parsed || {} + params = dotenv.parse(params.join('\n')) + + const envParams = { _: Object.assign(env, params) } + + const configFileDir = path.dirname( + path.resolve(process.cwd(), configFile) + ) + + process.chdir(configFileDir) + + return new Beau(config, envParams) + } +} + +Base.flags = { + config: flags.string({ + char: 'c', + description: 'The configuration file to be used.', + default: 'beau.yml' + }), + verbose: flags.boolean({ + char: 'V', + description: `Show all additional information available for a command.` + }), + 'no-format': flags.boolean({ + description: `Disables color formatting for usage on external tools.` + }) +} + +module.exports = Base diff --git a/bin/cli/commands/list.js b/bin/cli/commands/list.js index 388df4d..3acbb6b 100644 --- a/bin/cli/commands/list.js +++ b/bin/cli/commands/list.js @@ -1,26 +1,22 @@ -const clc = require('cli-color'); -const { Line } = require('clui'); -const { flags, Command } = require('@oclif/command'); -const { baseFlags, loadConfig } = require('../utils'); +const clc = require('cli-color') +const { Line } = require('clui') +const Base = require('../base') -class ListCommand extends Command { +class ListCommand extends Base { async run() { - const { flags } = this.parse(ListCommand); - const Beau = loadConfig(flags.config); + const { flags } = this.parse(ListCommand) + const Beau = this.loadConfig(flags.config) if (flags['no-format']) { return Beau.requests.list.forEach( ({ VERB, ALIAS, ENDPOINT, PATH }) => this.log( - VERB + - `\t` + - ALIAS + - `\t` + - ENDPOINT.replace(/\/$/, '') + - `/` + - PATH.replace(/^\//, '') + `${VERB}\t${ALIAS}\t${ENDPOINT.replace( + /\/$/, + '' + )}/${PATH.replace(/^\//, '')}` ) - ); + ) } new Line() @@ -28,7 +24,7 @@ class ListCommand extends Command { .column('HTTP Verb', 20, [clc.cyan]) .column('Alias', 30, [clc.cyan]) .column('Endpoint', 20, [clc.cyan]) - .output(); + .output() Beau.requests.list.forEach(({ VERB, ALIAS, ENDPOINT, PATH }) => new Line() @@ -39,13 +35,13 @@ class ListCommand extends Command { ENDPOINT.replace(/\/$/, '') + '/' + PATH.replace(/^\//, '') ) .output() - ); + ) - new Line().output(); + new Line().output() } } -ListCommand.description = `Lists all available requests in the config file.`; -ListCommand.flags = { ...baseFlags }; +ListCommand.description = `Lists all available requests in the config file.` +ListCommand.flags = { ...Base.flags } -module.exports = ListCommand; +module.exports = ListCommand diff --git a/bin/cli/commands/request.js b/bin/cli/commands/request.js index 09fdc56..673f833 100644 --- a/bin/cli/commands/request.js +++ b/bin/cli/commands/request.js @@ -1,10 +1,10 @@ const clc = require('cli-color'); const jsome = require('jsome'); const { Line, Spinner } = require('clui'); -const { flags, Command } = require('@oclif/command'); -const { baseFlags, loadConfig } = require('../utils'); +const { flags } = require('@oclif/command'); +const Base = require('../base'); -class RequestCommand extends Command { +class RequestCommand extends Base { prettyOutput(res, verbose = false) { let { status, body } = res.response; @@ -44,7 +44,7 @@ class RequestCommand extends Command { args } = this.parse(RequestCommand); - const Beau = loadConfig(config, params); + const Beau = this.loadConfig(config, params); const spinnerSprite = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷']; this.spinner = new Spinner('', spinnerSprite); @@ -91,7 +91,7 @@ class RequestCommand extends Command { RequestCommand.description = `Executes a request by name.`; RequestCommand.flags = { - ...baseFlags, + ...Base.flags, param: flags.string({ char: 'P', multiple: true, diff --git a/bin/cli/commands/validate.js b/bin/cli/commands/validate.js deleted file mode 100644 index 311ad7b..0000000 --- a/bin/cli/commands/validate.js +++ /dev/null @@ -1,30 +0,0 @@ -const { flags, Command } = require('@oclif/command'); -const { baseFlags, openConfigFile } = require('../utils'); -const { validate } = require('../../../src/schema.js'); - -class ValidateCommand extends Command { - async run() { - const { flags, args } = this.parse(ValidateCommand); - const configFile = args.alias || flags.config; - - const config = 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 = { ...baseFlags }; -ValidateCommand.args = [ - { - name: 'alias', - required: false, - description: `The configuration file to validate.` - } -]; -module.exports = ValidateCommand; diff --git a/bin/cli/utils.js b/bin/cli/utils.js deleted file mode 100644 index 695b37c..0000000 --- a/bin/cli/utils.js +++ /dev/null @@ -1,62 +0,0 @@ -const yaml = require('js-yaml'); -const fs = require('fs'); -const path = require('path'); -const dotenv = require('dotenv'); -const Beau = require('../../src/beau'); -const { flags } = require('@oclif/command'); - -const openConfigFile = configFile => { - if (!fs.existsSync(configFile)) { - throw new Error(`The config file, ${configFile} was not found.`); - } - - let config; - yaml.safeLoadAll(fs.readFileSync(configFile, 'utf-8'), doc => { - if (typeof config === 'undefined') { - config = doc; - } else { - if (typeof config.hosts === 'undefined') { - config.hosts = []; - } - - config.hosts.push(doc); - } - }); - - return config; -}; - -const loadConfig = (configFile, params = []) => { - const config = openConfigFile(configFile); - const env = dotenv.config().parsed || {}; - params = dotenv.parse(params.join('\n')); - - const envParams = { _: Object.assign(env, params) }; - - const configFileDir = path.dirname(path.resolve(process.cwd(), configFile)); - - process.chdir(configFileDir); - - return new Beau(config, envParams); -}; - -const baseFlags = { - config: flags.string({ - char: 'c', - description: 'The configuration file to be used.', - default: 'beau.yml' - }), - verbose: flags.boolean({ - char: 'V', - description: `Show all additional information available for a command.` - }), - 'no-format': flags.boolean({ - description: `Disables color formatting for usage on external tools.` - }) -}; - -module.exports = { - openConfigFile, - loadConfig, - baseFlags -}; diff --git a/examples/httpbin.yml b/examples/httpbin.yml index fffb8b9..3210d24 100644 --- a/examples/httpbin.yml +++ b/examples/httpbin.yml @@ -7,9 +7,10 @@ environment: name: David GET /anything: - alias: anything - payload: - name: $env.params.name + - alias: anything + payload: + name: $env.params.name + - alias: anything2 GET /cookies/set: alias: set-cookies diff --git a/package-lock.json b/package-lock.json index f30f821..9d5f890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "version": "7.5.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, "requires": { "@babel/highlight": "^7.0.0" } @@ -128,7 +127,6 @@ "version": "7.5.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", - "dev": true, "requires": { "chalk": "^2.0.0", "esutils": "^2.0.2", @@ -150,6 +148,14 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/runtime": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, "@babel/template": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", @@ -259,14 +265,30 @@ "p-each-series": "^1.0.0", "realpath-native": "^1.1.0", "rimraf": "^2.5.4", - "slash": "^2.0.0" + "slash": "^2.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, "graceful-fs": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } } } }, @@ -704,6 +726,7 @@ "clean-stack": "^1.3.0", "fs-extra": "^7.0.0", "indent-string": "^3.2.0", + "strip-ansi": "^5.0.0", "wrap-ansi": "^4.0.0" }, "dependencies": { @@ -736,6 +759,21 @@ } } }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + } + } + }, "wrap-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-4.0.0.tgz", @@ -1143,13 +1181,12 @@ } }, "ajv": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", - "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", + "version": "7.0.0-beta.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.0-beta.6.tgz", + "integrity": "sha512-9aDR4p4ReYBS1XxrYONdWuFVRweLjJTv8RaNkBEpJm09jkVcVYhtaul5IL7Y/x1RJ9UakETm0oBze4VHIjq4nA==", "requires": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^0.5.0", "uri-js": "^4.2.2" } }, @@ -1470,6 +1507,20 @@ "uuid": "^3.2.1" } }, + "better-ajv-errors": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-0.6.7.tgz", + "integrity": "sha512-PYgt/sCzR4aGpyNy5+ViSQ77ognMnWq7745zM+/flYO4/Yisdtp9wDQW2IKCyVYPUxQt3E/b5GBSwfhd1LPdlg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/runtime": "^7.0.0", + "chalk": "^2.4.1", + "core-js": "^3.2.1", + "json-to-ast": "^2.0.3", + "jsonpointer": "^4.0.1", + "leven": "^3.1.0" + } + }, "boxen": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", @@ -1867,9 +1918,16 @@ "dev": true, "requires": { "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", "wrap-ansi": "^5.1.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -1883,7 +1941,17 @@ "dev": true, "requires": { "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" } }, "wrap-ansi": { @@ -1893,7 +1961,8 @@ "dev": true, "requires": { "ansi-styles": "^3.2.0", - "string-width": "^3.0.0" + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" } } } @@ -1999,6 +2068,11 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, + "code-error-fragment": { + "version": "0.0.230", + "resolved": "https://registry.npmjs.org/code-error-fragment/-/code-error-fragment-0.0.230.tgz", + "integrity": "sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==" + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -2123,6 +2197,11 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "core-js": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.7.0.tgz", + "integrity": "sha512-NwS7fI5M5B85EwpWuIwJN4i/fbisQUwLwiSNUWeXlkAZ0sbBjLEvLvFLf1uzAUV66PcEPt4xCGCmOZSxVf3xzA==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -2609,8 +2688,7 @@ "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "event-emitter": { "version": "0.3.5", @@ -2813,9 +2891,9 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "3.2.2", @@ -3717,6 +3795,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -3762,6 +3845,24 @@ "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + } } }, "hard-rejection": { @@ -3837,11 +3938,6 @@ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "dev": true }, - "hoek": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", - "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" - }, "hosted-git-info": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", @@ -4562,21 +4658,6 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "isemail": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", - "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", - "requires": { - "punycode": "2.x.x" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } - } - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5491,21 +5572,10 @@ } } }, - "joi": { - "version": "14.3.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", - "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", - "requires": { - "hoek": "6.x.x", - "isemail": "3.x.x", - "topo": "3.x.x" - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.14.0", @@ -5699,15 +5769,24 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.5.0.tgz", + "integrity": "sha512-x+TRJIQFskrNnFKE2Viz9FCSjK1vIh+H/uaBiOYszh/IcZmAFneQ35H4osWDJp1NPXccuV2I0RMXmi2ZS6Kqcg==" }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "json-to-ast": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json-to-ast/-/json-to-ast-2.1.0.tgz", + "integrity": "sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==", + "requires": { + "code-error-fragment": "0.0.230", + "grapheme-splitter": "^1.0.4" + } + }, "json5": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", @@ -5725,6 +5804,11 @@ "graceful-fs": "^4.1.6" } }, + "jsonpointer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", + "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==" + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -5783,8 +5867,7 @@ "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" }, "levn": { "version": "0.3.0", @@ -7942,6 +8025,11 @@ } } }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -8907,14 +8995,6 @@ "repeat-string": "^1.6.1" } }, - "topo": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", - "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", - "requires": { - "hoek": "6.x.x" - } - }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -9158,9 +9238,9 @@ } }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "requires": { "punycode": "^2.1.0" } @@ -9414,6 +9494,12 @@ "yargs-parser": "^13.1.1" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -9439,7 +9525,17 @@ "dev": true, "requires": { "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" } }, "y18n": { diff --git a/package.json b/package.json index 0cdf3ba..8578da2 100644 --- a/package.json +++ b/package.json @@ -1,66 +1,66 @@ { - "name": "beau", - "version": "0.10.1", - "description": "Testing APIs made easy.", - "main": "./src/beau.js", - "author": "Sergio Diaz ", - "license": "MIT", - "scripts": { - "test": "jest -i", - "test:coverage": "jest --coverage ./src", - "release": "np" - }, - "files": [ - "/src", - "/bin" - ], - "dependencies": { - "@oclif/plugin-help": "3.2.0", - "@oclif/command": "1.8.0", - "@oclif/config": "1.17.0", - "@oclif/plugin-warn-if-update-available": "1.7.0", - "beau-std": "0.9.4", - "cli-color": "2.0.0", - "clui": "0.3.6", - "deepmerge": "4.2.2", - "dotenv": "8.2.0", - "globby": "11.0.1", - "is-plain-object": "4.1.0", - "joi": "14.3.1", - "js-yaml": "3.14.0", - "jsome": "2.5.0", - "request": "2.88.2", - "request-promise-native": "1.0.9", - "requireg": "0.2.2" - }, - "repository": "git@github.com:Seich/Beau.git", - "devDependencies": { - "jest": "24.9.0", - "strip-ansi": "5.2.0", - "jest-watch-typeahead": "0.6.1", - "strip-ansi": "6.0.0", - "np": "7.0.0" - }, - "oclif": { - "commands": "./bin/cli/commands", - "bin": "beau", - "plugins": [ - "@oclif/plugin-help", - "@oclif/plugin-warn-if-update-available" - ] - }, - "jest": { - "testEnvironment": "node", - "notify": true, - "watchPlugins": [ - "jest-watch-typeahead/filename", - "jest-watch-typeahead/testname" - ] - }, - "bin": { - "beau": "./bin/beau" - }, - "engines": { - "node": ">=8.10.0" - } + "name": "beau", + "version": "0.10.1", + "description": "Testing APIs made easy.", + "main": "./src/beau.js", + "author": "Sergio Diaz ", + "license": "MIT", + "scripts": { + "test": "jest -i", + "test:coverage": "jest --coverage ./src", + "release": "np" + }, + "files": [ + "/src", + "/bin" + ], + "dependencies": { + "@oclif/plugin-help": "3.2.0", + "@oclif/command": "1.8.0", + "@oclif/config": "1.17.0", + "@oclif/plugin-warn-if-update-available": "1.7.0", + "ajv": "7.0.0-beta.6", + "beau-std": "0.9.4", + "better-ajv-errors": "0.6.7", + "cli-color": "2.0.0", + "clui": "0.3.6", + "deepmerge": "4.2.2", + "dotenv": "8.2.0", + "globby": "11.0.1", + "is-plain-object": "4.1.0", + "js-yaml": "3.14.0", + "jsome": "2.5.0", + "request": "2.88.2", + "request-promise-native": "1.0.9", + "requireg": "0.2.2" + }, + "repository": "git@github.com:Seich/Beau.git", + "devDependencies": { + "jest": "24.9.0", + "jest-watch-typeahead": "0.6.1", + "strip-ansi": "6.0.0", + "np": "7.0.0" + }, + "oclif": { + "commands": "./bin/cli/commands", + "bin": "beau", + "plugins": [ + "@oclif/plugin-help", + "@oclif/plugin-warn-if-update-available" + ] + }, + "jest": { + "testEnvironment": "node", + "notify": true, + "watchPlugins": [ + "jest-watch-typeahead/filename", + "jest-watch-typeahead/testname" + ] + }, + "bin": { + "beau": "./bin/beau" + }, + "engines": { + "node": ">=8.10.0" + } } diff --git a/schema.json b/schema.json new file mode 100644 index 0000000..03a2644 --- /dev/null +++ b/schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://beaujs.com/schema.json", + "title": "Schema for Beau", + "type": "object", + "definitions": { + "request": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "$ref": "#/definitions/requestObject", + "required": ["alias"] + } + ] + }, + "requestObject": { + "type": "object", + "properties": { + "alias": { + "type": "string", + "description": "The name of this request." + }, + "headers": { + "type": "object", + "additionalProperties": true, + "description": "Headers that are part of this request." + }, + "params": { + "type": "object", + "additionalProperties": true, + "description": "Query String parameters to add to this request." + }, + "payload": { + "description": "This request's body. It is converted to json automatically if given an object. It's sent as a string otherwise.", + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "additionalProperties": true + } + ] + }, + "form": { + "type": "object", + "additionalProperties": true, + "description": "This request's body. Sets the content-type to application/x-www-form-urlencoded." + }, + "formdata": { + "type": "object", + "additionalProperties": true, + "description": "This request's body. Sets the content-type to multipart/form-data." + } + }, + "allOf": [ + { "not": { "required": ["payload", "form"] } }, + { "not": { "required": ["payload", "formdata"] } }, + { "not": { "required": ["formdata", "form"] } } + ] + } + }, + "properties": { + "version": { + "type": "number", + "enum": [1] + }, + "endpoint": { + "type": "string", + "description": "The root endpoint for this host." + }, + "cookiejar": { + "type": "boolean" + }, + "defaults": { + "$ref": "#/definitions/requestObject" + }, + "plugins": { + "type": "array", + "items": { + "anyOf": [ + { "type": "string" }, + { "type": "object", "additionalProperties": true } + ] + } + }, + "environment": {} + }, + "patternProperties": { + "(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH)\\s.*": { + "oneOf": [ + { + "$ref": "#/definitions/request" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/request" + } + } + ] + } + } +} diff --git a/src/__tests__/schema.spec.js b/src/__tests__/schema.spec.js deleted file mode 100644 index c4e818b..0000000 --- a/src/__tests__/schema.spec.js +++ /dev/null @@ -1,15 +0,0 @@ -const schema = require('../schema'); - -describe('Schema', () => { - it(`should validate an object against the schema`, async () => { - await expect( - schema.validate({ endpoint: 'http://example.com' }) - ).resolves.toHaveProperty('valid', true); - }); - - it(`should indicate the error when an schema is invalid`, async () => { - await expect( - schema.validate({ plugins: [{ hello: 1, world: 2 }] }) - ).resolves.toHaveProperty('valid', false); - }); -}); diff --git a/src/schema.js b/src/schema.js deleted file mode 100644 index 7c1a3fc..0000000 --- a/src/schema.js +++ /dev/null @@ -1,77 +0,0 @@ -const Joi = require('joi'); -const { requestRegex } = require('./shared.js'); - -const pluginSchema = [ - Joi.string(), - Joi.object() - .keys(null) - .max(1) -]; - -const requestSchema = [ - Joi.object() - .keys({ - HEADERS: Joi.object().keys(null), - PAYLOAD: [Joi.object().keys(null), Joi.string()], - PARAMS: Joi.object().keys(null), - FORM: Joi.object().keys(null), - ALIAS: Joi.string().required(), - FORMDATA: Joi.object().keys(null) - }) - .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 }) - .rename(/form/i, 'FORM', { override: true }) - .rename(/alias/i, 'ALIAS', { override: true }), - - Joi.string() -]; - -const hostSchema = Joi.object() - .keys({ - HOST: Joi.string().required(), - ENDPOINT: Joi.string(), - DEFAULTS: Joi.object().keys(null) - }) - .pattern(requestRegex, requestSchema) - .rename(/host/i, 'HOST', { override: true }) - .rename(/defaults/i, 'DEFAULTS', { override: true }) - .rename(/endpoint/i, 'ENDPOINT', { override: true }); - -const schema = Joi.object() - .keys({ - VERSION: Joi.number().integer(), - ENDPOINT: Joi.string().uri(), - PLUGINS: Joi.array().items(pluginSchema), - DEFAULTS: Joi.object(), - ENVIRONMENT: Joi.object(), - HOSTS: Joi.array().items(hostSchema), - COOKIEJAR: Joi.boolean() - }) - .pattern(requestRegex, requestSchema) - .rename(/version/i, 'VERSION', { override: true }) - .rename(/endpoint/i, 'ENDPOINT', { override: true }) - .rename(/hosts/i, 'HOSTS', { override: true }) - .rename(/plugins/i, 'PLUGINS', { override: true }) - .rename(/defaults/i, 'DEFAULTS', { override: true }) - .rename(/environment/i, 'ENVIRONMENT', { override: true }) - .rename(/cookiejar/i, 'COOKIEJAR', { override: true }); - -const validate = async function(config) { - try { - 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 };