├── .gitignore ├── utils.js ├── component.js ├── async.js ├── test ├── components │ ├── com_test.js │ ├── com_autoload.js │ └── com_class_test.js ├── utils_test.spec.js ├── config_test.spec.js ├── injector_test.spec.js └── merapi_test.spec.js ├── .travis.yml ├── test.ts ├── lib ├── apm.js ├── async.js ├── async_emitter.js ├── component.js ├── logger.js ├── utils.js ├── config.js └── injector.js ├── .eslintrc.json ├── README.md ├── package.json ├── index.d.ts ├── index.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | coverage -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("./lib/utils"); -------------------------------------------------------------------------------- /component.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("./lib/component"); -------------------------------------------------------------------------------- /async.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = require("./lib/async"); -------------------------------------------------------------------------------- /test/components/com_test.js: -------------------------------------------------------------------------------- 1 | 2 | exports.loader = function() { 3 | return {com:"com_test.js"}; 4 | }; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | install: 5 | - npm install 6 | script: 7 | - npm run cover 8 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | 2 | import merapi from "@yesboss/merapi"; 3 | 4 | function* x() { 5 | 6 | } 7 | 8 | let t = x(); 9 | 10 | let z : Gen -------------------------------------------------------------------------------- /test/components/com_autoload.js: -------------------------------------------------------------------------------- 1 | 2 | exports.loader = function(config) { 3 | config.set("autoloaded", true); 4 | return {com:"com_autoload.js"}; 5 | }; -------------------------------------------------------------------------------- /test/components/com_class_test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const async = require("../../async"); 3 | const Component = require("../../lib/component"); 4 | 5 | class ComClassTest extends Component { 6 | constructor(comTest) { 7 | super(); 8 | this.comTest = comTest; 9 | } 10 | 11 | *testAsync() { 12 | } 13 | 14 | *start() { 15 | 16 | } 17 | 18 | initialize() { 19 | return async.run(function*() { 20 | 21 | }); 22 | } 23 | } 24 | 25 | module.exports = ComClassTest; -------------------------------------------------------------------------------- /lib/apm.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function Start(config) { 4 | config.resolve(); 5 | if (config.data.apm && typeof config.data.apm === "object") { 6 | require("elastic-apm-node").start({ 7 | serviceName: config.data.name, 8 | serverUrl: config.data.apm.server, 9 | active: true, 10 | asyncHooks: true, 11 | logLevel: "info", 12 | captureSpanStackTraces: true, 13 | captureExceptions: true, 14 | }); 15 | } 16 | } 17 | 18 | module.exports = Start; 19 | -------------------------------------------------------------------------------- /lib/async.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const {co} = require("bluebird-co"); 3 | const utils = require("./utils"); 4 | const Promise = require("bluebird"); 5 | 6 | function async(fn) { 7 | if (utils.isGeneratorFunction(fn)) 8 | return co.wrap(fn); 9 | return function() { 10 | return new Promise(function(resolve) { 11 | return resolve(fn.apply(null, arguments)); 12 | }); 13 | }; 14 | } 15 | 16 | async.run = function run(fn) { 17 | return utils.isGeneratorFunction(fn) ? co(fn) : fn(); 18 | }; 19 | 20 | module.exports = async; -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "require-yield": [0], 14 | "indent": [ 15 | "warn", 16 | 4 17 | ], 18 | "linebreak-style": [ 19 | "warn", 20 | "unix" 21 | ], 22 | "quotes": [ 23 | "warn", 24 | "double" 25 | ], 26 | "semi": [ 27 | "warn", 28 | "always" 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/kata-ai/merapi.svg?branch=master)](https://travis-ci.org/kata-ai/merapi) 2 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/80d8831fefdd4309afc91c52ed16634e)](https://www.codacy.com/app/kata-ai/merapi?utm_source=github.com&utm_medium=referral&utm_content=kata-ai/merapi&utm_campaign=Badge_Grade) 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/80d8831fefdd4309afc91c52ed16634e)](https://www.codacy.com/app/kata-ai/merapi?utm_source=github.com&utm_medium=referral&utm_content=kata-ai/merapi&utm_campaign=Coverage_Grade) 4 | 5 | # Merapi 6 | Pluginable Depencency Injection Container for Node.js 7 | 8 | ## Installation 9 | Install using npm: 10 | ``` 11 | npm install merapi --save 12 | ``` 13 | 14 | ## Copyright 15 | Copyright (c) 2015-2017 Kata.ai. All rights reserved. 16 | 17 | 18 | ## Change Log 19 | v0.19.4 - ELASTIC APM support 20 | 21 | ## Contributors 22 | - Ahmad Rizqi Meydiarso (rizqme) - Initial Author 23 | - Roni Kurniawan 24 | - Ikmal Syifai 25 | - Ricky Anders -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "merapi", 3 | "version": "0.21.1", 4 | "description": "Javascript Framework for Microservices", 5 | "main": "./index.js", 6 | "types": "./index.d.ts", 7 | "scripts": { 8 | "test": "./node_modules/.bin/mocha", 9 | "cover": "istanbul cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | codacy-coverage && rm -rf ./coverage" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/kata-ai/merapi.git" 14 | }, 15 | "author": "kata.ai", 16 | "license": "MIT", 17 | "dependencies": { 18 | "bluebird": "^3.5.0", 19 | "bluebird-co": "^2.2.0", 20 | "colors": "^1.1.2", 21 | "dateformat": "^1.0.12", 22 | "esprima": "^4.0.0", 23 | "is-class": "0.0.4", 24 | "lodash": "^4.1.0", 25 | "to-snake-case": "^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "istanbul": "^0.4.5", 29 | "codacy-coverage": "^2.0.2", 30 | "mocha": "^2.4.5", 31 | "mocha-lcov-reporter": "^1.3.0", 32 | "sleep-promise": "^2.0.0", 33 | "then-sleep": "^1.0.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/async_emitter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const async = require("./async"); 4 | const utils = require("./utils"); 5 | 6 | class AsyncEmitter { 7 | 8 | on(event, fn, once) { 9 | if (!this._counter) 10 | this._counter = 0; 11 | if (!this._events) 12 | this._events = {}; 13 | let id = ++this._counter; 14 | if (!this._events[event]) 15 | this._events[event] = []; 16 | if (utils.isGeneratorFunction(fn)) 17 | fn = async(fn); 18 | this._events[event].push({ fn, once, id }); 19 | return id; 20 | } 21 | 22 | once(event, fn) { 23 | return this.on(event, fn, true); 24 | } 25 | 26 | removeListener(event, id) { 27 | if (!this._events) return; 28 | if (this._events[event]) { 29 | this._events[event] = this._events[event].filter(item => item.id !== id); 30 | } 31 | } 32 | 33 | *emit(event, ...args) { 34 | if (!this._events) return; 35 | let events = this._events[event]; 36 | if (!events) return; 37 | let newEvents = []; 38 | for (let i = 0; i < events.length; i++) { 39 | let ret = events[i].fn.apply(null, args); 40 | if (utils.isPromise(ret)) 41 | yield ret; 42 | if (!events[i].once) 43 | newEvents.push(events[i]); 44 | } 45 | this._events[event] = newEvents; 46 | } 47 | 48 | } 49 | 50 | AsyncEmitter.prototype.emit = async(AsyncEmitter.prototype.emit); 51 | module.exports = AsyncEmitter; -------------------------------------------------------------------------------- /lib/component.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const async = require("./async"); 3 | const utils = require("./utils"); 4 | 5 | class Reflection { 6 | 7 | } 8 | 9 | class Component { 10 | 11 | /** 12 | * Component base class 13 | * @constructor 14 | */ 15 | constructor() { 16 | utils.getAllPropertyNames(this).forEach( i => { 17 | if (!/Generator$/.test(i) && utils.isGeneratorFunction(this[i])) { 18 | this[i] = async(this[i]); 19 | } 20 | }); 21 | } 22 | 23 | *_initialize(injector, extra, prev) { 24 | if (this.__deps) { 25 | for (const i in this.__deps) { 26 | this[i] = yield injector.resolve(this.__deps[i], extra, prev); 27 | } 28 | } 29 | 30 | yield this.initialize(); 31 | } 32 | 33 | *initialize() { 34 | 35 | } 36 | 37 | *destroy() { 38 | 39 | } 40 | 41 | static reflect() { 42 | if (this.__reflection__) 43 | return this.__reflection__; 44 | let Parent = Object.getPrototypeOf(this.prototype); 45 | if (Parent.reflect) 46 | this.__reflection__ = new Reflection(this.toString(), Parent.reflect()); 47 | else 48 | this.__reflection__ = new Reflection(this.toString()); 49 | 50 | return this.__reflection__; 51 | } 52 | 53 | static mixin(klass) { 54 | class derived extends this {} 55 | Object.getOwnPropertyNames(klass.prototype).forEach(name => { 56 | derived.prototype[name] = klass.prototype[name]; 57 | }); 58 | return derived; 59 | } 60 | } 61 | 62 | module.exports = Component; -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* eslint no-console: "off" */ 3 | const dateFormat = require("dateformat"); 4 | const colors = require("colors/safe"); 5 | 6 | exports.factory = function($meta, config) { 7 | const stdLevel = 3; 8 | let name = config.default("name", "undefined"); 9 | let level = config.default("merapi.logging.level", stdLevel); 10 | let component = $meta.caller; 11 | 12 | const mapLevel = { 13 | error: 0, 14 | warn: 1, 15 | info: 2, 16 | verbose: 3, 17 | debug: 4, 18 | silly: 5 19 | }; 20 | 21 | let levelInt = typeof level == "string" ? mapLevel[level] || stdLevel : level; 22 | 23 | let label = name + "/" + component; 24 | 25 | return { 26 | error: function() { 27 | if (levelInt >= 0) 28 | console.error.apply(console, [colors.red("[ERROR] " + label), 29 | colors.gray(dateFormat(new Date(), "yyyy-mm-dd HH:MM:ss :"))] 30 | .concat(Array.prototype.slice.call(arguments))); 31 | }, 32 | 33 | warn: function() { 34 | if (levelInt >= 1) 35 | console.warn.apply(console, [colors.yellow("[WARN] " +label), 36 | colors.gray(dateFormat(new Date(), "yyyy-mm-dd HH:MM:ss :"))] 37 | .concat(Array.prototype.slice.call(arguments))); 38 | }, 39 | 40 | info: function() { 41 | if (levelInt >= 2) 42 | console.info.apply(console, [colors.cyan("[INFO] " +label), 43 | colors.gray(dateFormat(new Date(), "yyyy-mm-dd HH:MM:ss :"))] 44 | .concat(Array.prototype.slice.call(arguments))); 45 | }, 46 | 47 | log: function() { 48 | if (levelInt >= 3) 49 | console.log.apply(console, [colors.green("[LOG] " + label), 50 | colors.gray(dateFormat(new Date(), "yyyy-mm-dd HH:MM:ss :"))] 51 | .concat(Array.prototype.slice.call(arguments))); 52 | }, 53 | 54 | debug: function() { 55 | if (levelInt >= 4) 56 | console.log.apply(console, [colors.gray("[DEBUG] " + label), 57 | colors.gray(dateFormat(new Date(), "yyyy-mm-dd HH:MM:ss :"))] 58 | .concat(Array.prototype.slice.call(arguments))); 59 | }, 60 | 61 | trace: (levelInt >= 4) ? console.trace : function() {} 62 | 63 | }; 64 | }; -------------------------------------------------------------------------------- /test/utils_test.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const utils = require("../lib/utils"); 3 | const assert = require("assert"); 4 | 5 | /*global Promise*/ 6 | /*eslint-env mocha*/ 7 | 8 | describe("Util Test", function() { 9 | 10 | it("#isGeneratorFunction()", function(done) { 11 | 12 | function* gen() { 13 | let i = 0; 14 | while(i < 1000) yield i++; 15 | } 16 | 17 | assert(utils.isGeneratorFunction(gen), true); 18 | 19 | done(); 20 | }); 21 | 22 | it("#isGenerator()", function(done) { 23 | 24 | function* gen() { 25 | let i = 0; 26 | while(i < 1000) yield i++; 27 | } 28 | 29 | assert(utils.isGeneratorFunction(gen()), true); 30 | 31 | done(); 32 | }); 33 | 34 | it("#isPromise()", function(done) { 35 | 36 | let promise = new Promise(function(resolve) { 37 | resolve(); 38 | }); 39 | 40 | assert(utils.isPromise(promise), true); 41 | 42 | done(); 43 | }); 44 | 45 | it("#getAllPropertyNames()", function(done) { 46 | 47 | class A { 48 | 49 | a() { 50 | 51 | } 52 | 53 | } 54 | 55 | class B extends A { 56 | b() { 57 | 58 | } 59 | } 60 | 61 | let b = new B(); 62 | let keys = Object.keys(b); 63 | let props = utils.getAllPropertyNames(b); 64 | 65 | assert.equal(keys.indexOf("a"), -1); 66 | assert.equal(keys.indexOf("b"), -1); 67 | assert.notEqual(props.indexOf("a"), -1); 68 | assert.notEqual(props.indexOf("b"), -1); 69 | 70 | done(); 71 | }); 72 | 73 | it("#dependencyNames()", function(done) { 74 | 75 | class Deps { 76 | test() { 77 | 78 | } 79 | constructor(a, b) { 80 | a; 81 | b; 82 | } 83 | xxx() { 84 | 85 | } 86 | } 87 | 88 | function deps(b,c) { 89 | b; 90 | c; 91 | } 92 | 93 | assert.deepEqual(utils.dependencyNames(deps), ["b", "c"]); 94 | assert.deepEqual(utils.dependencyNames(Deps), ["a", "b"]); 95 | 96 | done(); 97 | }); 98 | 99 | it("#instantiate()", function(done) { 100 | 101 | class Class { 102 | 103 | constructor(a, b) { 104 | this.a = a; 105 | this.b = b; 106 | } 107 | 108 | } 109 | 110 | let obj = utils.instantiate(Class, ["a", "b"]); 111 | 112 | assert.notEqual(obj, null); 113 | assert.equal(obj.a, "a"); 114 | assert.equal(obj.b, "b"); 115 | 116 | done(); 117 | }); 118 | 119 | it("#compile()", function(done) { 120 | 121 | let template = utils.compile("Hello {you}! My name is {me}."); 122 | 123 | assert.equal(typeof template, "function"); 124 | assert.equal(template({you:"Bill", me:"Steve"}), "Hello Bill! My name is Steve."); 125 | assert.equal(template({you:"Bill"}), "Hello Bill! My name is ."); 126 | 127 | done(); 128 | }); 129 | 130 | }); -------------------------------------------------------------------------------- /test/config_test.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const async = require("../async"); 4 | const assert = require("assert"); 5 | const Config = require("../lib/config"); 6 | 7 | /*eslint-env mocha*/ 8 | 9 | describe("Config Test", function() { 10 | 11 | let config = Config.create({ 12 | "init": "data" 13 | }); 14 | 15 | before(async(function*() { 16 | config = Config.create({ 17 | "init": "data", 18 | "more.init": "more" 19 | }); 20 | })); 21 | 22 | it("successfully initialize", async(function* () { 23 | assert.notEqual(config, null); 24 | assert.equal(typeof config, "function"); 25 | assert.equal(typeof config.set, "function"); 26 | assert.equal(typeof config.get, "function"); 27 | })); 28 | 29 | it("can get initialized data", async(function* () { 30 | assert.equal(config("init"), "data"); 31 | assert.equal(config("more").init, "more"); 32 | assert.equal(config("more.init"), "more"); 33 | })); 34 | 35 | it("can set data", async(function* () { 36 | config("more.a", "a"); 37 | config("more.b", "b"); 38 | config("more.c", {x:1}); 39 | assert.equal(config("more").a, "a"); 40 | assert.equal(config("more.b"), "b"); 41 | assert.equal(config("more.c.x"), 1); 42 | })); 43 | 44 | it("can resolve data", async(function* () { 45 | config("more.a", "{more.b}"); 46 | config("more.b", "{more.c.x}"); 47 | config("more.c", {x:1}); 48 | config("more.escape", "\\{escaped}"); 49 | 50 | assert.equal(config.resolve("more.a"), 1); 51 | assert.equal(config.resolve("more.b"), 1); 52 | assert.equal(config("more.a"), "{more.b}"); 53 | assert.equal(config("more.escape"), "\\{escaped}"); 54 | 55 | config.resolve(); 56 | 57 | assert.equal(config("more").a, 1); 58 | assert.equal(config("more.b"), 1); 59 | assert.equal(config("more.c.x"), 1); 60 | assert.equal(config("more.escape"), "{escaped}"); 61 | })); 62 | 63 | it("can use alternative delimiter", async(function* () { 64 | let config = Config.create({}, {left:"${", right:"}"}); 65 | config("more.a", "${more.b}"); 66 | config("more.b", "${more.c.x}"); 67 | config("more.c", {x:1}); 68 | config("more.escape", "\\${escaped}"); 69 | 70 | assert.equal(config.resolve("more.a"), 1); 71 | assert.equal(config.resolve("more.b"), 1); 72 | assert.equal(config("more.a"), "${more.b}"); 73 | assert.equal(config("more.escape"), "\\${escaped}"); 74 | 75 | config.resolve(); 76 | 77 | assert.equal(config("more").a, 1); 78 | assert.equal(config("more.b"), 1); 79 | assert.equal(config("more.c.x"), 1); 80 | assert.equal(config("more.escape"), "${escaped}"); 81 | })); 82 | 83 | it("can check value exist", async(function* () { 84 | assert.equal(config.has("init"), true); 85 | assert.equal(config.has("nothing"), false); 86 | })); 87 | 88 | it("can default to value", async(function* () { 89 | assert.equal(config.default("init", "init"), "data"); 90 | assert.equal(config.default("a.b", "nothing"), "nothing"); 91 | })); 92 | 93 | it("can flatten config", async(function* () { 94 | config.set("test", {a:1, b:{c:2}}); 95 | let flat = config.flatten(); 96 | assert.equal(flat["test.a"], 1); 97 | assert.equal(flat["test.b.c"], 2); 98 | })); 99 | }); -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const esprima = require("esprima"); 4 | 5 | /** 6 | * Instantiate a class with arguments 7 | * @param {Class} Class 8 | * @param {Array} arguments 9 | * @returns {Object} 10 | * @private 11 | */ 12 | exports.instantiate = function instantiate(Class, args) { 13 | return new (Function.prototype.bind.apply(Class, [null].concat(args)))(); 14 | }; 15 | 16 | /** 17 | * Check if `obj` is a promise. 18 | * 19 | * @param {Object} obj 20 | * @returns {Boolean} 21 | * @private 22 | */ 23 | exports.isPromise = function isPromise(obj) { 24 | return obj && "function" == typeof obj.then; 25 | }; 26 | 27 | /** 28 | * Check if `obj` is a iterator. 29 | * 30 | * @param {Mixed} obj 31 | * @return {Boolean} 32 | * @api private 33 | */ 34 | 35 | exports.isIterator = function isIterator(obj) { 36 | return obj && "function" == typeof obj.next && "function" == typeof obj.throw; 37 | }; 38 | 39 | /** 40 | * Check if `obj` is a generator function. 41 | * 42 | * @param {Mixed} obj 43 | * @returns {Boolean} 44 | * @private 45 | */ 46 | exports.isGeneratorFunction = function isGeneratorFunction(obj) { 47 | let constructor = obj && obj.constructor; 48 | if (!constructor) return false; 49 | if ("GeneratorFunction" === constructor.name || "GeneratorFunction" === constructor.displayName) return true; 50 | return exports.isIterator(constructor.prototype); 51 | }; 52 | 53 | exports.getAllPropertyNames = function getAllPropertyNames(obj) { 54 | let props = {}; 55 | do { 56 | Object.getOwnPropertyNames(obj).forEach(function(name) {props[name] = true;}); 57 | } while ((obj = Object.getPrototypeOf(obj))); 58 | 59 | return Object.keys(props); 60 | }; 61 | 62 | /** 63 | * Get dependency names from loader function 64 | * 65 | * @method dependencyNames 66 | * @param {Function} fn 67 | * Loader function 68 | * @return {Array} 69 | * Array of names of the dependency 70 | */ 71 | exports.dependencyNames = function dependencyNames(fn) { 72 | let ast = esprima.parse("("+fn.toString()+")"); 73 | 74 | let expression = ast.body[0].expression; 75 | 76 | if (expression.type == "FunctionExpression" || expression.type == "ArrowFunctionExpression") { 77 | return expression.params.map(function(param) { 78 | return param.name; 79 | }); 80 | } 81 | 82 | if (expression.type == "ClassExpression") { 83 | let constructor = expression.body.body.find(function(item) { 84 | return item.kind == "constructor"; 85 | }); 86 | 87 | if (constructor) { 88 | return constructor.value.params.map(function(param) { 89 | return param.name; 90 | }); 91 | } 92 | else { 93 | let parent = Object.getPrototypeOf(fn); 94 | if (parent) 95 | return dependencyNames(parent); 96 | } 97 | } 98 | 99 | return []; 100 | }; 101 | 102 | 103 | function escape(str) { 104 | return str.split("").map(s => "\\"+s).join(""); 105 | } 106 | 107 | 108 | /** 109 | * Taken and modified from string-template 110 | * Copyright (c) Matt Esch 111 | */ 112 | exports.compile = function compile(string, { left = "{", right = "}" } = {}) { 113 | let nargs = new RegExp(escape(left)+"[$0-9_a-zA-Z\\.\\[\\]]+"+escape(right), "g"); 114 | let replacements = string.match(nargs) || []; 115 | let interleave = string.split(nargs); 116 | let replace = []; 117 | 118 | for (let i = 0; i < interleave.length; i++) { 119 | let current = interleave[i]; 120 | let replacement = replacements[i]; 121 | let escapeChar = current.charAt(current.length - 1); 122 | 123 | if (replacement) { 124 | replacement = replacement.substring(left.length, replacement.length - right.length); 125 | } 126 | 127 | if (escapeChar === "\\") { 128 | replace.push(current.substring(0, current.length-1) + left + replacement + right); 129 | } else { 130 | replace.push(current); 131 | if (replacement) { 132 | replace.push({ 133 | name: replacement 134 | }); 135 | } 136 | } 137 | } 138 | 139 | let prev = [""]; 140 | 141 | for (let j = 0; j < replace.length; j++) { 142 | let curr = replace[j]; 143 | 144 | if (String(curr) === curr) { 145 | let top = prev[prev.length - 1]; 146 | 147 | if (String(top) === top) { 148 | prev[prev.length - 1] = top + curr; 149 | } else { 150 | prev.push(curr); 151 | } 152 | } else { 153 | prev.push(curr); 154 | } 155 | } 156 | 157 | replace = prev; 158 | 159 | function template() { 160 | let args; 161 | 162 | if (arguments.length === 1 && typeof arguments[0] === "object") { 163 | args = arguments[0]; 164 | } else { 165 | args = arguments; 166 | } 167 | 168 | if (!args || !("hasOwnProperty" in args)) { 169 | args = {}; 170 | } 171 | 172 | let result = []; 173 | 174 | for (let i = 0; i < replace.length; i++) { 175 | if (i % 2 === 0) { 176 | result.push(replace[i]); 177 | } else { 178 | let argName = replace[i].name; 179 | let arg = args.hasOwnProperty(argName) ? args[argName] : null; 180 | if (arg !== null || arg !== undefined) { 181 | result.push(arg); 182 | } 183 | } 184 | } 185 | 186 | return result.join(""); 187 | } 188 | 189 | template.keys = []; 190 | for (let i = 0; i < replace.length; i++) { 191 | if (i % 2 != 0) { 192 | template.keys.push(replace[i].name); 193 | } 194 | } 195 | 196 | return template; 197 | }; 198 | 199 | exports.isArray = function isArray(obj) { 200 | return Object.prototype.toString.call( obj ) === "[object Array]"; 201 | }; 202 | 203 | exports.extend = require("util")._extend; -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const lo = require("lodash"); 4 | const utils = require("./utils"); 5 | 6 | function trimify(str) { 7 | if (typeof str === "string" || str instanceof String) { 8 | return str.trim(); 9 | } 10 | return str; 11 | } 12 | 13 | /** 14 | * Config creator 15 | * 16 | * @param {Object} data 17 | * Config data 18 | */ 19 | class Config { 20 | 21 | constructor(data, {left = "{", right = "}", lazy = false, recursive=true} = {}) { 22 | this.lazy = lazy; 23 | this.delimiters = {left, right}; 24 | this.recursive = recursive; 25 | this.data = {}; 26 | if (lazy) 27 | this.data = data; 28 | else 29 | this.set(data); 30 | this.logger = console; 31 | } 32 | 33 | /** 34 | * Get configuration entry 35 | * @param {String} path 36 | * configuration path 37 | * @param {Boolean} ignore (optional) 38 | * if set true, won't throw a warning 39 | * if no config found at path 40 | * @return {Any} 41 | * config entry, undefined if nothing 42 | * found 43 | */ 44 | get(path, ignore) { 45 | 46 | if (!path) return this.data; 47 | let val, data = this.data; 48 | path = path.replace(/^\[/g, "").replace(/\[/g, ".").replace(/\]/g, ""); 49 | let parts = path.split(".").map(o => /^(0|[1-9][0-9]*)$/.test(o) ? parseInt(o) : o); 50 | for (let i = 0; i < parts.length; i++) { 51 | val = data && data[parts[i]]; 52 | if (val === undefined) { 53 | if (this.parent && this.parent.has(path)) { 54 | return this.parent.get(path); 55 | } else { 56 | if (!ignore && this.env == "development") 57 | throw new Error("Cannot find config: " + path); 58 | return undefined; 59 | } 60 | } 61 | data = val; 62 | } 63 | 64 | return data; 65 | } 66 | /** 67 | * Set config by path 68 | * @param {String} path 69 | * config path 70 | * @param {Any} data 71 | * config data 72 | * @param {Boolean} ignore 73 | */ 74 | set(path, data, ignore) { 75 | data = trimify(data); 76 | if (arguments.length == 1) { 77 | for (let i in path) { 78 | this.set(i, path[i]); 79 | } 80 | return path; 81 | } 82 | path = path.replace(/^\[/g, "").replace(/\[/g, ".").replace(/\]/g, ""); 83 | if (lo.isObject(data) && !ignore) { 84 | for (let name in data) { 85 | this.set(path + "." + name, data[name]); 86 | } 87 | return data; 88 | } else { 89 | let parts = path.split(".").map(function(o) { 90 | if (/^(0|[1-9][0-9]*)$/.test(o)) return parseInt(o); 91 | return o; 92 | }); 93 | let current = parts.pop(); 94 | let parent = parts.join("."); 95 | let parentData = this.get(parent, true); 96 | if (!lo.isObject(parentData)) { 97 | if (typeof current == "number" && current === 0) 98 | parentData = this.set(parent, [], true); 99 | else 100 | parentData = this.set(parent, {}, true); 101 | } 102 | if (utils.isArray(parentData) && typeof current === "number" && current !== 0 && parentData[current-1] === undefined) { 103 | parentData = this.set(parent, Object.assign({},parentData), true); 104 | } 105 | parentData[current] = data; 106 | return data; 107 | } 108 | } 109 | 110 | /** 111 | * Check if config path exist 112 | * 113 | * @param {String} path 114 | * config path 115 | * @returns {Boolean} 116 | * True if path exists 117 | */ 118 | has(path) { 119 | return this.get(path, true) !== undefined; 120 | } 121 | 122 | /** 123 | * Get or use default value 124 | * @param {String} path 125 | * @param {Any} def 126 | * @returns {Any} 127 | */ 128 | default(path, def) { 129 | return this.has(path) ? this.get(path) : def; 130 | } 131 | 132 | /** 133 | * Internal flatten function 134 | * @param {Object} data 135 | * @returns {Object} 136 | */ 137 | _flatten(data) { 138 | if (!data) data = this.data; 139 | var obj, res = {}; 140 | if (lo.isArray(data) || lo.isObject(data)) { 141 | Object.keys(data).forEach(i => { 142 | if (/^[A-Za-z_$0-9\.\(\)\,]+$/.test(i) && ((lo.isArray(data[i]) && data[i].length > 0) || (lo.isObject(data[i]) && !lo.isArray(data[i])))) { 143 | obj = this._flatten(data[i]); 144 | for (var path in obj) { 145 | res[i+"."+path] = obj[path]; 146 | } 147 | } else { 148 | res[i+""] = data[i]; 149 | } 150 | }); 151 | } 152 | 153 | return res; 154 | } 155 | 156 | /** 157 | * Flatten config 158 | * @param {Object} data 159 | * @returns {Object} 160 | */ 161 | flatten(data) { 162 | let res = this._flatten(data); 163 | let np, ret = {}; 164 | 165 | for (let p in res) { 166 | np = p.replace(/\.(\d+)/g, "[$1]").replace(/\](\d+)\./g, "][$1]").replace(/^(\d+)/, "[$1]"); 167 | ret[np] = this.get(np); 168 | } 169 | 170 | return ret; 171 | } 172 | 173 | resolveValue(val, path = "") { 174 | let tpl = utils.compile(val, this.delimiters); 175 | let params = {}; 176 | if (tpl.keys.length == 1) { 177 | let key = val.substring(this.delimiters.left.length, val.length - this.delimiters.right.length); 178 | if (tpl.keys[0] == key) { 179 | return this.recursive ? this.resolve(key) : this.get(key); 180 | } 181 | 182 | } 183 | if (tpl.keys.length) { 184 | for (let i = 0; i < tpl.keys.length; i++) { 185 | let key = tpl.keys[i]; 186 | let pathArray = path.split("."); 187 | if (pathArray.length) { 188 | pathArray.pop(); 189 | key = key.replace(/^\$\./, pathArray.join(".") + "."); 190 | } 191 | params[tpl.keys[i]] = this.recursive ? this.resolve(key) : this.get(key); 192 | } 193 | return tpl(params); 194 | } 195 | 196 | return tpl(params); 197 | } 198 | 199 | /** 200 | * Resolve config dependency 201 | * @param {String} path 202 | * @returns {Object} 203 | * value 204 | */ 205 | resolve(path, recursive = true) { 206 | if (arguments.length) { 207 | let val = this.get(path); 208 | if (typeof val == "string") { 209 | return this.resolveValue(val, path); 210 | } else if (typeof val == "object" && recursive) { 211 | let subset = this.create({subset:val}, {lazy:false}); 212 | let flat = subset._flatten(); 213 | for (let n in flat) { 214 | let p = n.split("."); 215 | p.shift(); 216 | subset.set(n, this.resolve(path+"."+p.join(".")), true); 217 | } 218 | let res = subset.get("subset"); 219 | return res; 220 | } else { 221 | return val; 222 | } 223 | } 224 | let flat = this.flatten(); 225 | let ret = {}; 226 | for (let n in flat) { 227 | ret[n] = this.set(n, this.resolve(n, false)); 228 | } 229 | return ret; 230 | } 231 | 232 | /** 233 | * Create subconfig by path 234 | * 235 | * @method path 236 | * @param {String} path 237 | * config path 238 | * @return {Config} 239 | * subconfig 240 | */ 241 | path(path, opts) { 242 | return this.create(this.get(path), opts); 243 | } 244 | 245 | /** 246 | * Extend config with data 247 | * @param {Object} data 248 | * @returns {Config} 249 | */ 250 | extend(data) { 251 | let cfg = Config.create(data, this.delimiters); 252 | let flat = cfg.flatten(); 253 | for (let i in flat) { 254 | this.set(i, flat[i]); 255 | } 256 | return this; 257 | } 258 | 259 | /** 260 | * Create new config 261 | * @param {Object} data 262 | * @returns {Config} 263 | * new config 264 | */ 265 | static create(data, opts) { 266 | function config(path, value) { 267 | if (arguments.length >= 2) 268 | return config.set(path, value); 269 | if (typeof path == "string") 270 | return config.get(path); 271 | if (typeof path == "object") 272 | return config.set(path); 273 | config.logger.error("Invalid input"); 274 | } 275 | let cfg = new Config(data, opts); 276 | Object.getOwnPropertyNames(Config.prototype).forEach(name => config[name] = Config.prototype[name]); 277 | lo.extend(config, cfg); 278 | 279 | return config; 280 | } 281 | 282 | /** 283 | * Create new config 284 | * @param {Object} data 285 | * @returns {Config} 286 | * new config 287 | */ 288 | create(data, opts) { 289 | return Config.create(data, Object.assign({lazy:this.lazy, recursive:this.recursive}, this.delimiters, opts)); 290 | } 291 | } 292 | module.exports = Config; 293 | -------------------------------------------------------------------------------- /test/injector_test.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const async = require("../async"); 3 | const Component = require("../lib/component"); 4 | const Injector = require("../lib/injector"); 5 | const utils = require("../lib/utils"); 6 | const assert = require("assert"); 7 | const sleep = require("sleep-promise"); 8 | 9 | /*eslint-env mocha*/ 10 | 11 | describe("Injector Test", function() { 12 | let di = new Injector(); 13 | class ComClassTest extends Component { 14 | constructor(comTest) { 15 | super(); 16 | this.comTest = comTest; 17 | } 18 | 19 | *testAsync() { 20 | } 21 | 22 | initialize() { 23 | return async.run(function*() { 24 | 25 | }); 26 | } 27 | } 28 | 29 | beforeEach(function() { 30 | di = new Injector(); 31 | }); 32 | 33 | describe("Instantiation", function () { 34 | it("can be successfully instantiated", function() { 35 | assert.notEqual(di, null); 36 | }); 37 | }); 38 | 39 | describe("Registration", function() { 40 | it("can register inline loader", function () { 41 | let loader = function (comTest2) { 42 | comTest2; 43 | }; 44 | di.register("comTest1", loader); 45 | 46 | assert.notEqual(di.components["comTest1"], null); 47 | assert.equal(di.components.comTest1.loader, loader); 48 | assert.deepEqual(di.components.comTest1.deps, ["comTest2"]); 49 | }); 50 | 51 | it("can register object", function () { 52 | let object = { 53 | a: 1 54 | }; 55 | 56 | let string = "test"; 57 | di.register("objectTest", object, true); 58 | di.register("stringTest", string, true); 59 | 60 | assert.notEqual(di.components.objectTest, null); 61 | assert.notEqual(di.components.stringTest, null); 62 | assert.equal(di.components.objectTest.object, object); 63 | assert.equal(di.components.stringTest.object, string); 64 | }); 65 | 66 | it("can register inline loader", function () { 67 | let loader = function (comTest2) { 68 | comTest2; 69 | return { 70 | com: comTest2 71 | }; 72 | }; 73 | di.register("comTest1", loader); 74 | 75 | assert.notEqual(di.components.comTest1, null); 76 | assert.equal(di.components.comTest1.loader, loader); 77 | assert.deepEqual(di.components.comTest1.deps, ["comTest2"]); 78 | }); 79 | 80 | it("can register inline factory", function () { 81 | let fac = function ($meta) { 82 | return {meta:$meta}; 83 | }; 84 | di.register("facTest", {factory:fac}); 85 | 86 | assert.notEqual(di.components.facTest, null); 87 | assert.equal(di.components.facTest.factory, fac); 88 | assert.deepEqual(di.components.facTest.deps, ["$meta"]); 89 | }); 90 | 91 | it("can register class", function () { 92 | di.register("comClassTest", ComClassTest); 93 | 94 | assert.notEqual(di.components.comClassTest, null); 95 | assert.equal(di.components.comClassTest.loader, ComClassTest); 96 | assert.deepEqual(di.components.comClassTest.deps, ["comTest"]); 97 | }); 98 | }); 99 | 100 | describe("Resolution", function () { 101 | it("should resolve object component", async(function*() { 102 | let object = {a: 1}; 103 | di.register("objectTest", object, true); 104 | let res = yield di.resolve("objectTest"); 105 | 106 | assert.deepEqual(object, res); 107 | })); 108 | 109 | it("should resolve loader component", async(function*() { 110 | let object = {a: 1}; 111 | 112 | di.register("comTest", function() { 113 | return {obj:object}; 114 | }); 115 | 116 | let com1 = yield di.resolve("comTest"); 117 | let com2 = yield di.resolve("comTest"); 118 | 119 | assert.equal(com1.obj, object); 120 | assert.equal(com2.obj, object); 121 | assert.equal(com1, com2); 122 | })); 123 | 124 | it("should resolve factory component", async(function*() { 125 | let count = 0; 126 | di.register("facTest", {factory:function() { 127 | return {count:++count}; 128 | }}); 129 | 130 | let com1 = yield di.resolve("facTest"); 131 | let com2 = yield di.resolve("facTest"); 132 | 133 | assert.equal(com1.count, 1); 134 | assert.equal(com2.count, 2); 135 | })); 136 | 137 | it("should resolve es6 component", async(function*() { 138 | di.register("comClassTest", ComClassTest); 139 | 140 | di.register("comTest", function() { 141 | return {a:1}; 142 | }); 143 | 144 | let com = yield di.resolve("comTest"); 145 | let comClass = yield di.resolve("comClassTest"); 146 | assert.equal(comClass.comTest, com); 147 | assert.notEqual(utils.isGeneratorFunction(comClass.testAsync), true); 148 | assert.equal(utils.isPromise(comClass.testAsync()), true); 149 | })); 150 | 151 | it("should detect circular dependency", async(function*() { 152 | di.register("com1", function(com2){ 153 | return {com:com2}; 154 | }); 155 | 156 | di.register("com2", function(com3){ 157 | return {com:com3}; 158 | }); 159 | 160 | di.register("com3", function(com1) { 161 | return {com:com1}; 162 | }); 163 | 164 | let err = null; 165 | 166 | try { yield di.resolve("com1"); } catch(e) {err = e;} 167 | 168 | assert.notEqual(err, null); 169 | assert.notEqual(err.toString().indexOf("Circular dependency detected"), -1); 170 | 171 | })); 172 | 173 | it("should not get race condition", async(function*() { 174 | class ComX extends Component { 175 | constructor() { 176 | super(); 177 | this.x = 1; 178 | } 179 | *initialize() { 180 | yield sleep(100); 181 | this.x = 2; 182 | } 183 | } 184 | function ComY(comX) { 185 | return {x:comX.x}; 186 | } 187 | di.register("comX", ComX); 188 | di.register("comY", ComY); 189 | 190 | di.resolve("comX"); 191 | let comY = yield di.resolve("comY"); 192 | assert.equal(comY.x, 2); 193 | })); 194 | 195 | it("should register & resolve alias", async(function*() { 196 | "use strict"; 197 | 198 | di.register("com1", function(){ 199 | return {com:1}; 200 | }); 201 | 202 | di.alias("com2", "com1"); 203 | 204 | let com1 = yield di.resolve("com1"); 205 | let com2 = yield di.resolve("com2"); 206 | assert.equal(com1, com2); 207 | })); 208 | 209 | 210 | it("should resolve method", async(function*() { 211 | "use strict"; 212 | 213 | di.register("com1", function(){ 214 | let hello = "hello"; 215 | return { 216 | com:1, 217 | hello: function(){return hello + this.com;} 218 | }; 219 | }); 220 | 221 | let hello = yield di.resolveMethod("com1.hello"); 222 | assert.equal(hello(), "hello1"); 223 | })); 224 | }); 225 | 226 | 227 | describe("Execute", function () { 228 | it("should execute loader function", async(function*() { 229 | di.register("com1", function(){ 230 | return {com:1}; 231 | }); 232 | 233 | di.register("com2", function(com1){ 234 | return {com:com1}; 235 | }); 236 | 237 | let res = yield di.execute(function(com1, com2) { 238 | return [com1, com2]; 239 | }); 240 | 241 | assert.notEqual(res, null); 242 | assert.notEqual(res[0], null); 243 | assert.notEqual(res[1], null); 244 | assert.equal(res[0].com, 1); 245 | assert.equal(res[1].com.com, 1); 246 | })); 247 | 248 | it("should execute component class", async(function*() { 249 | "use strict"; 250 | 251 | let Component = require("../component"); 252 | di.register("com1", function(){ 253 | return {com:1}; 254 | }); 255 | 256 | di.register("com2", function(com1){ 257 | return {com:com1}; 258 | }); 259 | 260 | let res = yield di.execute(class Exec extends Component { 261 | constructor(com1, com2) { 262 | super(); 263 | this.com1 = com1; 264 | this.com2 = com2; 265 | } 266 | }); 267 | 268 | assert.notEqual(res, null); 269 | assert.notEqual(res.com1, null); 270 | assert.notEqual(res.com2, null); 271 | assert.equal(res.com1.com, 1); 272 | assert.equal(res.com2.com.com, 1); 273 | })); 274 | }); 275 | }); -------------------------------------------------------------------------------- /lib/injector.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const async = require("./async"); 3 | const isClass = require("is-class"); 4 | const utils = require("./utils"); 5 | const Component = require("./component"); 6 | const AsyncEmitter = require("./async_emitter"); 7 | 8 | 9 | class Injector extends Component.mixin(AsyncEmitter) { 10 | /** 11 | * Dependency Injection 12 | * @constructor 13 | * 14 | * @param {string} basepath 15 | * @param {string[]} paths 16 | */ 17 | constructor(options) { 18 | super(); 19 | options = options || {}; 20 | this.parent = options.parent || null; 21 | this.components = {}; 22 | this._resolveLock = {}; 23 | this.logger = options.logger || (this.parent && this.parent.logger) || console; 24 | } 25 | 26 | /** 27 | * Get component names 28 | * @returns {string} 29 | * component names 30 | */ 31 | getComponentNames() { 32 | return Object.keys(this.components); 33 | } 34 | 35 | /** 36 | * Get the descriptor of a component. 37 | * @param {String} name 38 | * name of the component to be registered 39 | */ 40 | getComponentDescriptor(name) { 41 | return this.components[name] ? Object.assign({}, this.components[name]) : null; 42 | } 43 | 44 | alias(aliasName, originalName) { 45 | let desc = this.getComponentDescriptor(originalName); 46 | if (!desc) 47 | throw new Error(`Cannot resolve alias ${aliasName}: component ${originalName} not found`); 48 | this.components[aliasName] = {ref:originalName}; 49 | } 50 | 51 | /** 52 | * Register component to the dependency injection 53 | * container. 54 | * @param {String} name 55 | * name of the component to be registered 56 | * @param {Object} component 57 | * the component loader to be registered 58 | * @param {Boolean} isObj 59 | * set to true, if component is not a loader 60 | */ 61 | register(name, component, isObj) { 62 | this.emit("beforeRegister", name, component, isObj); 63 | if (component && component.__esModule && component.default) 64 | component = component.default; 65 | if (isObj) { 66 | this.components[name] = { 67 | deps: [], 68 | object: component 69 | }; 70 | } else if (typeof component == "function") { 71 | this.components[name] = { 72 | deps: utils.dependencyNames(component), 73 | loader: component 74 | }; 75 | } else if (typeof component == "object") { 76 | if (typeof component.loader == "function" || typeof component.factory == "function") { 77 | component = utils.extend({}, component); 78 | this.components[name] = component; 79 | component.deps = (component.deps && component.deps.length) ? 80 | component.deps : utils.dependencyNames(component.loader || component.factory); 81 | } else if(component.ref && this.components[component.ref]) { 82 | this.components[name] = {ref: component.ref}; 83 | } 84 | } 85 | 86 | if (!this.components[name]) { 87 | throw new Error("Cannot register component '" + name + "'"); 88 | } 89 | 90 | if (utils.isGeneratorFunction(this.components[name].loader)) 91 | this.components[name].loader = async(this.components[name].loader); 92 | if (utils.isGeneratorFunction(this.components[name].factory)) 93 | this.components[name].factory = async(this.components[name].factory); 94 | 95 | this.emit("afterRegister", name, this.components[name]); 96 | } 97 | 98 | *resolve(name, extra, prev) { 99 | prev = prev ? Array.prototype.slice.call(prev) : []; 100 | if (prev.indexOf(name) > -1) { 101 | prev.push(name); 102 | throw new Error("Circular dependency detected for " + name + ": " + prev.join("->")); 103 | } 104 | prev.push(name); 105 | 106 | if (this._resolveLock[name]) 107 | yield this._resolveLock[name]; 108 | this._resolveLock[name] = this._resolve(name, extra, prev) 109 | .then((result) => { 110 | delete this._resolveLock[name]; 111 | return result; 112 | }) 113 | .catch((err) => { 114 | delete this._resolveLock[name]; 115 | return Promise.reject(err); 116 | }); 117 | return yield this._resolveLock[name]; 118 | } 119 | 120 | /** 121 | * Resolve a component by name 122 | * 123 | * @param {String} name 124 | * Component name to be resolved 125 | * @param {Object} extra 126 | * Optional extra dependencies to be injected 127 | * @return {Object} 128 | * Component object 129 | */ 130 | *_resolve(name, extra, prev) { 131 | let com = this.components[name]; 132 | yield this.emit("beforeResolve", name, extra, prev); 133 | if (!com) { 134 | if (this.parent) { 135 | return yield this.parent.resolve(name, extra); 136 | } else { 137 | let errMsg = "Cannot resolve " + name + ": component not registered."; 138 | if (prev && prev.length) 139 | errMsg += "\nrequired by: " + prev.join("->"); 140 | throw new Error(errMsg); 141 | } 142 | } 143 | 144 | if (com.ref) 145 | return yield this.resolve(com.ref, extra, prev); 146 | 147 | extra = extra ? utils.extend({}, extra) : {}; 148 | let meta = { 149 | name: name 150 | }; 151 | 152 | if (extra.$meta) 153 | meta.caller = extra.$meta.name; 154 | extra.$meta = meta; 155 | 156 | if (!com.object) { 157 | let deps; 158 | try { 159 | deps = yield this.dependencies(com.deps, extra, prev); 160 | } catch(e) { 161 | throw e; 162 | } 163 | if (com.factory) { 164 | let res; 165 | if (isClass(com.factory)) { 166 | res = utils.instantiate(com.factory, deps); 167 | if (typeof res._initialize == "function") 168 | yield res._initialize(this, extra, prev); 169 | } else { 170 | res = com.factory.apply({}, deps); 171 | } 172 | if (utils.isPromise(res)) 173 | return yield res; 174 | else 175 | return res; 176 | } 177 | if (com.loader) { 178 | if (isClass(com.loader)) { 179 | com.object = utils.instantiate(com.loader, deps); 180 | if (typeof com.object._initialize == "function") 181 | yield com.object._initialize(this, extra, prev); 182 | } else { 183 | com.object = com.loader.apply({}, deps); 184 | } 185 | if (utils.isPromise(com.object)) 186 | com.object = yield com.object; 187 | } 188 | 189 | if (com.object) 190 | yield this.emit("instantiate", name, com.object); 191 | } 192 | if (!com.object) { 193 | throw new Error(`Cannot resolve component ${name}`); 194 | } 195 | yield this.emit("afterResolve", name, com.object); 196 | return com.object; 197 | } 198 | 199 | *resolveMethod(str) { 200 | let splitted = str.split("."); 201 | let com = yield this.resolve(splitted[0]); 202 | if (com && com[splitted[1]] && typeof com[splitted[1]] === "function") 203 | return com[splitted[1]].bind(com); 204 | throw new Error(`Cannot find function '${splitted[1]}' of component '${splitted[0]}'`); 205 | } 206 | 207 | reflect(name) { 208 | let com = this.components[name]; 209 | if (com.ref) 210 | return this.reflect(com.ref); 211 | let Constructor = com.loader || com.factory; 212 | if (!Constructor || !Constructor.reflect) return null; 213 | return Constructor.reflect(); 214 | } 215 | 216 | /** 217 | * Execute a function using dependency injection 218 | * 219 | * @method execute 220 | * @async 221 | * @param {Function} fn 222 | * function to be executed 223 | * @return {Object} 224 | * return object of the function 225 | */ 226 | *execute(fn) { 227 | let depNames = utils.dependencyNames(fn); 228 | let deps = []; 229 | for (let i = 0; i < depNames.length; i++) { 230 | let dep = yield this.resolve(depNames[i]); 231 | if (!dep) return null; 232 | deps.push(dep); 233 | } 234 | 235 | if (fn.prototype instanceof Component) { 236 | let res = utils.instantiate(fn, deps); 237 | yield res._initialize(this); 238 | return res; 239 | } else { 240 | return fn.apply({}, deps); 241 | } 242 | } 243 | 244 | /** 245 | * Get the dependencies of a component 246 | * 247 | * @method dependencies 248 | * @async 249 | * @param {String} name 250 | * Component name 251 | * @param {Object} extra 252 | * Optional extra dependencies to be injected 253 | * @return {Array} 254 | * Array of dependency components 255 | */ 256 | *dependencies(names, extra, prev) { 257 | let dep, deps = []; 258 | for (let i = 0; i < names.length; i++) { 259 | if (names[i] in extra) { 260 | dep = extra[names[i]]; 261 | } else { 262 | dep = yield this.resolve(names[i], extra, prev); 263 | } 264 | deps.push(dep); 265 | } 266 | return deps; 267 | } 268 | 269 | create(options) { 270 | return new Injector(options); 271 | } 272 | } 273 | 274 | module.exports = Injector; 275 | -------------------------------------------------------------------------------- /test/merapi_test.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const asyn = require("../async"); 3 | const merapi = require("../index"); 4 | const assert = require("assert"); 5 | const Component = require("../component"); 6 | const sleep = require("sleep-promise"); 7 | 8 | process.env.NODE_ENV = process.NODE_ENV || "test"; 9 | 10 | /* eslint-env mocha */ 11 | 12 | describe("Merapi Test", function() { 13 | 14 | 15 | describe("Config", function() { 16 | 17 | let container = null; 18 | 19 | beforeEach(asyn(function*() { 20 | container = merapi({ 21 | delimiters: { 22 | left: "${", 23 | right: "}" 24 | }, 25 | config: { 26 | "package.name": "merapi-test", 27 | "myConfig": "${resolved.a}", 28 | "myEnvConfig": "${resolved.b}", 29 | "myStrEnvConfig": "${resolved.c}", 30 | "myCrlfStrEnvConfig": "${resolved.d}", 31 | "resolved": { 32 | "a": 1 33 | } 34 | }, 35 | 36 | envConfig: { 37 | test: { 38 | "resolved.b": 2, 39 | "resolved.c": "test", 40 | "resolved.d": "test\r", 41 | } 42 | }, 43 | 44 | extConfig: { 45 | more: true 46 | } 47 | }); 48 | 49 | yield container.initialize(); 50 | })); 51 | 52 | it("can resolve config", function() { 53 | assert.notEqual(container, null); 54 | 55 | let myConfig = container.config.get("myConfig"); 56 | let pkg = container.config.get("package"); 57 | assert.equal(myConfig, 1); 58 | assert.equal(pkg.name, "merapi-test"); 59 | }); 60 | 61 | it("can resolve environment config", function() { 62 | let myEnvConfig = container.config.get("myEnvConfig"); 63 | let myStrEnvConfig = container.config.get("myStrEnvConfig"); 64 | let myCrlfStrEnvConfig = container.config.get("myCrlfStrEnvConfig"); 65 | assert.equal(myEnvConfig, 2); 66 | assert.equal(myStrEnvConfig, "test"); 67 | assert.equal(myCrlfStrEnvConfig, "test"); 68 | }); 69 | 70 | it("can resolve extended config", function() { 71 | assert.equal(container.config.get("more"), true); 72 | }); 73 | 74 | it("can resolve environment variables", function() { 75 | let ENV = container.config.get("ENV"); 76 | let env = container.config.get("env"); 77 | 78 | assert.equal(env, process.env.NODE_ENV); 79 | assert.equal(ENV.NODE_ENV, process.env.NODE_ENV); 80 | assert.equal(ENV.PATH, process.env.PATH); 81 | }); 82 | }); 83 | 84 | describe("Components", function() { 85 | 86 | let container = null; 87 | let obj = {}; 88 | 89 | beforeEach(asyn(function*() { 90 | 91 | container = merapi({ 92 | basepath: __dirname, 93 | config: { 94 | "components": { 95 | "comTest": "ComTest", 96 | "comAutoLoad": {"type":"component", path:"ComAutoload", load:true}, 97 | "comClassTest": {"type":"component", "path":"ComClassTest"}, 98 | "comAlias": {"type":"alias", "ref":"comTest"} 99 | }, 100 | "main": "comClassTest" 101 | } 102 | }); 103 | 104 | container.register("obj", obj, true); 105 | container.alias("alias", "obj"); 106 | 107 | yield container.initialize(); 108 | })); 109 | 110 | it("can resolve object", asyn(function*() { 111 | const o = yield container.resolve("obj"); 112 | assert.equal(o, obj); 113 | })); 114 | 115 | it("can resolve alias", asyn(function*() { 116 | const o = yield container.resolve("obj"); 117 | const a = yield container.resolve("alias"); 118 | assert.equal(o, obj); 119 | assert.equal(o, a); 120 | })); 121 | 122 | it("can resolve alias from config", asyn(function*() { 123 | const o = yield container.resolve("comAlias"); 124 | const a = yield container.resolve("comTest"); 125 | assert.equal(o, a); 126 | })); 127 | 128 | it("can auto load component", asyn(function*() { 129 | const config = yield container.resolve("config"); 130 | assert.equal(config.default("autoloaded", false), false); 131 | yield container.start(); 132 | assert.equal(config.default("autoloaded", false), true); 133 | })); 134 | 135 | it("can resolve component loader", asyn(function*() { 136 | const comTest = yield container.resolve("comTest"); 137 | assert.notEqual(comTest, null); 138 | })); 139 | 140 | it("can resolve component class", asyn(function*() { 141 | const comClassTest = yield container.resolve("comClassTest"); 142 | assert.notEqual(comClassTest, null); 143 | assert.notEqual(comClassTest.comTest, null); 144 | })); 145 | 146 | it("should throw error if component type is not defined", asyn(function*() { 147 | const con = merapi({ 148 | basepath: __dirname, 149 | config: { 150 | components: { 151 | invalid: {type:"invalid"} 152 | } 153 | } 154 | }); 155 | 156 | let error = null; 157 | try { 158 | yield con.initialize(); 159 | } catch(e) { 160 | error = e; 161 | } 162 | 163 | assert.notEqual(error, null); 164 | assert.equal(error.message, "Cannot register component of unknown type: invalid"); 165 | })); 166 | 167 | it("should throw error if component is not found", asyn(function*() { 168 | const con = merapi({ 169 | basepath: __dirname, 170 | config: { 171 | components: { 172 | invalid: "InvalidComponent" 173 | } 174 | } 175 | }); 176 | 177 | let error = null; 178 | try { 179 | yield con.initialize(); 180 | } catch(e) { 181 | error = e; 182 | } 183 | 184 | assert.notEqual(error, null); 185 | assert.equal(error.code, "MODULE_NOT_FOUND"); 186 | })); 187 | 188 | it("should throw error if component definition is invalid", asyn(function*() { 189 | const con = merapi({ 190 | basepath: __dirname, 191 | config: { 192 | components: { 193 | invalid: false 194 | } 195 | } 196 | }); 197 | 198 | let error = null; 199 | try { 200 | yield con.initialize(); 201 | } catch(e) { 202 | error = e; 203 | } 204 | 205 | assert.notEqual(error, null); 206 | assert.equal(error.message, "Invalid component definition for invalid"); 207 | })); 208 | 209 | it("should warn if main is not defined", asyn(function*() { 210 | const con = merapi({ 211 | basepath: __dirname, 212 | config: { 213 | components: { 214 | } 215 | } 216 | }); 217 | 218 | const logger = yield con.resolve("logger"); 219 | let warningMessage = null; 220 | logger.warn = (msg) => { 221 | warningMessage = msg; 222 | }; 223 | con.register("logger", logger, true); 224 | 225 | yield con.start(); 226 | 227 | assert.notEqual(warningMessage, null); 228 | assert.equal(warningMessage, "No main defined"); 229 | })); 230 | }); 231 | 232 | describe("Starter", function() { 233 | 234 | it("can start a main module from component loader", asyn(function*() { 235 | 236 | let container = merapi({ 237 | basepath: __dirname, 238 | config: { 239 | } 240 | }); 241 | 242 | let testVal = 0; 243 | 244 | container.register("mainCom", function() { 245 | return { 246 | start() { 247 | testVal = 1; 248 | } 249 | }; 250 | }); 251 | 252 | let config = yield container.resolve("config"); 253 | 254 | config.set("main", "mainCom"); 255 | 256 | assert.equal(testVal, 0); 257 | yield container.initialize(); 258 | assert.equal(testVal, 0); 259 | yield container.start(); 260 | assert.equal(testVal, 1); 261 | })); 262 | 263 | it("can start a main module from component class", asyn(function*() { 264 | let testVal = 0; 265 | let container = merapi({ 266 | basepath: __dirname, 267 | config: { 268 | main: "mainCom" 269 | } 270 | }); 271 | 272 | container.register("mainCom", class MainComponent extends Component { 273 | *start() { 274 | yield sleep(1); 275 | testVal = 1; 276 | } 277 | }); 278 | 279 | 280 | assert.equal(testVal, 0); 281 | yield container.initialize(); 282 | assert.equal(testVal, 0); 283 | yield container.start(); 284 | assert.equal(testVal, 1); 285 | 286 | })); 287 | }); 288 | }); -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for merapi 2 | // Project: merapi 3 | // Definitions by: Ahmad Rizqi Meydiarso 4 | 5 | declare module "merapi" { 6 | 7 | export default function merapi (options : IContainerOptions) : Container; 8 | 9 | export interface IContainerEventHandlers { 10 | onBeforePluginInit? : (c : Container) => Promise | void; 11 | onAfterPluginInit? : (c : Container) => Promise | void; 12 | onBeforeConfigResolve? : (c : Container) => Promise | void; 13 | onAfterConfigResolve? : (c : Container) => Promise | void; 14 | onBeforeComponentRegister? : (name : string, com: any, isObj: boolean) => Promise | void; 15 | onAfterComponentRegister? : (name : string, desc : {deps:string[], object: any, loader: IClosure | IClass, factory: IClosure | IClass}) => Promise | void; 16 | onBeforeComponentResolve? : (name : string, deps : IHash, prev : string[]) => Promise | void; 17 | onAfterComponentResolve? : (name : string, component : any) => Promise | void; 18 | onComponentInstantiate? : (name : string, component : any) => Promise | void; 19 | onUncaughtException? : (e : Error) => Promise | void; 20 | } 21 | 22 | export interface IContainerOptions extends IContainerEventHandlers { 23 | basepath : string; 24 | config : Json; 25 | extConfig? : Json; 26 | envConfig? : IHash; 27 | delimiters? : {left:string, right:string}; 28 | } 29 | 30 | export interface IClass { 31 | prototype : T; 32 | new() : T; 33 | } 34 | 35 | export interface IClass { 36 | new (...args : any[]) : T; 37 | } 38 | 39 | export interface IEventHandler { 40 | (...args : any[]) : void; 41 | } 42 | 43 | export interface IAsyncEventHandler { 44 | (...args : any[]) : Promise; 45 | } 46 | 47 | export interface IComponent { 48 | initialize() : Promise; 49 | } 50 | 51 | export interface IClosure { 52 | (...deps : any []) : T; 53 | } 54 | 55 | export type Json = null|string|number|boolean|JsonObject|JsonArray; 56 | 57 | export interface JsonObject { 58 | [x: string]: Json; 59 | } 60 | 61 | export interface JsonArray extends Array { } 62 | 63 | export interface IComponentResolver { 64 | (type : string, options : Json) : T; 65 | } 66 | 67 | export interface IComponentDescriptor { 68 | deps?: string[], 69 | object?: Component, 70 | factory?: IClosure | IClass, 71 | loader?: IClosure | IClass 72 | } 73 | 74 | export interface IHash { 75 | [i : string] : T; 76 | } 77 | 78 | export interface IPluginDescriptor { 79 | [i : string] : Function; 80 | } 81 | 82 | /** 83 | * Simple Injectable Component 84 | */ 85 | export class Component implements IComponent { 86 | /** 87 | * Called when component is initialized. 88 | * Will convert generator functions to promises 89 | */ 90 | initialize() : Promise; 91 | /** 92 | * Create a component 93 | */ 94 | constructor (); 95 | 96 | static mixin(klass : {new(): T}) : IClass; 97 | } 98 | 99 | export interface IAsyncEmitter { 100 | on(event : T, fn : IEventHandler | IAsyncEventHandler, once? : boolean) : number; 101 | once(event : T, fn : IEventHandler | IAsyncEventHandler) : number; 102 | removeListener(event : T, id : number) : void; 103 | emit(event : T, ...args : any[]) : Promise; 104 | } 105 | 106 | /** 107 | * Component with asynchronous event emitter 108 | */ 109 | export class AsyncEmitter implements IAsyncEmitter { 110 | /** 111 | * Attach an event handler 112 | */ 113 | on(event : T, fn : IEventHandler | IAsyncEventHandler, once? : boolean) : number; 114 | /** 115 | * Attach an event handler for one trigger 116 | * only 117 | */ 118 | once(event : T, fn : IEventHandler | IAsyncEventHandler) : number; 119 | /** 120 | * Remove listener by specific id 121 | */ 122 | removeListener(event : T, id : number) : void; 123 | /** 124 | * Emit an event with provided arguments 125 | * asynchronously 126 | */ 127 | emit(event : T, ...args : any[]) : Promise; 128 | /** 129 | * Create an AsyncEmitter 130 | */ 131 | constructor(); 132 | } 133 | 134 | export interface IContainer { 135 | alias(aliasName : string, originName : string) : void; 136 | register(name : string, type : string | IClosure | T, options : boolean | Json) : void; 137 | registerComponentType(type : string, resolver : IComponentResolver) : void; 138 | resolve(name : string, deps : IHash, prev : string[]) : Promise | null; 139 | start() : Promise; 140 | stop() : Promise; 141 | get(name : string) : Promise; 142 | initPlugins(desc : string[] | IHash) : void; 143 | initPlugin(name : string, options : Json) : void; 144 | registerPlugin(name : string, plugin : IPluginDescriptor) : void; 145 | initialize() : Promise; 146 | } 147 | 148 | export class Container extends AsyncEmitter implements IContainer { 149 | /** 150 | * Link component to other name 151 | */ 152 | alias(aliasName : string, originName : string) : void; 153 | /** 154 | * Register a component 155 | */ 156 | register(name : string, type : string | IClosure | T, options? : boolean | Json) : void; 157 | /** 158 | * Register a component type 159 | */ 160 | registerComponentType(type : string, resolver : IComponentResolver) : void; 161 | /** 162 | * Resolve a component 163 | */ 164 | resolve(name : string, deps : IHash, prev : string[]) : Promise | null; 165 | /** 166 | * Start container 167 | */ 168 | start() : Promise; 169 | /** 170 | * Stop container 171 | */ 172 | stop() : Promise; 173 | /** 174 | * Get component synchronously 175 | */ 176 | get(name : string) : T; 177 | /** 178 | * Initialize plugins from node_modules 179 | */ 180 | initPlugins(desc : string[] | IHash) : void; 181 | /** 182 | * Initialize plugin from node_modules 183 | */ 184 | initPlugin(name : string, options : Json) : void; 185 | /** 186 | * Register plugin manually 187 | */ 188 | registerPlugin(name : string, plugin : IPluginDescriptor) : void; 189 | initialize() : Promise; 190 | /** 191 | * 192 | */ 193 | constructor (options : IContainerOptions); 194 | } 195 | 196 | export interface IConfigReader { 197 | (path : string, ignore? : boolean) : T; 198 | get() : IHash; 199 | get(path : string, ignore? : boolean) : T; 200 | has(path : string) : boolean; 201 | default(path : string, value : T) : T; 202 | flatten(data? : Json) : IHash; 203 | path(s : string) : IConfigReader; 204 | resolve(path : string) : T; 205 | } 206 | 207 | export interface IConfig extends IConfigReader { 208 | (path : string | IHash, value? : any, ignore? : boolean) : void; 209 | set(path : string | IHash, value? : any, ignore? : boolean) : void; 210 | resolve(path? : string) : T; 211 | path(s : string) : IConfig; 212 | extend(data : Json) : IConfig; 213 | create(data : Json, opts? : {left?:string, right?:string, lazy?: boolean, recursive?: boolean}) : IConfig; 214 | } 215 | 216 | export class Config { 217 | get() : IHash; 218 | get(path : string, ignore? : boolean) : T; 219 | set(path : string | IHash, value? : any, ignore? : boolean) : void; 220 | has(path : string) : boolean; 221 | default(path : string, value : T) : T; 222 | flatten(data? : Json) : IHash; 223 | resolve(path? : string) : T; 224 | path(s : string) : IConfig; 225 | extend(data : Json) : IConfig; 226 | create(data : Json, opts? : {left?:string, right?:string, lazy?: boolean, recursive?: boolean}) : IConfig; 227 | static create(data : Json, opts? : {left?:string, right?:string, lazy?: boolean, recursive?: boolean}) : IConfig; 228 | constructor(data : Json, opts? : {left?:string, right?:string, lazy?: boolean, recursive?: boolean}); 229 | } 230 | 231 | export interface ILogger extends Console { 232 | 233 | } 234 | 235 | export interface IInjector { 236 | getComponentNames() : string[]; 237 | getComponentDescriptor(name : string) : IComponentDescriptor; 238 | alias(aliasName : string, originalName : string) : void; 239 | register(name : string, type? : string | IClosure | T | IComponentDescriptor, options? : boolean | Json) : void; 240 | resolve(name : string, deps? : IHash, prev? : string[]) : Promise; 241 | resolveMethod(str : string) : Function | null; 242 | execute(fn : Function) : any; 243 | dependencies(names : string[], deps? : IHash, prev? : string[]) : Component[]; 244 | create(options : IHash) : Injector; 245 | } 246 | 247 | export class Injector extends Component implements IInjector { 248 | getComponentNames() : string[]; 249 | getComponentDescriptor(name : string) : IComponentDescriptor; 250 | alias(aliasName : string, originalName : string) : void; 251 | register(name : string, type? : string | IClosure | T | IComponentDescriptor, options? : boolean | Json) : void; 252 | resolve(name : string, deps? : IHash, prev? : string[]) : Promise; 253 | resolveMethod(str : string) : Function | null; 254 | execute(fn : Function) : any; 255 | dependencies(names : string[], deps? : IHash, prev? : string[]) : Component[]; 256 | create(options : IHash) : Injector; 257 | constructor(options : IHash); 258 | } 259 | 260 | export module utils { 261 | function instantiate(Class : {new(): T}, args : any[]) : T; 262 | function isPromise(obj : any) : obj is Promise; 263 | function isIterator(obj : any) : obj is Iterator; 264 | function isGeneratorFunction(obj : any) : obj is GeneratorFunction; 265 | function isArray(obj : any) : obj is Array; 266 | function getAllPropertyNames(obj : Object) : string[]; 267 | function dependencyNames(fn : Function) : string[]; 268 | function compile(str: string) : (dict : IHash) => string; 269 | function extend(target: X, origin : Y) : X & Y; 270 | function extend(target: X, a : Y, b : Z) : X & Y & Z; 271 | } 272 | 273 | export function async(fn : Function) : (...args: any[]) => Promise; 274 | 275 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const asyn = require("./lib/async"); 4 | const path = require("path"); 5 | const snake = require("to-snake-case"); 6 | 7 | const AsyncEmitter = require("./lib/async_emitter"); 8 | const Config = require("./lib/config"); 9 | const Injector = require("./lib/injector"); 10 | const Component = require("./lib/component"); 11 | const Logger = require("./lib/logger"); 12 | const utils = require("./lib/utils"); 13 | const apm = require("./lib/apm"); 14 | 15 | function merapi(options) { 16 | return new Container(options); 17 | } 18 | 19 | class Container extends Component.mixin(AsyncEmitter) { 20 | 21 | constructor(options) { 22 | super(); 23 | 24 | this.logger = console; 25 | 26 | this.types = {}; 27 | this.plugins = {}; 28 | this._loadOnStartList = []; 29 | this._isInitialized = false; 30 | this._isShuttingDown = false; 31 | 32 | this.registerComponentType("component", this.resolveComponentDescriptor.bind(this)); 33 | this.registerComponentType("alias", this.resolveAliasDescriptor.bind(this)); 34 | 35 | this.options = options; 36 | this.basepath = options.basepath; 37 | 38 | for (let i in options) { 39 | if (/^on[A-Z].*/.test(i)) { 40 | let eventName = i.substring(2); 41 | eventName = eventName.charAt(0).toLowerCase() + eventName.substring(1); 42 | this.on(eventName, options[i].bind(this)); 43 | } 44 | } 45 | 46 | this.config = Config.create(this.options.config, this.options.delimiters || { left: "{", right: "}" }); 47 | 48 | this.config.env = process.env.NODE_ENV || "development"; 49 | this.config.set("env", this.config.env); 50 | Object.keys(process.env).forEach(key => { 51 | this.config.set("ENV." + key, process.env[key]); 52 | this.config.set("$" + key, process.env[key]); 53 | }); 54 | 55 | if (this.options.envConfig && this.options.envConfig[this.config.env]) 56 | this.config.extend(this.options.envConfig[this.config.env]); 57 | 58 | if (this.options.extConfig) 59 | this.config.extend(this.options.extConfig); 60 | 61 | process.setMaxListeners(0); 62 | process.on("exit", this.emit.bind(this, "exit")); 63 | process.on("SIGINT", this.emit.bind(this, "exit")); 64 | process.on("SIGTERM", this.emit.bind(this, "exit")); 65 | this.on("exit", this.stop.bind(this)); 66 | this.on("uncaughtException", this.handleUncaughtException.bind(this)); 67 | process.on("unhandledRejection", this.emit.bind(this, "uncaughtException")); 68 | 69 | this.injector = new Injector(); 70 | this.injector.on("beforeRegister", this.emit.bind(this, "beforeComponentRegister")); 71 | this.injector.on("afterRegister", this.emit.bind(this, "afterComponentRegister")); 72 | this.injector.on("beforeResolve", this.emit.bind(this, "beforeComponentResolve")); 73 | this.injector.on("afterResolve", this.emit.bind(this, "afterComponentResolve")); 74 | this.injector.on("instantiate", this.emit.bind(this, "componentInstantiate")); 75 | 76 | this.injector.register("basepath", this.basepath, true); 77 | this.injector.register("config", this.config, true); 78 | this.injector.register("injector", {factory:($meta) => { 79 | return Object.assign(new Injector(),this.injector, { 80 | resolve: (name, extra, prev) => { 81 | return this.injector.resolve(name, extra, [$meta.caller].concat(prev|| [])); 82 | } 83 | }); 84 | }}); 85 | this.injector.register("container", this, true); 86 | this.injector.register("logger", require("./lib/logger")); 87 | } 88 | 89 | handleUncaughtException(e) { 90 | this.logger.error(e); 91 | } 92 | 93 | alias(aliasName, oriName) { 94 | return this.injector.alias(aliasName, oriName); 95 | } 96 | 97 | register(name, type, options) { 98 | if (typeof options == "boolean") 99 | return this.injector.register(name, type, options); 100 | if (typeof type == "function" || typeof type == "object") 101 | return this.injector.register(name, type); 102 | 103 | return this.injector.register(name, this.loadComponent(name, type, options)); 104 | } 105 | 106 | registerComponentType(type, resolver) { 107 | this.types[type] = resolver; 108 | } 109 | 110 | loadComponent(name, type, options) { 111 | let resolver = this.types[type]; 112 | if (!resolver) 113 | throw new Error("Cannot register component of unknown type: " + type); 114 | return resolver(name, options); 115 | } 116 | 117 | resolve() { 118 | return this.injector.resolve.apply(this.injector, arguments); 119 | } 120 | 121 | resolveComponentDescriptor(com, opt) { 122 | let paths = this.config.default("merapi.injector.paths", "./components"); 123 | if (typeof paths == "string") 124 | paths = [paths]; 125 | let error = null; 126 | for (let i = 0; i < paths.length; i++) { 127 | let split = opt.path.split("/"); 128 | let name = split.pop(); 129 | let folder = split.join("/"); 130 | let p = path.resolve(this.basepath, paths[i], folder, snake(name)); 131 | try { 132 | return require(p); 133 | } catch (e) { 134 | error = e; 135 | } 136 | } 137 | if (error) 138 | throw error; 139 | } 140 | 141 | resolveAliasDescriptor(com, opt) { 142 | return { ref: opt.ref }; 143 | } 144 | 145 | *initialize() { 146 | if (this._isInitialized) return; 147 | if (this._isInitializing) return yield this._isInitializing; 148 | this._isInitializing = this._initialize(); 149 | yield this._isInitializing; 150 | this._isInitialized = true; 151 | this._isInitializing = null; 152 | } 153 | 154 | *_initialize() { 155 | yield this.emit("beforeInit", this); 156 | yield this.emit("beforeConfigResolve", this); 157 | this.config.resolve(); 158 | yield this.emit("afterConfigResolve", this); 159 | 160 | yield this.emit("beforePluginInit", this); 161 | this.initPlugins(this.options.plugins); 162 | this.initPlugins(this.config.default("plugins", {})); 163 | yield this.emit("afterPluginInit", this); 164 | 165 | yield this.emit("beforeComponentsRegister", this); 166 | 167 | for (let i in this.plugins) { 168 | let plugin = this.plugins[i]; 169 | if (typeof plugin.initialize == "function") { 170 | if (utils.isGeneratorFunction(plugin.initialize)) 171 | plugin.initialize = asyn(plugin.initialize); 172 | let ret = plugin.initialize(this); 173 | if (utils.isPromise(ret)) 174 | yield ret; 175 | } 176 | } 177 | 178 | let componentDefinition = this.config.default("components", {}); 179 | for (let name in componentDefinition) { 180 | let opt = componentDefinition[name]; 181 | if (typeof opt == "string") { 182 | opt = { 183 | path: opt, 184 | type: "component" 185 | }; 186 | } else if (typeof opt != "object") { 187 | throw new Error("Invalid component definition for " + name); 188 | } 189 | this.register(name, opt.type, opt); 190 | if (opt.load) 191 | this._loadOnStartList.push(name); 192 | } 193 | yield this.emit("afterComponentsRegister", this); 194 | yield this.emit("init", this); 195 | 196 | this.config.logger = yield this.injector.resolve("logger", { $meta: { caller: "config" } }); 197 | this.injector.logger = yield this.injector.resolve("logger", { $meta: { caller: "injector" } }); 198 | this.logger = yield this.injector.resolve("logger", { $meta: { caller: "container", name: "merapi" }, }); 199 | 200 | yield this.emit("afterInit", this); 201 | } 202 | 203 | *start() { 204 | apm(this.config); 205 | yield this.emit("beforeStart", this); 206 | let main = this.config("main"); 207 | 208 | yield this.initialize(); 209 | if (!main) { 210 | this.logger.warn("No main defined"); 211 | } 212 | 213 | for (let i = 0; i < this._loadOnStartList.length; i++) 214 | yield this.injector.resolve(this._loadOnStartList[i]); 215 | 216 | yield this.emit("start", this); 217 | if (main) { 218 | let com = yield this.get(main); 219 | let argv = [].concat(process.argv); 220 | if (com) { 221 | if (com.start) { 222 | let res; 223 | if (utils.isGeneratorFunction(com.start)) 224 | res = asyn(com.start).apply(com, [argv]); 225 | else 226 | res = com.start(argv); 227 | if (utils.isPromise(res)) 228 | yield res; 229 | } else { 230 | this.logger.warn("Main component doesn't have start function"); 231 | } 232 | } else { 233 | this.logger.warn(`Main component ${main} not found`); 234 | } 235 | } 236 | 237 | yield this.emit("afterStart", this); 238 | } 239 | 240 | *get(name) { 241 | let main = this.config("main"); 242 | yield this.initialize(); 243 | if (!name) 244 | return yield this.resolve(main); 245 | else 246 | return yield this.resolve(name); 247 | } 248 | 249 | *stop() { 250 | if (this._isShuttingDown) return; 251 | this._isShuttingDown = true; 252 | this.logger.info("Received kill signal"); 253 | this.logger.info("Exiting process in 3sec..."); 254 | yield this.emit("beforeStop", this); 255 | yield this.emit("stop", this); 256 | yield this.emit("afterStop", this); 257 | this.cleanUp(); 258 | setTimeout(() => { 259 | process.exit(); 260 | }, 3000); 261 | } 262 | 263 | cleanUp() { 264 | let components = this.injector.components; 265 | for(let i in components) { 266 | let com = components[i].object; 267 | if (com && com.destroy) { 268 | com.destroy(); 269 | } 270 | } 271 | } 272 | 273 | initPlugins(desc) { 274 | if (utils.isArray(desc)) { 275 | desc.forEach(plugin => this.initPlugin(plugin, {})); 276 | } else if (typeof desc === "object") { 277 | Object.keys(desc).forEach(plugin => { 278 | if (desc[plugin]) { 279 | let options = typeof desc[plugin] === "object" ? desc[plugin] : {}; 280 | this.initPlugin(plugin, options); 281 | } 282 | }); 283 | } 284 | } 285 | 286 | initPlugin(name, options) { 287 | let plugin; 288 | let split = name.split("@"); 289 | if (split.length > 1) { 290 | let org = split[1]; 291 | let name = split[0]; 292 | plugin = require(`@${org}/merapi-plugin-${name}`)(this, options); 293 | } else { 294 | plugin = require(`merapi-plugin-${name}`)(this, options); 295 | } 296 | if (plugin.dependencies) 297 | plugin.dependencies.forEach(dep => { 298 | if (!this.plugins[dep]) 299 | this.initPlugin(dep); 300 | }); 301 | this.registerPlugin(name, plugin); 302 | } 303 | 304 | registerPlugin(name, plugin) { 305 | if (this.plugins[name]) 306 | return; 307 | this.plugins[name] = plugin; 308 | 309 | for (let i in plugin) { 310 | if (/^type[A-Z]\w*/.test(i)) { 311 | let typeName = i.substring(4); 312 | typeName = typeName.charAt(0).toLowerCase() + typeName.substring(1); 313 | this.registerComponentType(typeName, plugin[i].bind(plugin)); 314 | } 315 | if (/^on[A-Z]\w*/.test(i)) { 316 | if (utils.isGeneratorFunction(plugin[i])) 317 | plugin[i] = asyn(plugin[i]); 318 | let eventName = i.substring(2); 319 | eventName = eventName.charAt(0).toLowerCase() + eventName.substring(1); 320 | this.on(eventName, plugin[i].bind(plugin)); 321 | } 322 | if (!/Generator$/.test(i) && utils.isGeneratorFunction(plugin[i])) { 323 | plugin[i] = asyn(plugin[i]); 324 | } 325 | } 326 | } 327 | } 328 | 329 | merapi.Container = Container; 330 | merapi.Component = Component; 331 | merapi.AsyncEmitter = AsyncEmitter; 332 | merapi.Logger = Logger; 333 | merapi.Injector = Injector; 334 | merapi.Config = Config; 335 | merapi.utils = utils; 336 | merapi.async = asyn; 337 | merapi.default = merapi; 338 | 339 | 340 | Object.defineProperty(merapi, "__esModule", { value: true }); 341 | module.exports = merapi; 342 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abbrev@1, abbrev@1.0.x: 6 | version "1.0.9" 7 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" 8 | 9 | ajv@^6.5.5: 10 | version "6.12.2" 11 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" 12 | dependencies: 13 | fast-deep-equal "^3.1.1" 14 | fast-json-stable-stringify "^2.0.0" 15 | json-schema-traverse "^0.4.1" 16 | uri-js "^4.2.2" 17 | 18 | amdefine@>=0.0.4: 19 | version "1.0.1" 20 | resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" 21 | 22 | argparse@^1.0.7: 23 | version "1.0.10" 24 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 25 | dependencies: 26 | sprintf-js "~1.0.2" 27 | 28 | array-find-index@^1.0.1: 29 | version "1.0.2" 30 | resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" 31 | 32 | asn1@~0.2.3: 33 | version "0.2.4" 34 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" 35 | dependencies: 36 | safer-buffer "~2.1.0" 37 | 38 | assert-plus@1.0.0, assert-plus@^1.0.0: 39 | version "1.0.0" 40 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 41 | 42 | async@1.x: 43 | version "1.5.2" 44 | resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" 45 | 46 | asynckit@^0.4.0: 47 | version "0.4.0" 48 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 49 | 50 | aws-sign2@~0.7.0: 51 | version "0.7.0" 52 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" 53 | 54 | aws4@^1.8.0: 55 | version "1.9.1" 56 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" 57 | 58 | balanced-match@^1.0.0: 59 | version "1.0.0" 60 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 61 | 62 | bcrypt-pbkdf@^1.0.0: 63 | version "1.0.2" 64 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" 65 | dependencies: 66 | tweetnacl "^0.14.3" 67 | 68 | bluebird-co@^2.2.0: 69 | version "2.2.0" 70 | resolved "https://registry.yarnpkg.com/bluebird-co/-/bluebird-co-2.2.0.tgz#e0a49e63b6b17e34c91f48b3934b769a377fbed7" 71 | 72 | bluebird@^3.5.0: 73 | version "3.5.0" 74 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" 75 | 76 | bluebird@^3.5.x: 77 | version "3.7.2" 78 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" 79 | 80 | brace-expansion@^1.0.0: 81 | version "1.1.11" 82 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 83 | dependencies: 84 | balanced-match "^1.0.0" 85 | concat-map "0.0.1" 86 | 87 | builtin-modules@^1.0.0: 88 | version "1.1.1" 89 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" 90 | 91 | camelcase-keys@^2.0.0: 92 | version "2.1.0" 93 | resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" 94 | dependencies: 95 | camelcase "^2.0.0" 96 | map-obj "^1.0.0" 97 | 98 | camelcase@^2.0.0: 99 | version "2.1.1" 100 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" 101 | 102 | caseless@~0.12.0: 103 | version "0.12.0" 104 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" 105 | 106 | codacy-coverage@^2.0.2: 107 | version "2.1.1" 108 | resolved "https://registry.yarnpkg.com/codacy-coverage/-/codacy-coverage-2.1.1.tgz#8d22a58ef6858602d01de2ff2563be06c57e29a3" 109 | dependencies: 110 | bluebird "^3.5.x" 111 | commander "^2.x" 112 | joi "^12.x" 113 | lcov-parse "^1.x" 114 | lodash "^4.17.4" 115 | log-driver "^1.x" 116 | request "^2.83.0" 117 | request-promise "^4.x" 118 | 119 | colors@^1.1.2: 120 | version "1.1.2" 121 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" 122 | 123 | combined-stream@^1.0.6, combined-stream@~1.0.6: 124 | version "1.0.8" 125 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 126 | dependencies: 127 | delayed-stream "~1.0.0" 128 | 129 | commander@0.6.1: 130 | version "0.6.1" 131 | resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" 132 | 133 | commander@2.3.0: 134 | version "2.3.0" 135 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" 136 | 137 | commander@^2.x, commander@~2.20.3: 138 | version "2.20.3" 139 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 140 | 141 | concat-map@0.0.1: 142 | version "0.0.1" 143 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 144 | 145 | core-util-is@1.0.2: 146 | version "1.0.2" 147 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 148 | 149 | currently-unhandled@^0.4.1: 150 | version "0.4.1" 151 | resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" 152 | dependencies: 153 | array-find-index "^1.0.1" 154 | 155 | dashdash@^1.12.0: 156 | version "1.14.1" 157 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 158 | dependencies: 159 | assert-plus "^1.0.0" 160 | 161 | dateformat@^1.0.12: 162 | version "1.0.12" 163 | resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" 164 | dependencies: 165 | get-stdin "^4.0.1" 166 | meow "^3.3.0" 167 | 168 | debug@2.2.0: 169 | version "2.2.0" 170 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" 171 | dependencies: 172 | ms "0.7.1" 173 | 174 | decamelize@^1.1.2: 175 | version "1.2.0" 176 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 177 | 178 | deep-is@~0.1.3: 179 | version "0.1.3" 180 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" 181 | 182 | delayed-stream@~1.0.0: 183 | version "1.0.0" 184 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 185 | 186 | diff@1.4.0: 187 | version "1.4.0" 188 | resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" 189 | 190 | ecc-jsbn@~0.1.1: 191 | version "0.1.2" 192 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" 193 | dependencies: 194 | jsbn "~0.1.0" 195 | safer-buffer "^2.1.0" 196 | 197 | error-ex@^1.2.0: 198 | version "1.3.1" 199 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" 200 | dependencies: 201 | is-arrayish "^0.2.1" 202 | 203 | escape-string-regexp@1.0.2: 204 | version "1.0.2" 205 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" 206 | 207 | escodegen@1.8.x: 208 | version "1.8.1" 209 | resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" 210 | dependencies: 211 | esprima "^2.7.1" 212 | estraverse "^1.9.1" 213 | esutils "^2.0.2" 214 | optionator "^0.8.1" 215 | optionalDependencies: 216 | source-map "~0.2.0" 217 | 218 | esprima@2.7.x, esprima@^2.7.1: 219 | version "2.7.3" 220 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" 221 | 222 | esprima@^4.0.0: 223 | version "4.0.1" 224 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 225 | 226 | estraverse@^1.9.1: 227 | version "1.9.3" 228 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" 229 | 230 | esutils@^2.0.2: 231 | version "2.0.2" 232 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 233 | 234 | extend@~3.0.2: 235 | version "3.0.2" 236 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 237 | 238 | extsprintf@1.3.0: 239 | version "1.3.0" 240 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" 241 | 242 | extsprintf@^1.2.0: 243 | version "1.4.0" 244 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" 245 | 246 | fast-deep-equal@^3.1.1: 247 | version "3.1.1" 248 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" 249 | 250 | fast-json-stable-stringify@^2.0.0: 251 | version "2.1.0" 252 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" 253 | 254 | fast-levenshtein@~2.0.4: 255 | version "2.0.6" 256 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" 257 | 258 | find-up@^1.0.0: 259 | version "1.1.2" 260 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" 261 | dependencies: 262 | path-exists "^2.0.0" 263 | pinkie-promise "^2.0.0" 264 | 265 | forever-agent@~0.6.1: 266 | version "0.6.1" 267 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 268 | 269 | form-data@~2.3.2: 270 | version "2.3.3" 271 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" 272 | dependencies: 273 | asynckit "^0.4.0" 274 | combined-stream "^1.0.6" 275 | mime-types "^2.1.12" 276 | 277 | get-stdin@^4.0.1: 278 | version "4.0.1" 279 | resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" 280 | 281 | getpass@^0.1.1: 282 | version "0.1.7" 283 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" 284 | dependencies: 285 | assert-plus "^1.0.0" 286 | 287 | glob@3.2.11: 288 | version "3.2.11" 289 | resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d" 290 | dependencies: 291 | inherits "2" 292 | minimatch "0.3" 293 | 294 | glob@^5.0.15: 295 | version "5.0.15" 296 | resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" 297 | dependencies: 298 | inflight "^1.0.4" 299 | inherits "2" 300 | minimatch "2 || 3" 301 | once "^1.3.0" 302 | path-is-absolute "^1.0.0" 303 | 304 | graceful-fs@^4.1.2: 305 | version "4.1.11" 306 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 307 | 308 | growl@1.9.2: 309 | version "1.9.2" 310 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" 311 | 312 | handlebars@^4.0.1: 313 | version "4.7.6" 314 | resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e" 315 | dependencies: 316 | minimist "^1.2.5" 317 | neo-async "^2.6.0" 318 | source-map "^0.6.1" 319 | wordwrap "^1.0.0" 320 | optionalDependencies: 321 | uglify-js "^3.1.4" 322 | 323 | har-schema@^2.0.0: 324 | version "2.0.0" 325 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" 326 | 327 | har-validator@~5.1.3: 328 | version "5.1.3" 329 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" 330 | dependencies: 331 | ajv "^6.5.5" 332 | har-schema "^2.0.0" 333 | 334 | has-flag@^1.0.0: 335 | version "1.0.0" 336 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" 337 | 338 | hoek@4.x.x: 339 | version "4.2.1" 340 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" 341 | 342 | hosted-git-info@^2.1.4: 343 | version "2.4.1" 344 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.1.tgz#4b0445e41c004a8bd1337773a4ff790ca40318c8" 345 | 346 | http-signature@~1.2.0: 347 | version "1.2.0" 348 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" 349 | dependencies: 350 | assert-plus "^1.0.0" 351 | jsprim "^1.2.2" 352 | sshpk "^1.7.0" 353 | 354 | indent-string@^2.1.0: 355 | version "2.1.0" 356 | resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" 357 | dependencies: 358 | repeating "^2.0.0" 359 | 360 | inflight@^1.0.4: 361 | version "1.0.6" 362 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 363 | dependencies: 364 | once "^1.3.0" 365 | wrappy "1" 366 | 367 | inherits@2: 368 | version "2.0.3" 369 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 370 | 371 | is-arrayish@^0.2.1: 372 | version "0.2.1" 373 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 374 | 375 | is-builtin-module@^1.0.0: 376 | version "1.0.0" 377 | resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" 378 | dependencies: 379 | builtin-modules "^1.0.0" 380 | 381 | is-class@0.0.4: 382 | version "0.0.4" 383 | resolved "https://registry.yarnpkg.com/is-class/-/is-class-0.0.4.tgz#e057451705bb34e39e3e33598c93a9837296b736" 384 | 385 | is-finite@^1.0.0: 386 | version "1.0.2" 387 | resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" 388 | dependencies: 389 | number-is-nan "^1.0.0" 390 | 391 | is-typedarray@~1.0.0: 392 | version "1.0.0" 393 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 394 | 395 | is-utf8@^0.2.0: 396 | version "0.2.1" 397 | resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" 398 | 399 | isemail@3.x.x: 400 | version "3.2.0" 401 | resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.2.0.tgz#59310a021931a9fb06bbb51e155ce0b3f236832c" 402 | dependencies: 403 | punycode "2.x.x" 404 | 405 | isexe@^2.0.0: 406 | version "2.0.0" 407 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 408 | 409 | isstream@~0.1.2: 410 | version "0.1.2" 411 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 412 | 413 | istanbul@^0.4.5: 414 | version "0.4.5" 415 | resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" 416 | dependencies: 417 | abbrev "1.0.x" 418 | async "1.x" 419 | escodegen "1.8.x" 420 | esprima "2.7.x" 421 | glob "^5.0.15" 422 | handlebars "^4.0.1" 423 | js-yaml "3.x" 424 | mkdirp "0.5.x" 425 | nopt "3.x" 426 | once "1.x" 427 | resolve "1.1.x" 428 | supports-color "^3.1.0" 429 | which "^1.1.1" 430 | wordwrap "^1.0.0" 431 | 432 | jade@0.26.3: 433 | version "0.26.3" 434 | resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" 435 | dependencies: 436 | commander "0.6.1" 437 | mkdirp "0.3.0" 438 | 439 | joi@^12.x: 440 | version "12.0.0" 441 | resolved "https://registry.yarnpkg.com/joi/-/joi-12.0.0.tgz#46f55e68f4d9628f01bbb695902c8b307ad8d33a" 442 | dependencies: 443 | hoek "4.x.x" 444 | isemail "3.x.x" 445 | topo "2.x.x" 446 | 447 | js-yaml@3.x: 448 | version "3.13.1" 449 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" 450 | dependencies: 451 | argparse "^1.0.7" 452 | esprima "^4.0.0" 453 | 454 | jsbn@~0.1.0: 455 | version "0.1.1" 456 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 457 | 458 | json-schema-traverse@^0.4.1: 459 | version "0.4.1" 460 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 461 | 462 | json-schema@0.2.3: 463 | version "0.2.3" 464 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 465 | 466 | json-stringify-safe@~5.0.1: 467 | version "5.0.1" 468 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 469 | 470 | jsprim@^1.2.2: 471 | version "1.4.1" 472 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" 473 | dependencies: 474 | assert-plus "1.0.0" 475 | extsprintf "1.3.0" 476 | json-schema "0.2.3" 477 | verror "1.10.0" 478 | 479 | lcov-parse@^1.x: 480 | version "1.0.0" 481 | resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-1.0.0.tgz#eb0d46b54111ebc561acb4c408ef9363bdc8f7e0" 482 | 483 | levn@~0.3.0: 484 | version "0.3.0" 485 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" 486 | dependencies: 487 | prelude-ls "~1.1.2" 488 | type-check "~0.3.2" 489 | 490 | load-json-file@^1.0.0: 491 | version "1.1.0" 492 | resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" 493 | dependencies: 494 | graceful-fs "^4.1.2" 495 | parse-json "^2.2.0" 496 | pify "^2.0.0" 497 | pinkie-promise "^2.0.0" 498 | strip-bom "^2.0.0" 499 | 500 | lodash@^4.1.0: 501 | version "4.17.13" 502 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93" 503 | 504 | lodash@^4.17.15, lodash@^4.17.4: 505 | version "4.17.15" 506 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" 507 | 508 | log-driver@^1.x: 509 | version "1.2.7" 510 | resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" 511 | 512 | loud-rejection@^1.0.0: 513 | version "1.6.0" 514 | resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" 515 | dependencies: 516 | currently-unhandled "^0.4.1" 517 | signal-exit "^3.0.0" 518 | 519 | lru-cache@2: 520 | version "2.7.3" 521 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" 522 | 523 | map-obj@^1.0.0, map-obj@^1.0.1: 524 | version "1.0.1" 525 | resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" 526 | 527 | meow@^3.3.0: 528 | version "3.7.0" 529 | resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" 530 | dependencies: 531 | camelcase-keys "^2.0.0" 532 | decamelize "^1.1.2" 533 | loud-rejection "^1.0.0" 534 | map-obj "^1.0.1" 535 | minimist "^1.1.3" 536 | normalize-package-data "^2.3.4" 537 | object-assign "^4.0.1" 538 | read-pkg-up "^1.0.1" 539 | redent "^1.0.0" 540 | trim-newlines "^1.0.0" 541 | 542 | mime-db@1.44.0: 543 | version "1.44.0" 544 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" 545 | 546 | mime-types@^2.1.12, mime-types@~2.1.19: 547 | version "2.1.27" 548 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" 549 | dependencies: 550 | mime-db "1.44.0" 551 | 552 | minimatch@0.3: 553 | version "0.3.0" 554 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" 555 | dependencies: 556 | lru-cache "2" 557 | sigmund "~1.0.0" 558 | 559 | "minimatch@2 || 3": 560 | version "3.0.3" 561 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 562 | dependencies: 563 | brace-expansion "^1.0.0" 564 | 565 | minimist@0.0.8: 566 | version "0.0.8" 567 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 568 | 569 | minimist@^1.1.3, minimist@^1.2.5: 570 | version "1.2.5" 571 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 572 | 573 | mkdirp@0.3.0: 574 | version "0.3.0" 575 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" 576 | 577 | mkdirp@0.5.1, mkdirp@0.5.x: 578 | version "0.5.1" 579 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 580 | dependencies: 581 | minimist "0.0.8" 582 | 583 | mocha-lcov-reporter@^1.3.0: 584 | version "1.3.0" 585 | resolved "https://registry.yarnpkg.com/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz#469bdef4f8afc9a116056f079df6182d0afb0384" 586 | 587 | mocha@^2.4.5: 588 | version "2.5.3" 589 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-2.5.3.tgz#161be5bdeb496771eb9b35745050b622b5aefc58" 590 | dependencies: 591 | commander "2.3.0" 592 | debug "2.2.0" 593 | diff "1.4.0" 594 | escape-string-regexp "1.0.2" 595 | glob "3.2.11" 596 | growl "1.9.2" 597 | jade "0.26.3" 598 | mkdirp "0.5.1" 599 | supports-color "1.2.0" 600 | to-iso-string "0.0.2" 601 | 602 | ms@0.7.1: 603 | version "0.7.1" 604 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" 605 | 606 | native-or-bluebird@^1.2.0: 607 | version "1.2.0" 608 | resolved "https://registry.yarnpkg.com/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz#39c47bfd7825d1fb9ffad32210ae25daadf101c9" 609 | 610 | neo-async@^2.6.0: 611 | version "2.6.1" 612 | resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" 613 | 614 | nopt@3.x: 615 | version "3.0.6" 616 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" 617 | dependencies: 618 | abbrev "1" 619 | 620 | normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: 621 | version "2.3.6" 622 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.6.tgz#498fa420c96401f787402ba21e600def9f981fff" 623 | dependencies: 624 | hosted-git-info "^2.1.4" 625 | is-builtin-module "^1.0.0" 626 | semver "2 || 3 || 4 || 5" 627 | validate-npm-package-license "^3.0.1" 628 | 629 | number-is-nan@^1.0.0: 630 | version "1.0.1" 631 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 632 | 633 | oauth-sign@~0.9.0: 634 | version "0.9.0" 635 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" 636 | 637 | object-assign@^4.0.1: 638 | version "4.1.1" 639 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 640 | 641 | once@1.x, once@^1.3.0: 642 | version "1.4.0" 643 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 644 | dependencies: 645 | wrappy "1" 646 | 647 | optionator@^0.8.1: 648 | version "0.8.2" 649 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" 650 | dependencies: 651 | deep-is "~0.1.3" 652 | fast-levenshtein "~2.0.4" 653 | levn "~0.3.0" 654 | prelude-ls "~1.1.2" 655 | type-check "~0.3.2" 656 | wordwrap "~1.0.0" 657 | 658 | parse-json@^2.2.0: 659 | version "2.2.0" 660 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" 661 | dependencies: 662 | error-ex "^1.2.0" 663 | 664 | path-exists@^2.0.0: 665 | version "2.1.0" 666 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" 667 | dependencies: 668 | pinkie-promise "^2.0.0" 669 | 670 | path-is-absolute@^1.0.0: 671 | version "1.0.1" 672 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 673 | 674 | path-type@^1.0.0: 675 | version "1.1.0" 676 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" 677 | dependencies: 678 | graceful-fs "^4.1.2" 679 | pify "^2.0.0" 680 | pinkie-promise "^2.0.0" 681 | 682 | performance-now@^2.1.0: 683 | version "2.1.0" 684 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" 685 | 686 | pify@^2.0.0: 687 | version "2.3.0" 688 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 689 | 690 | pinkie-promise@^2.0.0: 691 | version "2.0.1" 692 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 693 | dependencies: 694 | pinkie "^2.0.0" 695 | 696 | pinkie@^2.0.0: 697 | version "2.0.4" 698 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 699 | 700 | prelude-ls@~1.1.2: 701 | version "1.1.2" 702 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" 703 | 704 | psl@^1.1.28: 705 | version "1.8.0" 706 | resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" 707 | 708 | punycode@2.x.x, punycode@^2.1.0, punycode@^2.1.1: 709 | version "2.1.1" 710 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 711 | 712 | qs@~6.5.2: 713 | version "6.5.2" 714 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" 715 | 716 | read-pkg-up@^1.0.1: 717 | version "1.0.1" 718 | resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" 719 | dependencies: 720 | find-up "^1.0.0" 721 | read-pkg "^1.0.0" 722 | 723 | read-pkg@^1.0.0: 724 | version "1.1.0" 725 | resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" 726 | dependencies: 727 | load-json-file "^1.0.0" 728 | normalize-package-data "^2.3.2" 729 | path-type "^1.0.0" 730 | 731 | redent@^1.0.0: 732 | version "1.0.0" 733 | resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" 734 | dependencies: 735 | indent-string "^2.1.0" 736 | strip-indent "^1.0.1" 737 | 738 | repeating@^2.0.0: 739 | version "2.0.1" 740 | resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" 741 | dependencies: 742 | is-finite "^1.0.0" 743 | 744 | request-promise-core@1.1.3: 745 | version "1.1.3" 746 | resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" 747 | dependencies: 748 | lodash "^4.17.15" 749 | 750 | request-promise@^4.x: 751 | version "4.2.5" 752 | resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.5.tgz#186222c59ae512f3497dfe4d75a9c8461bd0053c" 753 | dependencies: 754 | bluebird "^3.5.0" 755 | request-promise-core "1.1.3" 756 | stealthy-require "^1.1.1" 757 | tough-cookie "^2.3.3" 758 | 759 | request@^2.83.0: 760 | version "2.88.2" 761 | resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" 762 | dependencies: 763 | aws-sign2 "~0.7.0" 764 | aws4 "^1.8.0" 765 | caseless "~0.12.0" 766 | combined-stream "~1.0.6" 767 | extend "~3.0.2" 768 | forever-agent "~0.6.1" 769 | form-data "~2.3.2" 770 | har-validator "~5.1.3" 771 | http-signature "~1.2.0" 772 | is-typedarray "~1.0.0" 773 | isstream "~0.1.2" 774 | json-stringify-safe "~5.0.1" 775 | mime-types "~2.1.19" 776 | oauth-sign "~0.9.0" 777 | performance-now "^2.1.0" 778 | qs "~6.5.2" 779 | safe-buffer "^5.1.2" 780 | tough-cookie "~2.5.0" 781 | tunnel-agent "^0.6.0" 782 | uuid "^3.3.2" 783 | 784 | resolve@1.1.x: 785 | version "1.1.7" 786 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" 787 | 788 | safe-buffer@^5.0.1, safe-buffer@^5.1.2: 789 | version "5.2.0" 790 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" 791 | 792 | safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: 793 | version "2.1.2" 794 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 795 | 796 | "semver@2 || 3 || 4 || 5": 797 | version "5.3.0" 798 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" 799 | 800 | sigmund@~1.0.0: 801 | version "1.0.1" 802 | resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" 803 | 804 | signal-exit@^3.0.0: 805 | version "3.0.2" 806 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 807 | 808 | sleep-promise@^2.0.0: 809 | version "2.0.0" 810 | resolved "https://registry.yarnpkg.com/sleep-promise/-/sleep-promise-2.0.0.tgz#e7e798dfe56c044da85882d76d22a99804663c41" 811 | 812 | source-map@^0.6.1: 813 | version "0.6.1" 814 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 815 | 816 | source-map@~0.2.0: 817 | version "0.2.0" 818 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" 819 | dependencies: 820 | amdefine ">=0.0.4" 821 | 822 | spdx-correct@~1.0.0: 823 | version "1.0.2" 824 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" 825 | dependencies: 826 | spdx-license-ids "^1.0.2" 827 | 828 | spdx-expression-parse@~1.0.0: 829 | version "1.0.4" 830 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" 831 | 832 | spdx-license-ids@^1.0.2: 833 | version "1.2.2" 834 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" 835 | 836 | sprintf-js@~1.0.2: 837 | version "1.0.3" 838 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 839 | 840 | sshpk@^1.7.0: 841 | version "1.16.1" 842 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" 843 | dependencies: 844 | asn1 "~0.2.3" 845 | assert-plus "^1.0.0" 846 | bcrypt-pbkdf "^1.0.0" 847 | dashdash "^1.12.0" 848 | ecc-jsbn "~0.1.1" 849 | getpass "^0.1.1" 850 | jsbn "~0.1.0" 851 | safer-buffer "^2.0.2" 852 | tweetnacl "~0.14.0" 853 | 854 | stealthy-require@^1.1.1: 855 | version "1.1.1" 856 | resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" 857 | 858 | strip-bom@^2.0.0: 859 | version "2.0.0" 860 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" 861 | dependencies: 862 | is-utf8 "^0.2.0" 863 | 864 | strip-indent@^1.0.1: 865 | version "1.0.1" 866 | resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" 867 | dependencies: 868 | get-stdin "^4.0.1" 869 | 870 | supports-color@1.2.0: 871 | version "1.2.0" 872 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" 873 | 874 | supports-color@^3.1.0: 875 | version "3.2.3" 876 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" 877 | dependencies: 878 | has-flag "^1.0.0" 879 | 880 | then-sleep@^1.0.1: 881 | version "1.0.1" 882 | resolved "https://registry.yarnpkg.com/then-sleep/-/then-sleep-1.0.1.tgz#759823bdc4de56ba2a20812868eb872a803ed1f9" 883 | dependencies: 884 | native-or-bluebird "^1.2.0" 885 | 886 | to-iso-string@0.0.2: 887 | version "0.0.2" 888 | resolved "https://registry.yarnpkg.com/to-iso-string/-/to-iso-string-0.0.2.tgz#4dc19e664dfccbe25bd8db508b00c6da158255d1" 889 | 890 | to-no-case@^1.0.0: 891 | version "1.0.2" 892 | resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a" 893 | 894 | to-snake-case@^1.0.0: 895 | version "1.0.0" 896 | resolved "https://registry.yarnpkg.com/to-snake-case/-/to-snake-case-1.0.0.tgz#ce746913897946019a87e62edfaeaea4c608ab8c" 897 | dependencies: 898 | to-space-case "^1.0.0" 899 | 900 | to-space-case@^1.0.0: 901 | version "1.0.0" 902 | resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17" 903 | dependencies: 904 | to-no-case "^1.0.0" 905 | 906 | topo@2.x.x: 907 | version "2.0.2" 908 | resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182" 909 | dependencies: 910 | hoek "4.x.x" 911 | 912 | tough-cookie@^2.3.3, tough-cookie@~2.5.0: 913 | version "2.5.0" 914 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" 915 | dependencies: 916 | psl "^1.1.28" 917 | punycode "^2.1.1" 918 | 919 | trim-newlines@^1.0.0: 920 | version "1.0.0" 921 | resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" 922 | 923 | tunnel-agent@^0.6.0: 924 | version "0.6.0" 925 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 926 | dependencies: 927 | safe-buffer "^5.0.1" 928 | 929 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 930 | version "0.14.5" 931 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 932 | 933 | type-check@~0.3.2: 934 | version "0.3.2" 935 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" 936 | dependencies: 937 | prelude-ls "~1.1.2" 938 | 939 | uglify-js@^3.1.4: 940 | version "3.9.1" 941 | resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.1.tgz#a56a71c8caa2d36b5556cc1fd57df01ae3491539" 942 | dependencies: 943 | commander "~2.20.3" 944 | 945 | uri-js@^4.2.2: 946 | version "4.2.2" 947 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" 948 | dependencies: 949 | punycode "^2.1.0" 950 | 951 | uuid@^3.3.2: 952 | version "3.4.0" 953 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" 954 | 955 | validate-npm-package-license@^3.0.1: 956 | version "3.0.1" 957 | resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" 958 | dependencies: 959 | spdx-correct "~1.0.0" 960 | spdx-expression-parse "~1.0.0" 961 | 962 | verror@1.10.0: 963 | version "1.10.0" 964 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" 965 | dependencies: 966 | assert-plus "^1.0.0" 967 | core-util-is "1.0.2" 968 | extsprintf "^1.2.0" 969 | 970 | which@^1.1.1: 971 | version "1.2.14" 972 | resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" 973 | dependencies: 974 | isexe "^2.0.0" 975 | 976 | wordwrap@^1.0.0, wordwrap@~1.0.0: 977 | version "1.0.0" 978 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" 979 | 980 | wrappy@1: 981 | version "1.0.2" 982 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 983 | --------------------------------------------------------------------------------