From 06774970747d18aa9b316d6384fe6860f668e0f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20D=C3=ADaz?= Date: Wed, 16 May 2018 18:24:52 -0600 Subject: [PATCH] Making improvements to the plugins system. (#22) This is mostly some clean up. Now plugins are checked for existence before being required. The regex for dynamic values was improved a little in an attemp to make it more reliable. Plugins are only required once now. The CLI's CWD is set to that of the config file. This makes relative paths mentioned in the file more reliable. --- bin/cli/base.js | 7 +++ src/__mocks__/requireg.js | 6 +-- src/__tests__/beau.spec.js | 3 ++ src/__tests__/config.spec.js | 81 ++++++++++++++++++----------------- src/__tests__/plugins.spec.js | 39 ++++++----------- src/plugins.js | 57 ++++++++++++++++-------- src/shared.js | 2 +- 7 files changed, 108 insertions(+), 87 deletions(-) diff --git a/bin/cli/base.js b/bin/cli/base.js index 83a6796..e1e7f68 100644 --- a/bin/cli/base.js +++ b/bin/cli/base.js @@ -1,5 +1,6 @@ const yaml = require('js-yaml'); const fs = require('fs'); +const path = require('path'); const dotenv = require('dotenv'); const { Command, flags } = require('@oclif/command'); @@ -22,6 +23,12 @@ class Base extends Command { const envParams = { params: Object.assign(env, params) }; + const configFileDir = path.dirname( + path.resolve(process.cwd(), configFile) + ); + + process.chdir(configFileDir); + return new Beau(config, envParams); } } diff --git a/src/__mocks__/requireg.js b/src/__mocks__/requireg.js index 69fc692..405760b 100644 --- a/src/__mocks__/requireg.js +++ b/src/__mocks__/requireg.js @@ -2,13 +2,13 @@ function requireg(name) { return require(name); } -requireg.std_resolving = false; +requireg.resolving = true; requireg.resolve = function(name) { - if (requireg.std_resolving) { + if (requireg.resolving) { return ''; } else { - throw new Error(`Failed to resolve.`); + return undefined; } }; diff --git a/src/__tests__/beau.spec.js b/src/__tests__/beau.spec.js index e1b4b00..9f7bbcf 100644 --- a/src/__tests__/beau.spec.js +++ b/src/__tests__/beau.spec.js @@ -1,6 +1,9 @@ const yaml = require('js-yaml'); const Beau = require('../beau'); +const requireg = require('requireg'); +requireg.resolving = false; + describe(`Beau's config Loader.`, () => { it('should create a request list', () => { const doc = yaml.safeLoad(` diff --git a/src/__tests__/config.spec.js b/src/__tests__/config.spec.js index cd437f6..bde9f49 100644 --- a/src/__tests__/config.spec.js +++ b/src/__tests__/config.spec.js @@ -1,22 +1,25 @@ const yaml = require('js-yaml'); const Config = require('../config'); +const requireg = require('requireg'); +requireg.resolving = false; + describe('Config', () => { - it('should load valid config keys', () => { - const doc = yaml.safeLoad(` + it('should load valid config keys', () => { + const doc = yaml.safeLoad(` version: 1 endpoint: http://martianwabbit.com shouldntBeAdded: true `); - const config = new Config(doc); - expect(config.ENDPOINT).toBe(doc.endpoint); - expect(config.VERSION).toBe(doc.version); - expect(config.shouldntBeAdded).toBeUndefined(); - }); + const config = new Config(doc); + expect(config.ENDPOINT).toBe(doc.endpoint); + expect(config.VERSION).toBe(doc.version); + expect(config.shouldntBeAdded).toBeUndefined(); + }); - it('should load requests', () => { - const doc = yaml.safeLoad(` + it('should load requests', () => { + const doc = yaml.safeLoad(` endpoint: http://example.com GET /profile: get-profile @@ -28,12 +31,12 @@ describe('Config', () => { hello: world `); - const config = new Config(doc); - expect(Object.keys(config.REQUESTS).length).toBe(4); - }); + const config = new Config(doc); + expect(Object.keys(config.REQUESTS).length).toBe(4); + }); - it('should set up defaults for all requests', () => { - const doc = yaml.safeLoad(` + it('should set up defaults for all requests', () => { + const doc = yaml.safeLoad(` version: 1 endpoint: 'http://jsonplaceholder.typicode.com' @@ -48,16 +51,16 @@ describe('Config', () => { hello: world `); - const config = new Config(doc); + const config = new Config(doc); - expect(config).toMatchSnapshot(); - Object.values(config.REQUESTS).forEach(r => { - expect(r.HEADERS.authentication).toMatch('hello'); - }); + expect(config).toMatchSnapshot(); + Object.values(config.REQUESTS).forEach(r => { + expect(r.HEADERS.authentication).toMatch('hello'); }); + }); - it('should load multiple hosts', () => { - const doc = yaml.safeLoad(` + it('should load multiple hosts', () => { + const doc = yaml.safeLoad(` endpoint: http://example.org defaults: @@ -95,13 +98,13 @@ describe('Config', () => { GET /posts: posts `); - let config = new Config(doc); + let config = new Config(doc); - expect(config).toMatchSnapshot(); - }); + expect(config).toMatchSnapshot(); + }); - it('should namespace all aliases within an host', () => { - const doc = yaml.safeLoad(` + it('should namespace all aliases within an host', () => { + const doc = yaml.safeLoad(` hosts: - host: test1 endpoint: http://example.com @@ -111,14 +114,14 @@ describe('Config', () => { GET /posts: posts `); - let config = new Config(doc); + let config = new Config(doc); - expect(config.REQUESTS[0].ALIAS).toBe('test1:posts'); - expect(config.REQUESTS[1].ALIAS).toBe('test2:posts'); - }); + expect(config.REQUESTS[0].ALIAS).toBe('test1:posts'); + expect(config.REQUESTS[1].ALIAS).toBe('test2:posts'); + }); - it(`should throw if host doesn't have a host key`, () => { - const doc = yaml.safeLoad(` + it(`should throw if host doesn't have a host key`, () => { + const doc = yaml.safeLoad(` hosts: - endpoint: http://example.com GET /posts: posts @@ -128,11 +131,11 @@ describe('Config', () => { GET /posts: posts `); - expect(() => new Config(doc)).toThrow(); - }); + expect(() => new Config(doc)).toThrow(); + }); - it(`should merge host settings with global settings`, () => { - const doc = yaml.safeLoad(` + it(`should merge host settings with global settings`, () => { + const doc = yaml.safeLoad(` defaults: headers: hello: 1 @@ -150,7 +153,7 @@ describe('Config', () => { GET /posts: posts `); - let config = new Config(doc); - expect(config.REQUESTS[0].HEADERS.hello).toBe(1); - }); + let config = new Config(doc); + expect(config.REQUESTS[0].HEADERS.hello).toBe(1); + }); }); diff --git a/src/__tests__/plugins.spec.js b/src/__tests__/plugins.spec.js index f70fb3f..313b0c0 100644 --- a/src/__tests__/plugins.spec.js +++ b/src/__tests__/plugins.spec.js @@ -1,31 +1,15 @@ const yaml = require('js-yaml'); -const Config = require('../config'); const Plugins = require('../plugins'); const Request = require('../request'); const RequestCache = require('../requestCache'); const requireg = require('requireg'); describe(`Beau's plugin system`, () => { - let config; let request; let plugins; - let doc; beforeEach(() => { - doc = yaml.safeLoad(` - version: 1 - endpoint: 'http://example.com' - - plugins: - - Modifiers: - data: hi - - DynamicValues - - GET /posts/$[add(1, 1)]: get-post - `); - - config = new Config(doc); - plugins = config.PLUGINS; + plugins = new Plugins([{ Modifiers: [Object] }, 'DynamicValues'], []); }); it('should load all plugins', () => { @@ -34,19 +18,24 @@ describe(`Beau's plugin system`, () => { expect(plugins.registry.dynamicValues.length).toBe(1); }); - it(`should load autoload plugins`, () => { - requireg.std_resolving = true; - config = new Config(doc); - expect(config.PLUGINS.registry.dynamicValues.length).toBe(2); - requireg.std_resolving = false; - }); - it(`should throw if given an invalid configuration`, () => { expect(() => new Plugins([{ test1: true, test2: true }])).toThrow(); }); it(`shouldn't do anything when given an empty array.`, () => { - expect(new Plugins()).toMatchSnapshot(); + expect(new Plugins([], [])).toMatchSnapshot(); + }); + + it(`should warn if the plugin is not available.`, () => { + const spy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + requireg.resolving = false; + + new Plugins(['not-a-Package']); + expect(spy).toHaveBeenCalled(); + + requireg.resolving = true; + spy.mockReset(); + spy.mockRestore(); }); describe(`Request Modifiers`, () => { diff --git a/src/plugins.js b/src/plugins.js index 264569d..071f549 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -14,32 +14,51 @@ class Plugins { this.context = {}; - plugins.forEach(plugin => this.loadPlugin(plugin)); - autoload.forEach(plugin => { - try { - requireg.resolve(plugin); - this.loadPlugin(plugin); - } catch (e) {} - }); + this.loadPlugins(autoload.concat(plugins)); } - loadPlugin(plugin) { - let name = plugin; - let settings = {}; + normalizePlugins(plugins) { + let results = {}; - if (typeof plugin === 'object') { - let keys = Object.keys(plugin); + plugins.forEach(plugin => { + let name = plugin; + let settings = undefined; - if (keys.length !== 1) { - throw new Error(`Plugin items should contain only one key.`); + if (typeof plugin === 'object') { + let keys = Object.keys(plugin); + + if (keys.length !== 1) { + throw new Error( + `Plugin items should contain only one key.` + ); + } + + name = keys[0]; + settings = plugin[name]; } - name = keys[0]; - settings = plugin[name]; - } + results[name] = settings; + }); - plugin = requireg(`beau-${toKebabCase(name)}`); - new plugin(this, settings); + return results; + } + + loadPlugins(plugins) { + plugins = this.normalizePlugins(plugins); + Object.keys(plugins).forEach(name => { + const module = `beau-${toKebabCase(name)}`; + + if (typeof requireg.resolve(module) !== 'undefined') { + const plugin = requireg(module); + new plugin(this, plugins[name]); + } else { + if (name === 'std') return; + + console.warn( + `Plugin ${name} couldn't be found. It is available globally?` + ); + } + }); } executeModifier(modifier, obj, orig) { diff --git a/src/shared.js b/src/shared.js index 1d1f86d..558d421 100644 --- a/src/shared.js +++ b/src/shared.js @@ -12,7 +12,7 @@ const httpVerbs = [ const requestRegex = new RegExp(`(${httpVerbs.join('|')})\\s(.*)`, 'i'); const replacementRegex = /(?:\\?)\$([a-zA-Z\.\d\-\_\/\\\:]+)/g; -const dynamicValueRegex = /\$\[(\w+\((?:.|[\n\r])+?\))\]/g; +const dynamicValueRegex = /\$\[(\w+\((?:.|[\n\r])*?\))\]/g; const UpperCaseKeys = function(obj) { let result = {};