clout-js
Advanced tools
Comparing version 2.1.0-beta.2 to 2.1.0-beta.3
@@ -7,19 +7,19 @@ /*! | ||
module.exports = function(grunt) { | ||
grunt.initConfig({ | ||
jsdoc : { | ||
dist : { | ||
src: ['bin/**/*.js', 'hooks/**/*.js', 'hookslib/**/*.js', 'lib/**/*.js', 'test/lib.js', 'index.js', 'README.md'], | ||
options: { | ||
tutorials: 'tutorials/', | ||
destination: 'docs', | ||
template: './node_modules/minami' | ||
} | ||
} | ||
} | ||
}); | ||
module.exports = function gruntConfig(grunt) { | ||
grunt.initConfig({ | ||
jsdoc: { | ||
dist: { | ||
src: ['bin/**/*.js', 'hooks/**/*.js', 'hookslib/**/*.js', 'lib/**/*.js', 'test/lib.js', 'index.js', 'README.md'], | ||
options: { | ||
tutorials: 'tutorials/', | ||
destination: 'docs', | ||
template: './node_modules/minami', | ||
}, | ||
}, | ||
}, | ||
}); | ||
grunt.loadNpmTasks('grunt-jsdoc'); | ||
grunt.registerTask('gendoc', ['jsdoc']); | ||
grunt.registerTask('defualt', ['jsdoc']); | ||
grunt.loadNpmTasks('grunt-jsdoc'); | ||
grunt.registerTask('gendoc', ['jsdoc']); | ||
grunt.registerTask('defualt', ['jsdoc']); | ||
}; |
@@ -13,27 +13,27 @@ /*! | ||
module.exports = { | ||
/** | ||
* initialize apis from application paths | ||
* @property {event} event start | ||
* @property {priority} priority API | ||
*/ | ||
initialize: { | ||
event: 'start', | ||
priority: 'API', | ||
fn: function (next) { | ||
let clientApiFolder = path.resolve(this.rootDirectory, 'apis'); | ||
let apiDirs = this.modules | ||
.map((moduleInfo) => path.resolve(moduleInfo.path, 'apis')) | ||
.concat([clientApiFolder]); | ||
/** | ||
* initialize apis from application paths | ||
* @property {event} event start | ||
* @property {priority} priority API | ||
*/ | ||
initialize: { | ||
event: 'start', | ||
priority: 'API', | ||
fn(next) { | ||
const clientApiFolder = path.resolve(this.rootDirectory, 'apis'); | ||
const apiDirs = this.modules | ||
.map(moduleInfo => path.resolve(moduleInfo.path, 'apis')) | ||
.concat([clientApiFolder]); | ||
if (!this.core) { | ||
this.core = {}; | ||
} | ||
if (!this.core) { | ||
this.core = {}; | ||
} | ||
this.core.api = new CloutApiRoutes(this); | ||
this.core.api.loadAPIsFromDirs(apiDirs); | ||
this.core.api.attachRouterToApp(); | ||
this.core.api = new CloutApiRoutes(this); | ||
this.core.api.loadAPIsFromDirs(apiDirs); | ||
this.core.api.attachRouterToApp(); | ||
next(); | ||
} | ||
} | ||
next(); | ||
}, | ||
}, | ||
}; |
@@ -10,20 +10,19 @@ /*! | ||
*/ | ||
const debug = require('debug')('clout:hook/config'); | ||
module.exports = { | ||
/** | ||
* add config to application locals | ||
* @property {event} event start | ||
* @property {priority} priority 25 | ||
*/ | ||
middleware: { | ||
event: 'start', | ||
priority: 25, | ||
fn: function (next) { | ||
!this.app.locals && (this.app.locals = {}); | ||
this.app.locals.config = this.config; | ||
this.app.request.clout = this; | ||
next(); | ||
} | ||
} | ||
/** | ||
* add config to application locals | ||
* @property {event} event start | ||
* @property {priority} priority 25 | ||
*/ | ||
middleware: { | ||
event: 'start', | ||
priority: 25, | ||
fn(next) { | ||
this.app.locals = this.app.locals || {}; | ||
this.app.locals.config = this.config; | ||
this.app.request.clout = this; | ||
next(); | ||
}, | ||
}, | ||
}; |
@@ -12,82 +12,85 @@ /*! | ||
const debug = require('debug')('clout:hook/controllers'); | ||
const utils = require('../lib/utils'); | ||
const express = require('express'); | ||
const router = express.Router(); | ||
const async = require('async'); | ||
const Q = require('q'); | ||
const utils = require('../lib/utils'); | ||
module.exports = { | ||
/** | ||
* initialize controllers from application paths | ||
* @property {event} event start | ||
* @property {priority} priority CONTROLLER | ||
*/ | ||
initialize: { | ||
event: 'start', | ||
priority: 'CONTROLLER', | ||
fn: function (next) { | ||
var self = this; | ||
/** | ||
* initialize controllers from application paths | ||
* @property {event} event start | ||
* @property {priority} priority CONTROLLER | ||
*/ | ||
initialize: { | ||
event: 'start', | ||
priority: 'CONTROLLER', | ||
fn(next) { | ||
const self = this; | ||
try { | ||
function loadController(dir) { | ||
let name = dir.split('controllers/')[1].replace('.js', ''); | ||
debug('loading controller %s', name); | ||
let controller = require(dir); | ||
function loadController(dir) { | ||
const name = dir.split('controllers/')[1].replace('.js', ''); | ||
debug('loading controller %s', name); | ||
const controller = require(dir); | ||
if (!controller.path) { return; } | ||
if (!controller.path) { return; } | ||
let hooks = controller.hooks || []; | ||
let method = controller.method ? controller.method.toLowerCase() : 'all'; | ||
const hooks = controller.hooks || []; | ||
const method = controller.method ? controller.method.toLowerCase() : 'all'; | ||
// log endpoint request | ||
router[method](controller.path, function (req, res, next) { | ||
req.logger.info('Endpoint [%s] %s', req.method, req.path); | ||
debug('Endpoint [%s] %s', req.method, req.path); | ||
next(); | ||
}); | ||
// log endpoint request | ||
router[method](controller.path, (req, res, done) => { | ||
req.logger.info('Endpoint [%s] %s', req.method, req.path); | ||
debug('Endpoint [%s] %s', req.method, req.path); | ||
done(); | ||
}); | ||
// load hook first | ||
hooks.forEach(function (hook) { | ||
router[method](controller.path, function (req) { | ||
hook.name && debug('hook:', hook.name); | ||
hook.apply(this, arguments); | ||
}); | ||
}); | ||
// load hook first | ||
hooks.forEach((hook) => { | ||
router[method](controller.path, function cloutHook(...args) { | ||
if (hook.name) { | ||
debug('hook:', hook.name); | ||
} | ||
// load controller | ||
if (controller.fn) { | ||
debug('loaded endpoint [%s] %s', method, controller.path); | ||
router[method](controller.path, function (req) { | ||
debug('loading controller %s:%s', name); | ||
controller.fn.apply(this, arguments); | ||
}); | ||
} | ||
} | ||
hook.apply(this, args); | ||
}); | ||
}); | ||
function loadControllersFromDirectory(dir) { | ||
let dirs = utils.getGlobbedFiles(path.join(dir, '**/**.js')); | ||
dirs.forEach(loadController); | ||
return Promise.resolve(); | ||
} | ||
// load controller | ||
if (controller.fn) { | ||
debug('loaded endpoint [%s] %s', method, controller.path); | ||
router[method](controller.path, function cloutController(...args) { | ||
debug('loading controller %s:%s', name); | ||
controller.fn.apply(this, args); | ||
}); | ||
} | ||
} | ||
debug('loading controllers'); | ||
// 1) load module controllers | ||
async.each(this.modules, function (module, next) { | ||
loadControllersFromDirectory(path.join(module.path, 'controllers')).then(function () { | ||
next(null); | ||
}, next); | ||
}, function done(err) { | ||
if (err) { throw new Error(err); } | ||
// 2) load application controllers | ||
loadControllersFromDirectory(path.join(self.rootDirectory, 'controllers')).then(function () { | ||
debug('attached router'); | ||
self.app.use('/', router); | ||
next(); | ||
}, next); | ||
}); | ||
} catch(e) { | ||
console.log(e); | ||
} | ||
} | ||
} | ||
function loadControllersFromDirectory(dir) { | ||
const dirs = utils.getGlobbedFiles(path.join(dir, '**/**.js')); | ||
dirs.forEach(loadController); | ||
return Promise.resolve(); | ||
} | ||
try { | ||
debug('loading controllers'); | ||
// 1) load module controllers | ||
async.each(this.modules, (module, done) => { | ||
loadControllersFromDirectory(path.join(module.path, 'controllers')) | ||
.then(() => done()) | ||
.catch(err => done(err)); | ||
}, (err) => { | ||
if (err) { throw new Error(err); } | ||
// 2) load application controllers | ||
loadControllersFromDirectory(path.join(self.rootDirectory, 'controllers')).then(() => { | ||
debug('attached router'); | ||
self.app.use('/', router); | ||
next(); | ||
}, next); | ||
}); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
}, | ||
}, | ||
}; |
@@ -10,123 +10,125 @@ /*! | ||
*/ | ||
const | ||
debug = require('debug')('clout:hook/engines'), | ||
fs = require('fs-extra'), | ||
path = require('path'); | ||
const debug = require('debug')('clout:hook/engines'); | ||
const fs = require('fs-extra'); | ||
const path = require('path'); | ||
module.exports = { | ||
/** | ||
* initialize engine mechanist | ||
* @property {event} event start | ||
* @property {priority} priority 2 | ||
*/ | ||
initialize: { | ||
event: 'start', | ||
priority: 2, | ||
fn: function (next) { | ||
var self = this; | ||
debug('initialize engines'); | ||
!this.app.engines && (this.app.engines = {}); | ||
/** | ||
* initialize engine mechanist | ||
* @property {event} event start | ||
* @property {priority} priority 2 | ||
*/ | ||
initialize: { | ||
event: 'start', | ||
priority: 2, | ||
fn(next) { | ||
const self = this; | ||
debug('initialize engines'); | ||
this.app.engines = this.app.engines || {}; | ||
Object.defineProperty(this.app.engines, 'add', { | ||
value: function add(ext, engine) { | ||
debug('adding engine %s', ext); | ||
self.app.engines[ext] = engine; | ||
self.app.engine(ext, engine); | ||
} | ||
}); | ||
Object.defineProperty(this.app.engines, 'add', { | ||
value: function add(ext, engine) { | ||
debug('adding engine %s', ext); | ||
self.app.engines[ext] = engine; | ||
self.app.engine(ext, engine); | ||
}, | ||
}); | ||
next(); | ||
} | ||
}, | ||
/** | ||
* attach EJS engine | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
html: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn: function (next) { | ||
debug('adding ejs engine for html'); | ||
this.app.engines.add('html', require('ejs').__express); | ||
next(); | ||
} | ||
}, | ||
/** | ||
* attach EJS engine | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
ejs: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn: function (next) { | ||
debug('adding ejs engine'); | ||
this.app.engines.add('ejs', require('ejs').__express); | ||
next(); | ||
} | ||
}, | ||
/** | ||
* attach HBS engine | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
hbs: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn: function (next) { | ||
debug('adding hbs engine'); | ||
this.app.engines.add('hbs', require('hbs').__express); | ||
next(); | ||
} | ||
}, | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* attach EJS engine | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
html: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn(next) { | ||
debug('adding ejs engine for html'); | ||
this.app.engines.add('html', require('ejs').__express); | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* attach EJS engine | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
ejs: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn(next) { | ||
debug('adding ejs engine'); | ||
this.app.engines.add('ejs', require('ejs').__express); | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* attach HBS engine | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
hbs: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn(next) { | ||
debug('adding hbs engine'); | ||
this.app.engines.add('hbs', require('hbs').__express); | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* attach rendering mechanism | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
render: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn: function (next) { | ||
this.app._render = this.app.render; | ||
this.app.render = function (view, opts, cb) { | ||
var ext = path.extname(view), | ||
engines = this.engines, | ||
dirs = this.get('views'), | ||
queue = [], | ||
found = false; | ||
// if no extension, try each | ||
if (!ext || !engines[ext]) { | ||
Object.keys(engines).forEach(function (ext) { | ||
queue.push(view + '.' + ext); | ||
dirs.forEach(function (dir) { | ||
queue.push(path.join(dir, view + '.' + ext)); | ||
}); | ||
}); | ||
} | ||
// queue directly | ||
queue.push(view); | ||
dirs.forEach(function (dir) { | ||
queue.push(path.join(dir, view)); | ||
}); | ||
// run search | ||
do { | ||
var dir = queue.shift(); | ||
if (fs.existsSync(dir)) { | ||
found = true; | ||
view = dir; | ||
} | ||
} while (!found && queue.length > 0); | ||
// not found | ||
if (!found) { | ||
return cb(new Error('Unable to find layout "' + view + '"')); | ||
} | ||
// do original render | ||
this._render.call(this, view, opts, cb); | ||
}; | ||
next(); | ||
} | ||
}, | ||
/** | ||
* attach rendering mechanism | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
render: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn(next) { | ||
this.app._render = this.app.render; | ||
this.app.render = function cloutRender(view, opts, cb) { | ||
const ext = path.extname(view); | ||
const {engines} = this; | ||
const dirs = this.get('views'); | ||
const queue = []; | ||
// if no extension, try each | ||
if (!ext || !engines[ext]) { | ||
Object.keys(engines).forEach((engineExt) => { | ||
queue.push(`${view}.${engineExt}`); | ||
dirs.forEach((dir) => { | ||
queue.push(path.join(dir, `${view}.${engineExt}`)); | ||
}); | ||
}); | ||
} | ||
// queue directly | ||
queue.push(view); | ||
dirs.forEach((dir) => { | ||
queue.push(path.join(dir, view)); | ||
}); | ||
const viewFilePath = queue.find(dir => fs.existsSync(dir)); | ||
// not found | ||
if (!viewFilePath) { | ||
return cb(new Error(`Unable to find layout "${viewFilePath}"`)); | ||
} | ||
// do original render | ||
return this._render.call(this, viewFilePath, opts, (err, response) => { | ||
if (err) { | ||
console.error({err}, 'failed to render'); | ||
} | ||
cb(err, response); | ||
}); | ||
}; | ||
next(); | ||
}, | ||
}, | ||
}; |
@@ -23,231 +23,235 @@ /*! | ||
module.exports = { | ||
/** | ||
* Initialize express application | ||
* @property {event} event start | ||
* @property {priority} priority 1 | ||
*/ | ||
initialize: { | ||
event: 'start', | ||
priority: 1, | ||
fn: function (next) { | ||
// TODO:- | ||
// - move to Clout.js for initialization | ||
this.app = express(); | ||
this.app.use((req, resp, next) => { | ||
resp.setHeader('x-powered-by', 'Clout-JS'); | ||
resp.setHeader('clout-version', `${this.package.version}`); | ||
resp.setHeader('clout-env', this.config.env); | ||
next(); | ||
}); | ||
/** | ||
* Initialize express application | ||
* @property {event} event start | ||
* @property {priority} priority 1 | ||
*/ | ||
initialize: { | ||
event: 'start', | ||
priority: 1, | ||
fn(next) { | ||
// TODO:- | ||
// - move to Clout.js for initialization | ||
this.app = express(); | ||
this.app.use((req, resp, done) => { | ||
resp.setHeader('x-powered-by', 'Clout-JS'); | ||
resp.setHeader('clout-version', `${this.package.version}`); | ||
resp.setHeader('clout-env', this.config.env); | ||
done(); | ||
}); | ||
// request parsing | ||
this.app.use(bodyParser.json()); | ||
debug('loaded bodyParser.json()'); | ||
this.app.use(bodyParser.urlencoded({ | ||
extended: true | ||
})); | ||
debug('loaded bodyParser.urlencoded()'); | ||
this.app.use(bodyParser.text({})); | ||
debug('loaded bodyParser.text()'); | ||
this.app.use(bodyParser.raw({})); | ||
debug('loaded bodyParser.raw()'); | ||
this.app.use(cookieParser()); | ||
next(); | ||
} | ||
}, | ||
/** | ||
* attach compression mechanism | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
compress: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn: function (next) { | ||
debug('appending compression to middleware'); | ||
this.app.use(compress()); | ||
next(); | ||
} | ||
}, | ||
/** | ||
* attach session mechanism | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
session: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn: function (next) { | ||
let sessionConf = this.config.session || {}; | ||
// request parsing | ||
this.app.use(bodyParser.json()); | ||
debug('loaded bodyParser.json()'); | ||
this.app.use(bodyParser.urlencoded({ | ||
extended: true, | ||
})); | ||
debug('loaded bodyParser.urlencoded()'); | ||
this.app.use(bodyParser.text({})); | ||
debug('loaded bodyParser.text()'); | ||
this.app.use(bodyParser.raw({})); | ||
debug('loaded bodyParser.raw()'); | ||
this.app.use(cookieParser()); | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* attach compression mechanism | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
compress: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn(next) { | ||
debug('appending compression to middleware'); | ||
this.app.use(compress()); | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* attach session mechanism | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
session: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn(next) { | ||
const sessionConf = this.config.session || {}; | ||
if (!sessionConf.secret) { | ||
this.logger.warn('session.secret is undefined'); | ||
sessionConf.secret = '1c6bf8c5cef18097a5389c3ca6d73328'; | ||
} | ||
if (!sessionConf.secret) { | ||
this.logger.warn('session.secret is undefined'); | ||
sessionConf.secret = '1c6bf8c5cef18097a5389c3ca6d73328'; | ||
} | ||
if (!sessionConf.resave) { | ||
sessionConf.resave = true; | ||
} | ||
if (!sessionConf.resave) { | ||
sessionConf.resave = true; | ||
} | ||
if (!sessionConf.saveUninitialized) { | ||
sessionConf.saveUninitialized = false; | ||
} | ||
if (!sessionConf.saveUninitialized) { | ||
sessionConf.saveUninitialized = false; | ||
} | ||
this.config.session = sessionConf; | ||
this.app.session = session(sessionConf); | ||
this.app.use(this.app.session); | ||
next(); | ||
} | ||
}, | ||
/** | ||
* attach public folders | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
publicFolders: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn: function (next) { | ||
let useDir = (dir) => { | ||
if (!fs.existsSync(dir)) { return; } | ||
debug('appending public dir %s', dir); | ||
this.app.use(express.static(dir)); | ||
} | ||
this.config.session = sessionConf; | ||
this.app.session = session(sessionConf); | ||
this.app.use(this.app.session); | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* attach public folders | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
publicFolders: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn(next) { | ||
const useDir = (dir) => { | ||
if (!fs.existsSync(dir)) { return; } | ||
debug('appending public dir %s', dir); | ||
this.app.use(express.static(dir)); | ||
}; | ||
// application public folder | ||
useDir(path.join(this.rootDirectory, 'public')); | ||
// application public folder | ||
useDir(path.join(this.rootDirectory, 'public')); | ||
// modules | ||
this.modules.forEach(module => useDir(path.join(module.path, 'public'))); | ||
// modules | ||
this.modules.forEach(module => useDir(path.join(module.path, 'public'))); | ||
// clout public folder | ||
useDir(path.join(__dirname, '../resources/public')); | ||
// clout public folder | ||
useDir(path.join(__dirname, '../resources/public')); | ||
next(); | ||
} | ||
}, | ||
/** | ||
* attach views folders | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
views: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn: function (next) { | ||
let views = []; | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* attach views folders | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
views: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn(next) { | ||
const views = []; | ||
function useDir(dir) { | ||
if (!fs.existsSync(dir)) { return; } | ||
debug('appending views dir %s', dir); | ||
views.push(dir); | ||
} | ||
function useDir(dir) { | ||
if (!fs.existsSync(dir)) { return; } | ||
debug('appending views dir %s', dir); | ||
views.push(dir); | ||
} | ||
// application public folder | ||
useDir(path.join(this.rootDirectory, 'views')); | ||
// application public folder | ||
useDir(path.join(this.rootDirectory, 'views')); | ||
// modules | ||
this.modules.forEach(module => useDir(path.join(module.path, 'views'))); | ||
// modules | ||
this.modules.forEach(module => useDir(path.join(module.path, 'views'))); | ||
// clout public folder | ||
useDir(path.join(__dirname, '../resources/views')); | ||
// clout public folder | ||
useDir(path.join(__dirname, '../resources/views')); | ||
// set views | ||
this.app.set('views', views); | ||
next(); | ||
} | ||
}, | ||
request: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn: function (next) { | ||
// TODO:- | ||
// - Support converting form data | ||
// - Support multipart data | ||
next(); | ||
} | ||
}, | ||
/** | ||
* attach clout response mechanism | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
response: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn: function (next) { | ||
var httpResponseMap = this.config.httpResponseMap; | ||
// set views | ||
this.app.set('views', views); | ||
next(); | ||
}, | ||
}, | ||
request: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn(next) { | ||
// TODO:- | ||
// - Support converting form data | ||
// - Support multipart data | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* attach clout response mechanism | ||
* @property {event} event start | ||
* @property {priority} priority MIDDLEWARE | ||
*/ | ||
response: { | ||
event: 'start', | ||
priority: 'MIDDLEWARE', | ||
fn(next) { | ||
const {httpResponseMap} = this.config; | ||
function jsonFormat(method, context, payload) { | ||
return () => { | ||
context | ||
.type('json') | ||
.status(method.code) | ||
.send(JSON.stringify(payload.data)); | ||
} | ||
} | ||
function jsonFormat(method, context, payload) { | ||
return () => { | ||
context | ||
.type('json') | ||
.status(method.code) | ||
.send(JSON.stringify(payload.data)); | ||
}; | ||
} | ||
function htmlFormat(method, context, payload) { | ||
return () => { | ||
!method.render && (method.render = DEFAULT_HTML_RENDER); | ||
context | ||
.status(method.code) | ||
.render(method.render, { | ||
data: payload.data | ||
}); | ||
} | ||
} | ||
function htmlFormat(_method, context, payload) { | ||
return () => { | ||
const method = _method.render | ||
? _method | ||
: {..._method, ...{render: DEFAULT_HTML_RENDER}}; | ||
// TODO:- | ||
// - refactor to add support for more file types (CSV, XML) | ||
// - success: false should point to an error html response | ||
for (let methodName in httpResponseMap) { | ||
let method = httpResponseMap[methodName]; | ||
context | ||
.status(method.code) | ||
.render(method.render, { | ||
data: payload.data, | ||
}); | ||
}; | ||
} | ||
if (typeof express.response[methodName] !== 'undefined') { | ||
debug('overiding express response method `%s`', methodName); | ||
} | ||
// TODO:- | ||
// - refactor to add support for more file types (CSV, XML) | ||
// - success: false should point to an error html response | ||
Object.keys(httpResponseMap).forEach((methodName) => { | ||
const method = httpResponseMap[methodName]; | ||
express.response[methodName] = function (data) { | ||
let deffered = Q.defer(); | ||
let payload = { | ||
data: data, | ||
code: method.code, | ||
success: method.method | ||
}; | ||
if (typeof express.response[methodName] !== 'undefined') { | ||
debug('overiding express response method `%s`', methodName); | ||
} | ||
// bind our formaters | ||
let jsonFormatFn = jsonFormat(method, this, payload); | ||
let htmlFormatFn = htmlFormat(method, this, payload); | ||
express.response[methodName] = function cloutResponse(data) { | ||
const deffered = Q.defer(); | ||
const payload = { | ||
data, | ||
code: method.code, | ||
success: method.method, | ||
}; | ||
// let express choose the format | ||
this.format({ | ||
text: jsonFormatFn, | ||
json: jsonFormatFn, | ||
html: htmlFormatFn, | ||
default: htmlFormatFn | ||
}); | ||
// bind our formaters | ||
const jsonFormatFn = jsonFormat(method, this, payload); | ||
const htmlFormatFn = htmlFormat(method, this, payload); | ||
deffered.resolve(); | ||
return deffered.promise; | ||
}; | ||
} | ||
next(); | ||
} | ||
}, | ||
/** | ||
* attach error handling | ||
* @property {event} event start | ||
*/ | ||
errorHandler: { | ||
event: 'start', | ||
fn: function (next) { | ||
this.app.use(function (err, req, resp, next) { | ||
if (!err) { return next(); } | ||
req.logger.error(err.stack); | ||
resp.error(err); | ||
}); | ||
next(); | ||
} | ||
} | ||
// let express choose the format | ||
this.format({ | ||
text: jsonFormatFn, | ||
json: jsonFormatFn, | ||
html: htmlFormatFn, | ||
default: htmlFormatFn, | ||
}); | ||
deffered.resolve(); | ||
return deffered.promise; | ||
}; | ||
}); | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* attach error handling | ||
* @property {event} event start | ||
*/ | ||
errorHandler: { | ||
event: 'start', | ||
fn(next) { | ||
this.app.use((err, req, resp, done) => { | ||
if (!err) { return done(); } | ||
req.logger.error(err.stack); | ||
return resp.error(err); | ||
}); | ||
next(); | ||
}, | ||
}, | ||
}; |
@@ -10,62 +10,61 @@ /*! | ||
*/ | ||
const | ||
debug = require('debug')('clout:hook/models'), | ||
utils = require('../lib/utils'), | ||
Q = require('q'), | ||
path = require('path'); | ||
const debug = require('debug')('clout:hook/models'); | ||
const path = require('path'); | ||
const utils = require('../lib/utils'); | ||
module.exports = { | ||
/** | ||
* initialize models | ||
* @property {event} event start | ||
* @property {priority} priority MODEL | ||
*/ | ||
initialize: { | ||
event: 'start', | ||
priority: 'MODEL', | ||
fn: function (next) { | ||
debug('initialize models'); | ||
this.models = {}; | ||
// append to middleware | ||
this.app.request.models = this.models; | ||
next(); | ||
} | ||
}, | ||
/** | ||
* load models from application paths | ||
* @property {event} event start | ||
* @property {priority} priority MODEL + 2 | ||
*/ | ||
loadModels: { | ||
event: 'start', | ||
priority: 18, | ||
fn: function (next) { | ||
var self = this; | ||
/** | ||
* initialize models | ||
* @property {event} event start | ||
* @property {priority} priority MODEL | ||
*/ | ||
initialize: { | ||
event: 'start', | ||
priority: 'MODEL', | ||
fn(next) { | ||
debug('initialize models'); | ||
this.models = {}; | ||
// append to middleware | ||
this.app.request.models = this.models; | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* load models from application paths | ||
* @property {event} event start | ||
* @property {priority} priority MODEL + 2 | ||
*/ | ||
loadModels: { | ||
event: 'start', | ||
priority: 18, | ||
fn(next) { | ||
const self = this; | ||
function loadModelsFromDir(dir) { | ||
var dirs = utils.getGlobbedFiles(path.join(dir, '**/**.js')); | ||
dirs.forEach(function (dir) { | ||
var modelName = dir.split('models/')[1].replace('.js', ''); | ||
debug('loading model %s', modelName); | ||
if (self.models.hasOwnProperty(modelName)) { | ||
throw new Error('Cannot load model `' + modelName + '` as it already exists'); | ||
} | ||
try { | ||
self.models[modelName] = require(dir); | ||
} catch (e) { | ||
throw new Error('Error loading model `' + modelName + '`: ' + e); | ||
} | ||
}); | ||
} | ||
function loadModelsFromDir(dir) { | ||
const dirs = utils.getGlobbedFiles(path.join(dir, '**/**.js')); | ||
dirs.forEach((filePath) => { | ||
const modelName = filePath.split('models/')[1].replace('.js', ''); | ||
debug('loading model %s', modelName); | ||
if (self.models[modelName]) { | ||
throw new Error(`Cannot load model \`${modelName}\` as it already exists`); | ||
} | ||
debug('loading models'); | ||
// 1) load module hooks | ||
this.modules.forEach(function (module) { | ||
loadModelsFromDir(path.join(module.path, 'models')); | ||
}); | ||
// 2) load application hooks | ||
loadModelsFromDir(path.join(self.rootDirectory, 'models')); | ||
next(); | ||
} | ||
} | ||
try { | ||
self.models[modelName] = require(filePath); | ||
} catch (e) { | ||
throw new Error(`Error loading model \`${modelName}\`: ${e}`); | ||
} | ||
}); | ||
} | ||
debug('loading models'); | ||
// 1) load module hooks | ||
this.modules.forEach((module) => { | ||
loadModelsFromDir(path.join(module.path, 'models')); | ||
}); | ||
// 2) load application hooks | ||
loadModelsFromDir(path.join(self.rootDirectory, 'models')); | ||
next(); | ||
}, | ||
}, | ||
}; |
@@ -10,87 +10,90 @@ /*! | ||
*/ | ||
const | ||
debug = require('debug')('clout:hook/server'), | ||
https = require('https'); | ||
const debug = require('debug')('clout:hook/server'); | ||
const https = require('https'); | ||
module.exports = { | ||
/** | ||
* http instance | ||
* @property {event} event start | ||
* @property {priority} priority 25 | ||
*/ | ||
http: { | ||
event: 'start', | ||
priority: 25, | ||
fn: function (next) { | ||
var self = this, | ||
port = process.env.PORT || this.config.http && this.config.http.port || 8080; | ||
this.server.http = this.app.listen(port, function () { | ||
if (self.server.http.address()) { | ||
debug('http server started on port %s', self.server.http.address().port); | ||
} | ||
next(); | ||
}); | ||
} | ||
}, | ||
/** | ||
* stop http instance | ||
* @property {event} event stop | ||
* @property {priority} priority 25 | ||
*/ | ||
httpStop: { | ||
event: 'stop', | ||
priority: 25, | ||
fn: function (next) { | ||
let http = this.server.http; | ||
/** | ||
* http instance | ||
* @property {event} event start | ||
* @property {priority} priority 25 | ||
*/ | ||
http: { | ||
event: 'start', | ||
priority: 25, | ||
fn(next) { | ||
const self = this; | ||
const port = process.env.PORT || (this.config.http && this.config.http.port) || 8080; | ||
if (http) { | ||
let port = http.address().port; | ||
http.close(); | ||
debug('http server stopped on port %s', port); | ||
} | ||
this.server.http = this.app.listen(port, () => { | ||
if (self.server.http.address()) { | ||
debug('http server started on port %s', self.server.http.address().port); | ||
} | ||
next(); | ||
}); | ||
}, | ||
}, | ||
/** | ||
* stop http instance | ||
* @property {event} event stop | ||
* @property {priority} priority 25 | ||
*/ | ||
httpStop: { | ||
event: 'stop', | ||
priority: 25, | ||
fn(next) { | ||
const {http} = this.server; | ||
next(); | ||
} | ||
}, | ||
/** | ||
* https instance | ||
* @property {event} event start | ||
* @property {priority} priority 25 | ||
*/ | ||
https: { | ||
event: 'start', | ||
priority: 25, | ||
fn: function (next) { | ||
if (!this.config.https) { return next(); } | ||
debug('Securely using https protocol'); | ||
var port = process.env.SSLPORT || this.config.https.port || 8443, | ||
conf = this.config.https; | ||
if (http) { | ||
const {port} = http.address(); | ||
if (!conf) { return next(); } | ||
http.close(); | ||
debug('http server stopped on port %s', port); | ||
} | ||
this.server.https = https.createServer(conf, this.app).listen(); | ||
debug('https server started on port %s', this.server.https.address().port); | ||
next(); | ||
} | ||
}, | ||
/** | ||
* stop https instance | ||
* @property {event} event stop | ||
* @property {priority} priority 25 | ||
*/ | ||
httpsStop: { | ||
event: 'stop', | ||
priority: 25, | ||
fn: function (next) { | ||
let https = this.server.https; | ||
next(); | ||
}, | ||
}, | ||
/** | ||
* https instance | ||
* @property {event} event start | ||
* @property {priority} priority 25 | ||
*/ | ||
https: { | ||
event: 'start', | ||
priority: 25, | ||
fn(next) { | ||
if (!this.config.https) { return next(); } | ||
if (https) { | ||
let port = https.address().port; | ||
https.close(); | ||
debug('https server stopped on port %s', port); | ||
} | ||
debug('Securely using https protocol'); | ||
const conf = this.config.https; | ||
next(); | ||
} | ||
}, | ||
if (!conf) { return next(); } | ||
const port = process.env.SSLPORT || this.config.https.port || 8443; | ||
this.server.https = https.createServer(conf, this.app).listen(port); | ||
debug('https server started on port %s', this.server.https.address().port); | ||
return next(); | ||
}, | ||
}, | ||
/** | ||
* stop https instance | ||
* @property {event} event stop | ||
* @property {priority} priority 25 | ||
*/ | ||
httpsStop: { | ||
event: 'stop', | ||
priority: 25, | ||
fn(next) { | ||
if (this.server.https) { | ||
const {port} = this.server.https.address(); | ||
this.server.https.close(); | ||
debug('https server stopped on port %s', port); | ||
} | ||
next(); | ||
}, | ||
}, | ||
}; |
@@ -1,22 +0,23 @@ | ||
const {safePromisifyCallFn} = require('../../lib/utils'); | ||
const CloutAPIRoute = require('../CloutApiRoute'); | ||
module.exports = { | ||
name: 'api', | ||
fn(apiRoute) { | ||
const apiPath = this.path && `${this.path}.:acceptType?`; | ||
name: 'api', | ||
fn(fn) { | ||
const apiPath = this.path && `${this.path}.:acceptType?`; | ||
this.methods.forEach((method) => { | ||
// attach logging | ||
this.router[method](apiPath, function (req, resp, next) { | ||
req.logger.info('Endpoint [%s] /api%s', req.method, req.path); | ||
next(); | ||
}); | ||
function attachHook(method, hookFn) { | ||
return this.router[method](apiPath, this.handlePromisePostTriggers(hookFn)); | ||
} | ||
// attach hooks | ||
this.hooks.map((hookFn) => this.router[method](apiPath, this.handlePromisePostTriggers(hookFn))); | ||
this.methods.forEach((method) => { | ||
// attach logging | ||
this.router[method](apiPath, (req, resp, next) => { | ||
req.logger.info('Endpoint [%s] /api%s', req.method, req.path); | ||
next(); | ||
}); | ||
this.router[method](apiPath, this.handlePromisePostTriggers(this.fn)); | ||
}); | ||
} | ||
// attach hooks | ||
this.hooks.map(hookFn => attachHook(method, hookFn)); | ||
this.router[method](apiPath, this.handlePromisePostTriggers(fn)); | ||
}); | ||
}, | ||
}; |
const path = require('path'); | ||
const {getGlobbedFiles} = require('../../lib/utils'); | ||
const { getGlobbedFiles } = require('../../lib/utils'); | ||
@@ -7,13 +7,13 @@ module.exports = {}; | ||
function getApiTypes() { | ||
const apiTypeGlob = path.resolve(__dirname, './**/*.js'); | ||
const apiTypeFilePaths = getGlobbedFiles(apiTypeGlob); | ||
return apiTypeFilePaths.reduce((acc, filePath) => { | ||
const {name} = path.parse(filePath); | ||
acc[name] = require(filePath); | ||
return acc; | ||
}, module.exports); | ||
const apiTypeGlob = path.resolve(__dirname, './**/*.js'); | ||
const apiTypeFilePaths = getGlobbedFiles(apiTypeGlob); | ||
return apiTypeFilePaths.reduce((acc, filePath) => { | ||
const { name } = path.parse(filePath); | ||
acc[name] = require(filePath); | ||
return acc; | ||
}, module.exports); | ||
} | ||
getApiTypes() | ||
getApiTypes(); |
@@ -1,29 +0,29 @@ | ||
const {safePromisifyCallFn} = require('../../lib/utils'); | ||
const CloutAPIRoute = require('../CloutApiRoute'); | ||
const request = require('express/lib/request'); | ||
const { safePromisifyCallFn } = require('../../lib/utils'); | ||
const request = require('express/lib/request') | ||
const expressRequestParam = request.param; | ||
request.param = function (name) { | ||
const {_params} = this; | ||
request.param = function cloutParam(name) { | ||
const { _params } = this; | ||
return _params[name]; | ||
} | ||
return _params[name] || expressRequestParam.apply(this, [name]); | ||
}; | ||
module.exports = { | ||
fn(fn) { | ||
const key = this.result || this.param; | ||
fn(fn) { | ||
const key = this.result || this.param; | ||
async function cloutApiParam(req, resp, next, ...args) { | ||
req._params = req._paramsparams || {}; | ||
async function cloutApiParam(req, resp, next, ...args) { | ||
req._params = req._paramsparams || {}; | ||
try { | ||
req._params[key] = await safePromisifyCallFn(fn, this, [req, resp, null, ...args]);; | ||
next(); | ||
} catch (err) { | ||
next(err); | ||
} | ||
} | ||
try { | ||
req._params[key] = await safePromisifyCallFn(fn, this, [req, resp, null, ...args]); | ||
next(); | ||
} catch (err) { | ||
next(err); | ||
} | ||
} | ||
this.router.param(this.param, cloutApiParam); | ||
} | ||
this.router.param(this.param, cloutApiParam); | ||
}, | ||
}; |
@@ -6,3 +6,3 @@ /*! | ||
*/ | ||
const {safePromisifyCallFn} = require('../lib/utils'); | ||
const { safePromisifyCallFn } = require('../lib/utils'); | ||
const types = require('./apiType'); | ||
@@ -12,4 +12,4 @@ | ||
const TYPES_DEFINITION = { | ||
API: 'api', | ||
PARAM: 'param' | ||
API: 'api', | ||
PARAM: 'param', | ||
}; | ||
@@ -23,3 +23,3 @@ const DEFAULT_TYPE = TYPES_DEFINITION.API; | ||
class CloutApiRoute { | ||
/** | ||
/** | ||
* @constructor | ||
@@ -36,66 +36,68 @@ * @param {object} _opts | ||
*/ | ||
constructor(_opts) { | ||
this._opts = _opts; | ||
constructor(_opts) { | ||
this._opts = _opts; | ||
this.type = (this._opts.type || DEFAULT_TYPE).toLowerCase(); | ||
this.isPublicFacing = this.type.includes('api'); | ||
this.type = (this._opts.type || DEFAULT_TYPE).toLowerCase(); | ||
this.isPublicFacing = this.type.includes('api'); | ||
switch (this.type) { | ||
case TYPES_DEFINITION.PARAM: | ||
this.param = this._opts.param; | ||
this.result = this._opts.result; | ||
break; | ||
default: | ||
this.path = this._opts.path; | ||
this.hooks = this._opts.hooks || []; | ||
this.methods = (this._opts.methods || [this._opts.method || DEFAULT_METHOD]).map((method) => method.toLowerCase()); | ||
this.params = this._opts.params; // TODO:- time to start packing modules? clout-swagger | ||
break; | ||
} | ||
const methods = this._opts.methods || [this._opts.method || DEFAULT_METHOD]; | ||
// Documentation Specific | ||
this.group = this._opts.group; | ||
this.name = this._opts.name; | ||
this.description = this._opts.description; | ||
// What actually runs | ||
this.fn = this._opts.fn; | ||
switch (this.type) { | ||
case TYPES_DEFINITION.PARAM: | ||
this.param = this._opts.param; | ||
this.result = this._opts.result; | ||
break; | ||
default: | ||
this.path = this._opts.path; | ||
this.hooks = this._opts.hooks || []; | ||
this.methods = (methods).map(method => method.toLowerCase()); | ||
this.params = this._opts.params; // TODO:- time to start packing modules? clout-swagger | ||
break; | ||
} | ||
/** | ||
// Documentation Specific | ||
this.group = this._opts.group; | ||
this.name = this._opts.name; | ||
this.description = this._opts.description; | ||
// What actually runs | ||
this.fn = this._opts.fn; | ||
} | ||
/** | ||
* handles router method in a promise | ||
* @param {*} fn RouterCallback | ||
*/ | ||
handlePromisePostTriggers(fn) { | ||
const {isPublicFacing} = this; | ||
return function (req, resp, next, ...args) { | ||
safePromisifyCallFn(fn, this, [req, resp, null, ...args]) | ||
.then((data) => { | ||
if (isPublicFacing) { | ||
return resp.ok(data); | ||
} | ||
handlePromisePostTriggers(fn) { | ||
const { isPublicFacing } = this; | ||
return function postPromiseHandle(req, resp, next, ...args) { | ||
safePromisifyCallFn(fn, this, [req, resp, null, ...args]) | ||
.then((data) => { | ||
if (isPublicFacing) { | ||
return resp.ok(data); | ||
} | ||
next(null, data) | ||
}) | ||
.catch((err) => next(err)); | ||
} | ||
} | ||
return next(null, data); | ||
}) | ||
.catch(err => next(err)); | ||
}; | ||
} | ||
/** | ||
/** | ||
* attach express router | ||
* @param {object} router express router | ||
*/ | ||
attachRouter(router) { | ||
this.router = router; | ||
attachRouter(router) { | ||
this.router = router; | ||
const matchedApiType = types[this.type]; | ||
const matchedApiType = types[this.type]; | ||
if (matchedApiType) { | ||
const cloutApi = matchedApiType.fn.apply(this, [this.fn]); | ||
} else { | ||
console.error(`unrecognised type ${this.type}`); | ||
} | ||
if (matchedApiType) { | ||
matchedApiType.fn.apply(this, [this.fn]); | ||
} else { | ||
console.error(`unrecognised type ${this.type}`); | ||
} | ||
}; | ||
} | ||
} | ||
module.exports = CloutApiRoute; |
@@ -8,6 +8,6 @@ /*! | ||
const { merge } = require('lodash'); | ||
const CloutApiRoute = require('../hookslib/CloutApiRoute'); | ||
const express = require('express'); | ||
const path = require('path'); | ||
const CloutApiRoute = require('./CloutApiRoute'); | ||
const utils = require('../lib/utils'); | ||
const path = require('path'); | ||
@@ -17,2 +17,31 @@ const PRIORITIZED_FILES = ['index.js', 'clout.hook.js']; | ||
/** | ||
* Sort array by PRIORITIZED_FILES | ||
* @param {string} a string | ||
* @param {string} b string | ||
*/ | ||
function sortByPriority(a, b) { | ||
const keys = {a, b}; | ||
const getPriorityFor = key => PRIORITIZED_FILES.indexOf(keys[key]); | ||
const priorityIndexForA = getPriorityFor(a); | ||
const bPriority = getPriorityFor(b); | ||
const weight = { | ||
a: priorityIndexForA + 1, | ||
b: bPriority + 1, | ||
}; | ||
const bBiggerThanA = weight.b > weight.a; | ||
const aBiggerThanB = weight.a > weight.b; | ||
if (aBiggerThanB) { | ||
return 1; | ||
} | ||
if (bBiggerThanA) { | ||
return -1; | ||
} | ||
return 0; | ||
} | ||
/** | ||
* CloutApiRoutes | ||
@@ -22,116 +51,102 @@ * @class | ||
class CloutApiRoutes { | ||
/** | ||
/** | ||
* @constructor | ||
* @param {object} app clout instance | ||
*/ | ||
constructor(app) { | ||
this.clout = app; | ||
this.config = { | ||
basePath: '/api', | ||
acceptTypes: { | ||
json: 'application/json', | ||
html: 'text/html' | ||
} | ||
}; | ||
this.routes = {}; | ||
this.router = express.Router(); | ||
this.initializeAcceptTypeHandler(); | ||
constructor(app) { | ||
this.clout = app; | ||
this.config = { | ||
basePath: '/api', | ||
acceptTypes: { | ||
json: 'application/json', | ||
html: 'text/html', | ||
}, | ||
}; | ||
this.routes = {}; | ||
this.router = express.Router(); | ||
this.initializeAcceptTypeHandler(); | ||
this.clout.logger.debug('Module CloutApiRoutes loaded'); | ||
} | ||
this.clout.logger.debug('Module CloutApiRoutes loaded'); | ||
} | ||
/** | ||
/** | ||
* Clout-JS handler for custom content requests | ||
*/ | ||
initializeAcceptTypeHandler() { | ||
this.router.param('acceptType', (req, resp, next, type) => { | ||
let acceptType = this.config.acceptTypes[type]; | ||
initializeAcceptTypeHandler() { | ||
this.router.param('acceptType', (req, resp, next, type) => { | ||
const acceptType = this.config.acceptTypes[type]; | ||
req.logger.info(`handling param '${acceptType}'`); | ||
req.logger.info(`handling param '${acceptType}'`); | ||
if (acceptType) { | ||
req.headers['accept'] = `${acceptType},` + req.headers['accept']; | ||
} | ||
if (acceptType) { | ||
req.headers.accept = `${acceptType},${req.headers.accept}`; | ||
} | ||
next(); | ||
}); | ||
} | ||
next(); | ||
}); | ||
} | ||
/** | ||
/** | ||
* Attaches router to clout-app | ||
*/ | ||
attachRouterToApp() { | ||
let basePath = this.config.basePath; | ||
attachRouterToApp() { | ||
const {basePath} = this.config; | ||
this.clout.app.use(basePath, this.router); | ||
this.clout.logger.debug(`router attached at ${basePath}`); | ||
} | ||
this.clout.app.use(basePath, this.router); | ||
this.clout.logger.debug(`router attached at ${basePath}`); | ||
} | ||
/** | ||
/** | ||
* Add CloutApiRouter to router | ||
* @param {object} CloutApiRouter | ||
*/ | ||
addRoute(cloutRoute) { | ||
if (!this.routes[cloutRoute.group]) { | ||
this.routes[cloutRoute.group] = []; | ||
} | ||
this.routes[cloutRoute.group].push(cloutRoute); | ||
cloutRoute.attachRouter(this.router); | ||
addRoute(cloutRoute) { | ||
if (!this.routes[cloutRoute.group]) { | ||
this.routes[cloutRoute.group] = []; | ||
} | ||
/** | ||
* Load APIs from a file | ||
* @param {string} filePath | ||
*/ | ||
loadAPIFromFile(filePath) { | ||
let groupName = path.basename(filePath).replace('.js', ''); | ||
let apis = require(filePath); | ||
this.routes[cloutRoute.group].push(cloutRoute); | ||
this.clout.logger.debug(`loading API from file ${filePath}`); | ||
cloutRoute.attachRouter(this.router); | ||
} | ||
return Object.keys(apis).map((apiName) => { | ||
let opts = merge({ | ||
name: apiName, | ||
group: groupName | ||
}, apis[apiName]); | ||
/** | ||
* Load APIs from a file | ||
* @param {string} filePath | ||
*/ | ||
loadAPIFromFile(filePath) { | ||
const groupName = path.basename(filePath).replace('.js', ''); | ||
const apis = require(filePath); | ||
return this.addRoute(new CloutApiRoute(opts)); | ||
}); | ||
} | ||
this.clout.logger.debug(`loading API from file ${filePath}`); | ||
/** | ||
* Finds all the **.js files inside a directory and loads it | ||
* @param {string} dir path containing directory of APIs | ||
*/ | ||
loadAPIsFromDir(dir) { | ||
let globbedDirs = utils.getGlobbedFiles(path.join(dir, '**/**.js')); | ||
return Object.keys(apis).map((apiName) => { | ||
const opts = merge({ | ||
name: apiName, | ||
group: groupName, | ||
}, apis[apiName]); | ||
return globbedDirs.sort((a, b) => { | ||
const aPriority = PRIORITIZED_FILES.indexOf(a) !== -1; | ||
const bPriority = PRIORITIZED_FILES.indexOf(b) !== -1; | ||
const weight = {a: 0, b: 0}; | ||
return this.addRoute(new CloutApiRoute(opts)); | ||
}); | ||
} | ||
if (aPriority !== -1) { | ||
weight.a = aPriority + 1; | ||
} | ||
if (bPriority !== -1) { | ||
weight.b = aPriority + 1; | ||
} | ||
/** | ||
* Finds all the **.js files inside a directory and loads it | ||
* @param {string} dir path containing directory of APIs | ||
*/ | ||
loadAPIsFromDir(dir) { | ||
const globbedDirs = utils.getGlobbedFiles(path.join(dir, '**/**.js')); | ||
return weight.a > weight.b ? 1 : weight.b > weight.a ? -1 : 0; | ||
}).map((filePath) => this.loadAPIFromFile(filePath)); | ||
} | ||
return globbedDirs.sort(sortByPriority).map(filePath => this.loadAPIFromFile(filePath)); | ||
} | ||
/** | ||
* Finds all the **.js files inside an array of directories and loads it | ||
* @param {array} dirs array of paths containing directory of APIs | ||
*/ | ||
loadAPIsFromDirs(dirs) { | ||
return dirs.map((dir) => this.loadAPIsFromDir(dir)); | ||
} | ||
}; | ||
/** | ||
* Finds all the **.js files inside an array of directories and loads it | ||
* @param {array} dirs array of paths containing directory of APIs | ||
*/ | ||
loadAPIsFromDirs(dirs) { | ||
return dirs.map(dir => this.loadAPIsFromDir(dir)); | ||
} | ||
} | ||
module.exports = CloutApiRoutes; |
@@ -1,2 +0,2 @@ | ||
import '@types/express'; | ||
import {Express} from 'express'; | ||
@@ -29,2 +29,1 @@ declare global { | ||
} | ||
@@ -12,5 +12,5 @@ /*! | ||
if (process.env.APPLICATION_DIR) { | ||
applicationDir = process.env.APPLICATION_DIR; | ||
applicationDir = process.env.APPLICATION_DIR; | ||
} else if (module && module.parent) { | ||
applicationDir = path.dirname(module.parent.filename); | ||
applicationDir = path.dirname(module.parent.filename); | ||
} | ||
@@ -24,3 +24,3 @@ | ||
if (!applicationDir) { | ||
throw new Error('application not found'); | ||
throw new Error('application not found'); | ||
} | ||
@@ -27,0 +27,0 @@ |
666
lib/Clout.js
@@ -12,4 +12,3 @@ /*! | ||
const fs = require('fs-extra'); | ||
const EventEmitter = require('events').EventEmitter; | ||
const util = require('util'); | ||
const { EventEmitter } = require('events'); | ||
@@ -23,2 +22,3 @@ const debug = require('debug')('clout:core'); | ||
const Config = require('./Config'); | ||
const CloutApiRoute = require('../hookslib/CloutApiRoute'); | ||
@@ -36,7 +36,7 @@ /** | ||
const CORE_PRIORITY = { | ||
CONFIG: 5, | ||
MIDDLEWARE: 10, | ||
MODEL: 15, | ||
API: 20, | ||
CONTROLLER: 25 | ||
CONFIG: 5, | ||
MIDDLEWARE: 10, | ||
MODEL: 15, | ||
API: 20, | ||
CONTROLLER: 25, | ||
}; | ||
@@ -51,401 +51,359 @@ | ||
class Clout extends EventEmitter { | ||
/** | ||
* @constructor | ||
* @param {path} rootDirectory application directory | ||
*/ | ||
constructor(rootDirectory) { | ||
super(); | ||
this.handleProcess(); | ||
/** | ||
* @constructor | ||
* @param {path} rootDirectory application directory | ||
*/ | ||
constructor(rootDirectory) { | ||
super(); | ||
this.process = process; | ||
this.handleProcess(); | ||
this.rootDirectory = null; | ||
this.package = {}; | ||
this.applicationPackage = {}; | ||
this.config = {}; | ||
this.logger = {debug: debug}; | ||
this.app = null; | ||
this.server = {}; | ||
this.modules = []; | ||
this.moduleCache = []; | ||
this.rootDirectory = null; | ||
this.package = {}; | ||
this.applicationPackage = {}; | ||
this.config = {}; | ||
this.logger = { debug }; | ||
this.app = null; | ||
this.server = {}; | ||
this.modules = []; | ||
this.moduleCache = []; | ||
// expose core libraries | ||
this.utils = utils; | ||
this.async = async; | ||
this._ = _; | ||
this.fs = fs; | ||
// expose core libraries | ||
this.utils = utils; | ||
this.async = async; | ||
this._ = _; | ||
this.fs = fs; | ||
// allow application hooks (Synchronous) | ||
this.CORE_PRIORITY = CORE_PRIORITY; | ||
this.hooks = { | ||
start: [], | ||
stop: [], | ||
reload: [] | ||
}; | ||
// allow application hooks (Synchronous) | ||
this.CORE_PRIORITY = CORE_PRIORITY; | ||
this.hooks = { | ||
start: [], | ||
stop: [], | ||
reload: [], | ||
}; | ||
// Load clout configuration | ||
this.config = new Config(); | ||
this.config.loadFromDir(path.join(__dirname, '../resources/conf')); | ||
// Load clout configuration | ||
this.config = new Config(); | ||
this.config.loadFromDir(path.join(__dirname, '../resources/conf')); | ||
this.applicationPackage = {}; | ||
this.applicationPackage = {}; | ||
// load clout package.json | ||
this.package = require(path.join(__dirname, '../package.json')); | ||
// load clout package.json | ||
this.package = require(path.join(__dirname, '../package.json')); | ||
// load clout modules | ||
if (this.package.modules) { | ||
this.addModules(this.package.modules); | ||
} | ||
// load clout modules | ||
if (this.package.modules) { | ||
this.addModules(this.package.modules); | ||
} | ||
if (rootDirectory) { | ||
// set application root directory | ||
this.rootDirectory = path.resolve(rootDirectory); | ||
if (rootDirectory) { | ||
// set application root directory | ||
this.rootDirectory = path.resolve(rootDirectory); | ||
// load application manifest | ||
['package.json', 'clout.json'].forEach((fileName) => { | ||
let filePath = path.resolve(this.rootDirectory, fileName); | ||
if (!fs.existsSync(filePath)) { | ||
return debug(`${fileName} not found`); | ||
} | ||
// load application manifest | ||
['package.json', 'clout.json'].forEach((fileName) => { | ||
const filePath = path.resolve(this.rootDirectory, fileName); | ||
if (!fs.existsSync(filePath)) { | ||
return debug(`${fileName} not found`); | ||
} | ||
_.merge(this.applicationPackage, require(filePath)); | ||
}); | ||
return _.merge(this.applicationPackage, require(filePath)); | ||
}); | ||
process.title = `[clout-js v${this.package.version}] ${this.applicationPackage.name}`; | ||
this.process.title = `[clout-js v${this.package.version}] ${this.applicationPackage.name}`; | ||
// add rootdir to node_modules | ||
module.paths.unshift(path.join(this.rootDirectory, 'node_modules')); | ||
// add rootdir to node_modules | ||
module.paths.unshift(path.join(this.rootDirectory, 'node_modules')); | ||
// load modules from application manifest | ||
if (this.applicationPackage.modules) { | ||
this.addModules(this.applicationPackage.modules); | ||
} | ||
} | ||
// load modules from application manifest | ||
if (this.applicationPackage.modules) { | ||
this.addModules(this.applicationPackage.modules); | ||
} | ||
} | ||
// append module configuration | ||
this.modules.forEach((module) => this.config.loadFromDir(path.join(module.path, 'conf'))); | ||
// append module configuration | ||
this.modules.forEach(module => this.config.loadFromDir(path.join(module.path, 'conf'))); | ||
// append application configuration (Overrides module conf) | ||
this.config.loadFromDir(path.join(this.rootDirectory, 'conf')); | ||
// append application configuration (Overrides module conf) | ||
this.config.loadFromDir(path.join(this.rootDirectory, 'conf')); | ||
// initialize logger | ||
this.logger = new Logger(this); | ||
// initialize logger | ||
this.logger = new Logger(this); | ||
// 1) load core hooks | ||
// 2) load application hooks | ||
// 3) load module hooks | ||
this.loadHooksFromDir(CLOUT_MODULE_PATH) | ||
.then(this.loadHooksFromDir(this.rootDirectory)) | ||
.then(() => new Promise((resolve, reject) => { | ||
async.each(this.modules, (module, next) => { | ||
this.loadHooksFromDir(module.path) | ||
.then(() => next()) | ||
.catch((err) => { | ||
console.error(err); | ||
next() | ||
}); | ||
}, (err) => { | ||
if (err) { return reject(err); } | ||
resolve(); | ||
}); | ||
// 1) load core hooks | ||
// 2) load application hooks | ||
// 3) load module hooks | ||
this.loadHooksFromDir(CLOUT_MODULE_PATH) | ||
.then(this.loadHooksFromDir(this.rootDirectory)) | ||
.then(async () => this.modules.map(async module => this.loadHooksFromDir(module.path))) | ||
.then((moduleHooks) => { | ||
this.initialized = true; | ||
return moduleHooks; | ||
}) | ||
.catch(err => console.error(err)); | ||
} | ||
this.initialized = true; | ||
})) | ||
.catch((err) => console.error(err)); | ||
} | ||
/** | ||
* hook into clout runtime | ||
* @param {string} event event name | ||
* @param {Function} fn function to execute | ||
* @param {String} fn._name hook name | ||
* @param {String} fn.group hook group | ||
* @param {Number} priority function priority | ||
* @param {Boolean} override override existing | ||
* @example | ||
* // register a function to the hook | ||
* clout.registerHook('start', function (next) { | ||
* next(); | ||
* }); | ||
* // invoking an error in clout runtime | ||
* clout.registerHook('start', function (next) { | ||
* next(new Error('Error executing function')); | ||
* }); | ||
*/ | ||
registerHook(event, fn, priority, override) { | ||
const hasPriority = typeof priority !== 'undefined'; | ||
const hasEvent = this.hooks[event]; | ||
debug('registerHook:event=%s:fn:priority=%s', event, hasEvent, priority); | ||
/** | ||
* hook into clout runtime | ||
* @param {string} event event name | ||
* @param {Function} fn function to execute | ||
* @param {String} fn._name hook name | ||
* @param {String} fn.group hook group | ||
* @param {Number} priority function priority | ||
* @param {Boolean} override override existing | ||
* @example | ||
* // register a function to the hook | ||
* clout.registerHook('start', function (next) { | ||
* next(); | ||
* }); | ||
* // invoking an error in clout runtime | ||
* clout.registerHook('start', function (next) { | ||
* next(new Error('Error executing function')); | ||
* }); | ||
*/ | ||
registerHook(event, fn, priority, override) { | ||
debug('registerHook:event=%s:fn:priority=%s', event, priority); | ||
if (!hasEvent) { | ||
throw new Error('Invalid Hook Event'); | ||
} | ||
if (!this.hooks.hasOwnProperty(event)) { | ||
throw new Error('Invalid Hook Event'); | ||
} | ||
if (hasPriority) { | ||
fn.priority = priority; | ||
} | ||
typeof priority !== 'undefined' && (fn.priority = priority); | ||
// find existing, override | ||
if (override === true) { | ||
debug('override'); | ||
for (let i = 0, l = this.hooks[event].length; i < l; i += 1) { | ||
const hook = this.hooks[event][i]; | ||
if (hook._name !== null && hook._name === fn._name && hook.group === fn.group) { | ||
debug('match found, overriden'); | ||
this.hooks[event][i] = fn; | ||
return; | ||
} | ||
} | ||
} | ||
// find existing, override | ||
if (override === true) { | ||
debug('override'); | ||
for (var i = 0, l = this.hooks[event].length; i < l; ++i) { | ||
var hook = this.hooks[event][i]; | ||
if (hook._name !== null && hook._name === fn._name && hook.group === fn.group) { | ||
debug('match found, overriden'); | ||
this.hooks[event][i] = fn; | ||
return; | ||
} | ||
} | ||
} | ||
// push is no priority | ||
if (!fn.priority) { | ||
debug('push hook (no priority)'); | ||
this.hooks[event].push(fn); | ||
return; | ||
} | ||
// push is no priority | ||
if (!fn.priority) { | ||
debug('push hook (no priority)'); | ||
return this.hooks[event].push(fn); | ||
} | ||
// find the correct place to register hook | ||
for (let i = 0, l = this.hooks[event].length; i < l; i += 1) { | ||
const tmpPriority = this.hooks[event][i].priority || 99999; | ||
// find the correct place to register hook | ||
for (var i = 0, l = this.hooks[event].length; i < l; ++i) { | ||
var tmp_p = this.hooks[event][i].priority || 99999; | ||
if (fn.priority < tmp_p) { | ||
debug('push hook at index %s', String(i)); | ||
return this.hooks[event].splice(i, 0, fn); | ||
} | ||
} | ||
if (fn.priority < tmpPriority) { | ||
debug('push hook at index %s', String(i)); | ||
this.hooks[event].splice(i, 0, fn); | ||
return; | ||
} | ||
} | ||
debug('push hook (lowest priority yet)'); | ||
return this.hooks[event].push(fn); | ||
} | ||
debug('push hook (lowest priority yet)'); | ||
this.hooks[event].push(fn); | ||
} | ||
/** | ||
* Loads hooks from directory | ||
* @param {Path} dir directory | ||
* @return {Promise} promise | ||
*/ | ||
loadHooksFromDir(dir) { | ||
var glob = path.join(dir, '/hooks/**/*.js'), | ||
files = utils.getGlobbedFiles(glob); | ||
/** | ||
* Loads hooks from directory | ||
* @param {Path} dir directory | ||
* @return {Promise} promise | ||
*/ | ||
loadHooksFromDir(dir) { | ||
const glob = path.join(dir, '/hooks/**/*.js'); | ||
const files = utils.getGlobbedFiles(glob); | ||
debug('loadHooksFromDir: %s', dir); | ||
debug('loadHooksFromDir: %s', dir); | ||
return new Promise((resolve, reject) => { | ||
async.each(files, (file, next) => { | ||
debug('loading hooks from file: %s', String(file)); | ||
return new Promise((resolve, reject) => { | ||
async.each(files, (file, next) => { | ||
debug('loading hooks from file: %s', String(file)); | ||
let hooks = require(file); | ||
let keys = Object.keys(hooks); | ||
const hooks = require(file); | ||
const keys = Object.keys(hooks); | ||
keys.forEach((key) => { | ||
let hook = hooks[key]; | ||
let args = []; | ||
keys.forEach((key) => { | ||
const hook = hooks[key]; | ||
const args = []; | ||
debug('Loading hook: %s', key); | ||
debug('Loading hook: %s', key); | ||
// create args | ||
if (!hook.event || !hook.fn) { | ||
throw new Error('Hook missing attributes'); | ||
} | ||
hook.fn.group = file.split('hooks/')[1].replace('.js', ''); | ||
hook.fn._name = key; | ||
args.push(hook.event); | ||
args.push(hook.fn); | ||
if (typeof hook.priority !== 'undefined') { | ||
if (typeof hook.priority === 'string') { | ||
if (!this.CORE_PRIORITY.hasOwnProperty(hook.priority)) { | ||
throw "Invalid priority type"; | ||
} | ||
hook.priority = this.CORE_PRIORITY[hook.priority]; | ||
} | ||
args.push(hook.priority); | ||
} else { | ||
args.push(null); | ||
} | ||
if (hook.override) { | ||
args.push(true); | ||
} | ||
this.registerHook.apply(this, args); | ||
}); | ||
next(); | ||
}, function done(err) { | ||
if (err) { | ||
debug(err); | ||
return reject(err); | ||
} | ||
debug('all hooks loaded from %s', dir); | ||
resolve(); | ||
}); | ||
}); | ||
} | ||
// create args | ||
if (!hook.event || !hook.fn) { | ||
throw new Error('Hook missing attributes'); | ||
} | ||
hook.fn.group = file.split('hooks/')[1].replace('.js', ''); | ||
hook.fn._name = key; | ||
args.push(hook.event); | ||
args.push(hook.fn); | ||
if (typeof hook.priority !== 'undefined') { | ||
if (typeof hook.priority === 'string') { | ||
if (!this.CORE_PRIORITY[hook.priority]) { | ||
throw new Error('Invalid priority type'); | ||
} | ||
hook.priority = this.CORE_PRIORITY[hook.priority]; | ||
} | ||
args.push(hook.priority); | ||
} else { | ||
args.push(null); | ||
} | ||
if (hook.override) { | ||
args.push(true); | ||
} | ||
addModules(modules) { | ||
debug('loading modules', JSON.stringify(modules)); | ||
modules.forEach((moduleName) => this.addModule(moduleName)); | ||
} | ||
this.registerHook(...args); | ||
}); | ||
next(); | ||
}, (err) => { | ||
if (err) { | ||
debug(err); | ||
return reject(err); | ||
} | ||
debug('all hooks loaded from %s', dir); | ||
resolve(); | ||
}); | ||
}); | ||
} | ||
/** | ||
* Load clout-js node module | ||
* @param {string} moduleName clout node module name | ||
*/ | ||
addModule(moduleName) { | ||
if (!!~this.moduleCache.indexOf(moduleName)) { | ||
debug('module: %s already loaded', moduleName); | ||
return; | ||
} | ||
addModules(modules) { | ||
debug('loading modules', JSON.stringify(modules)); | ||
modules.forEach(moduleName => this.addModule(moduleName)); | ||
} | ||
this.logger.debug('loading module: %s', moduleName); | ||
this.moduleCache.push(moduleName); | ||
/** | ||
* Load clout-js node module | ||
* @param {string} moduleName clout node module name | ||
*/ | ||
addModule(moduleName) { | ||
if (this.moduleCache.includes(moduleName)) { | ||
debug('module: %s already loaded', moduleName); | ||
return; | ||
} | ||
let cloutModule = { | ||
name: moduleName, | ||
path: path.dirname(require.resolve(moduleName)), | ||
manifest: {} | ||
}; | ||
this.logger.debug('loading module: %s', moduleName); | ||
this.moduleCache.push(moduleName); | ||
this.modules.push(cloutModule); | ||
debug(cloutModule); | ||
const cloutModule = { | ||
name: moduleName, | ||
path: path.dirname(require.resolve(moduleName)), | ||
manifest: {}, | ||
}; | ||
// load module manifest | ||
['package.json', 'clout.json'].forEach((fileName) => { | ||
let filePath = path.resolve(cloutModule.path, fileName); | ||
if (!fs.existsSync(filePath)) { | ||
return debug(`${fileName} not found`); | ||
} | ||
this.modules.push(cloutModule); | ||
debug(cloutModule); | ||
_.merge(cloutModule.manifest, require(filePath)); | ||
}); | ||
// load module manifest | ||
['package.json', 'clout.json'].forEach((fileName) => { | ||
const filePath = path.resolve(cloutModule.path, fileName); | ||
if (!fs.existsSync(filePath)) { | ||
return debug(`${fileName} not found`); | ||
} | ||
// load module modules | ||
if (cloutModule.manifest.modules) { | ||
debug('%s loading modules %s', moduleName, manifest.modules); | ||
this.addModules(manifest.modules); | ||
} | ||
} | ||
_.merge(cloutModule.manifest, require(filePath)); | ||
}); | ||
/** | ||
* Start clout | ||
* @return {Promise} returns a promise | ||
*/ | ||
start() { | ||
this.emit('initialized'); | ||
// load module modules | ||
if (cloutModule.manifest.modules) { | ||
debug('%s loading modules %s', moduleName, cloutModule.manifest.modules); | ||
this.addModules(cloutModule.manifest.modules); | ||
} | ||
} | ||
if (!this.initialized) { | ||
return new Promise((resolve) => { | ||
setTimeout(() => resolve(this.start()), 100); | ||
}); | ||
} | ||
/** | ||
* Start clout | ||
* @return {Promise} returns a promise | ||
*/ | ||
start() { | ||
this.emit('initialized'); | ||
this.emit('start'); | ||
if (!this.initialized) { | ||
return new Promise((resolve) => { | ||
setTimeout(() => resolve(this.start()), 100); | ||
}); | ||
} | ||
return new Promise((resolve, reject) => { | ||
process.nextTick(() => { | ||
async.eachLimit(this.hooks.start, 1, (hook, next) => { | ||
debug('executing', hook.name || hook._name, hook.group); | ||
let hookResponse = hook.apply(this, [next]); | ||
this.emit('start'); | ||
// support promises | ||
if (typeof hookResponse === 'object') { | ||
hookResponse.then(next, (err) => next(null, err)); | ||
} | ||
}, (err) => { | ||
if (err) { | ||
debug(err); | ||
return reject(err); | ||
} | ||
resolve(); | ||
this.emit('started'); | ||
}); | ||
}); | ||
}); | ||
} | ||
return new Promise((resolve, reject) => { | ||
process.nextTick(() => { | ||
async.eachLimit(this.hooks.start, 1, (hook, next) => { | ||
debug('executing', hook.name || hook._name, hook.group); | ||
const hookResponse = hook.apply(this, [next]); | ||
// TODO:- investigate if we still need this? | ||
/** | ||
* Add API | ||
* @param {string} path api path | ||
* @param {function} fn express function | ||
*/ | ||
addApi(path, fn) { | ||
this.app.use(path, function (req, resp, next) { | ||
let promiseResponse = fn.apply(this, arguments); | ||
// support promises | ||
if (typeof hookResponse === 'object') { | ||
hookResponse.then(next, err => next(null, err)); | ||
} | ||
}, (err) => { | ||
if (err) { | ||
debug(err); | ||
return reject(err); | ||
} | ||
resolve(); | ||
this.emit('started'); | ||
}); | ||
}); | ||
}); | ||
} | ||
// support for prmises | ||
// bind to app.use | ||
if (String(promiseResponse) === '[object Promise]') { | ||
promiseResponse | ||
.then((payload) => { | ||
switch (Object.prototype.toString.call(payload)) { | ||
case '[object Object]': | ||
case '[object String]': | ||
resp.success(payload); | ||
break; | ||
case '[object Undefined]': | ||
break; | ||
default: | ||
console.error('type not supported'); | ||
resp.error('response type is invalid'); | ||
break; | ||
} | ||
next(); | ||
}) | ||
.catch((payload) => { | ||
switch (Object.prototype.toString.call(payload)) { | ||
case '[object Object]': | ||
case '[object String]': | ||
resp.error(payload); | ||
break; | ||
case '[object Undefined]': | ||
break; | ||
default: | ||
console.error('type not supported'); | ||
resp.error('response type is invalid'); | ||
break; | ||
} | ||
next(); | ||
}); | ||
} | ||
}); | ||
// TODO:- investigate if we still need this? | ||
/** | ||
* Add API | ||
* @param {string} path api path | ||
* @param {function} fn express function | ||
*/ | ||
addApi(apiPath, fn) { | ||
if (this.core.api) { | ||
this.core.addRoute(new CloutApiRoute({ | ||
path: apiPath, | ||
fn, | ||
})); | ||
} | ||
} | ||
return new Promise.resolve(); | ||
} | ||
/** | ||
* Stop clout | ||
* @return {Promise} returns a promise | ||
*/ | ||
stop() { | ||
this.emit('stop'); | ||
return new Promise((resolve, reject) => { | ||
async.eachLimit(this.hooks.stop, 1, (hook, next) => { | ||
hook.apply(this, [next]); | ||
}, (err) => { | ||
if (err) { | ||
debug(err); | ||
return reject(err); | ||
} | ||
/** | ||
* Stop clout | ||
* @return {Promise} returns a promise | ||
*/ | ||
stop() { | ||
this.emit('stop'); | ||
return new Promise((resolve, reject) => { | ||
async.eachLimit(this.hooks.stop, 1, (hook, next) => { | ||
hook.apply(this, [next]); | ||
}, (err) => { | ||
if (err) { | ||
debug(err); | ||
return reject(err); | ||
} | ||
resolve(); | ||
this.emit('stopped'); | ||
}); | ||
}); | ||
} | ||
resolve(); | ||
this.emit('stopped'); | ||
}); | ||
}); | ||
} | ||
/** | ||
* Reload clout | ||
* @return {Promise} returns a promise | ||
*/ | ||
reload() { | ||
this.emit('reload'); | ||
/** | ||
* Reload clout | ||
* @return {Promise} returns a promise | ||
*/ | ||
reload() { | ||
this.emit('reload'); | ||
return this.stop() | ||
.then(this.start) | ||
.then(() => this.emit('reloaded')); | ||
} | ||
return this.stop() | ||
.then(this.start) | ||
.then(() => { | ||
deferred.resolve(); | ||
this.emit('reloaded'); | ||
}); | ||
} | ||
handleProcess() { | ||
process.on('unhandledRejection', (err) => { | ||
console.error(err); | ||
}); | ||
process.on('uncaughtException', (err) => { | ||
console.log(err); | ||
process.exit(0); | ||
}); | ||
} | ||
handleProcess() { | ||
this.process.on('unhandledRejection', err => console.error(err)); | ||
this.process.on('uncaughtException', (err) => { | ||
console.error(err); | ||
process.exit(0); | ||
}); | ||
} | ||
} | ||
@@ -452,0 +410,0 @@ |
@@ -7,72 +7,77 @@ /*! | ||
const path = require('path'); | ||
const fs = require('fs'); | ||
const _ = require('lodash'); | ||
const async = require('async'); | ||
const utils = require('./utils'); | ||
const debug = require('debug')('clout:commands'); | ||
const prompt = require('prompt'); | ||
const utils = require('./utils'); | ||
module.exports = function config(clout) { | ||
var COMMANDS_DIR = path.join(__dirname, 'commands'); | ||
debug('COMMANDS_DIR: %s', COMMANDS_DIR); | ||
const COMMANDS_DIR = path.join(__dirname, 'commands'); | ||
debug('COMMANDS_DIR: %s', COMMANDS_DIR); | ||
prompt.message = ""; | ||
prompt.delimiter = ""; | ||
prompt.message = ''; | ||
prompt.delimiter = ''; | ||
var globPattern = COMMANDS_DIR + '**/*.js'; | ||
utils.getGlobbedFiles(globPattern).forEach(function (filePath) { | ||
var commandConf = require(filePath), | ||
command = clout.program.command(commandConf.command).desc(commandConf.desc); | ||
const globPattern = `${COMMANDS_DIR}**/*.js`; | ||
utils.getGlobbedFiles(globPattern).forEach((filePath) => { | ||
const commandConf = require(filePath); | ||
const command = clout.program.command(commandConf.command).desc(commandConf.desc); | ||
debug('creating command `%s`: %s', commandConf.command, commandConf.desc); | ||
debug('creating command `%s`: %s', commandConf.command, commandConf.desc); | ||
// load options | ||
commandConf.options && commandConf.options.forEach(function (option) { | ||
debug('option: %s', option); | ||
command.option.apply(command, option); | ||
}); | ||
// we may add custom loaders for command actions | ||
command.action(function (argv) { | ||
if (commandConf.banner !== false) { | ||
// print banner | ||
console.log('\n %s %s', clout.package.name.cyan, clout.package.version); | ||
console.log(' %s\n', clout.package.description.grey); | ||
} | ||
async.series([ | ||
function ensureMissingOptions(next) { | ||
var missingOptions = []; | ||
// check missing options | ||
commandConf.required && commandConf.required.forEach(function (required) { | ||
var value = argv.param.apply(argv, typeof required.option === 'string' ? [required.option] : required.option); | ||
if (!value) { | ||
missingOptions.push(required); | ||
} | ||
}); | ||
// load options | ||
if (commandConf.options) { | ||
commandConf.options.forEach((option) => { | ||
debug('option: %s', option); | ||
command.option(...option); | ||
}); | ||
} | ||
if (missingOptions.length === 0) { | ||
return next(); | ||
} | ||
// we may add custom loaders for command actions | ||
command.action((argv) => { | ||
if (commandConf.banner !== false) { | ||
// print banner | ||
console.log('\n %s %s', clout.package.name.cyan, clout.package.version); | ||
console.log(' %s\n', clout.package.description.grey); | ||
} | ||
async.series([ | ||
function ensureMissingOptions(next) { | ||
const missingOptions = []; | ||
// check missing options | ||
if (commandConf.required) { | ||
commandConf.required.forEach((required) => { | ||
const value = argv.param(...typeof required.option === 'string' ? [required.option] : required.option); | ||
if (!value) { | ||
missingOptions.push(required); | ||
} | ||
}); | ||
} | ||
// prompt for missing | ||
prompt.get(missingOptions, function (err, result) { | ||
if (!result) { | ||
// probably a SIGKILL | ||
console.log(''); | ||
return; | ||
} | ||
_.merge(argv.params, result); | ||
next(); | ||
}); | ||
prompt.start(); | ||
} | ||
], function (err) { | ||
if (err) { | ||
console.error(err.red); | ||
return; | ||
} | ||
this.prompt = prompt; | ||
commandConf.action.apply(this, [argv]); | ||
}); | ||
}); | ||
}); | ||
if (missingOptions.length === 0) { | ||
return next(); | ||
} | ||
// prompt for missing | ||
prompt.get(missingOptions, (err, result) => { | ||
if (!result) { | ||
// probably a SIGKILL | ||
console.log(''); | ||
return; | ||
} | ||
_.merge(argv.params, result); | ||
next(); | ||
}); | ||
prompt.start(); | ||
}, | ||
], function onError(err) { | ||
if (err) { | ||
console.error(err.red); | ||
return; | ||
} | ||
this.prompt = prompt; | ||
commandConf.action.apply(this, [argv]); | ||
}); | ||
}); | ||
}); | ||
}; |
@@ -6,93 +6,96 @@ /*! | ||
*/ | ||
const | ||
_ = require('lodash'), | ||
debug = require('debug')('clout:install'), | ||
path = require('path'), | ||
async = require('async'), | ||
fs = require('fs-extra'), | ||
exec = require('child_process').exec; | ||
const _ = require('lodash'); | ||
const debug = require('debug')('clout:install'); | ||
const path = require('path'); | ||
const async = require('async'); | ||
const fs = require('fs-extra'); | ||
const {exec} = require('child_process'); | ||
module.exports = { | ||
command: 'install', | ||
desc: 'install a clout module', | ||
options: [ | ||
['--name', 'service name:'], | ||
['--projectDir', 'Project Directory'], | ||
['--workspaceDir', 'Workspace Directory'], | ||
['--module', 'Workspace Directory (required)'] | ||
], | ||
required: [ | ||
{ | ||
description: 'Module Name:', | ||
option: 'module', | ||
name: 'module', | ||
required: true | ||
} | ||
], | ||
action: function (argv) { | ||
var serviceName = argv.param('name'), | ||
serviceId = serviceName && serviceName.replace(' ', '-').toLowerCase(), | ||
projectDir = undefined, | ||
moduleName = argv.param('module'), | ||
pkg = {}, | ||
cloutPkg = {}; | ||
command: 'install', | ||
desc: 'install a clout module', | ||
options: [ | ||
['--name', 'service name:'], | ||
['--projectDir', 'Project Directory'], | ||
['--workspaceDir', 'Workspace Directory'], | ||
['--module', 'Workspace Directory (required)'], | ||
], | ||
required: [ | ||
{ | ||
description: 'Module Name:', | ||
option: 'module', | ||
name: 'module', | ||
required: true, | ||
}, | ||
], | ||
action(argv) { | ||
const serviceName = argv.param('name'); | ||
const serviceId = serviceName && serviceName.replace(' ', '-').toLowerCase(); | ||
const moduleName = argv.param('module'); | ||
let pkg = {}; | ||
const cloutPkg = {}; | ||
let projectDir; | ||
debug('serviceName: %s', serviceName); | ||
debug('serviceId: %s', serviceId); | ||
// get projectDirectory | ||
if (argv.param('projectDir')) { | ||
projectDir = path.resolve(projectDir); | ||
} else if (argv.param('workspaceDir')) { | ||
projectDir = path.join(argv.param('workspaceDir'), serviceId); | ||
} else { | ||
serviceId && (projectDir = path.join(process.cwd(), serviceId)); | ||
if (!serviceId || !fs.existsSync(projectDir)) { | ||
projectDir = process.cwd(); | ||
} | ||
} | ||
debug('serviceName: %s', serviceName); | ||
debug('serviceId: %s', serviceId); | ||
// get projectDirectory | ||
if (argv.param('projectDir')) { | ||
projectDir = path.resolve(projectDir); | ||
} else if (argv.param('workspaceDir')) { | ||
projectDir = path.join(argv.param('workspaceDir'), serviceId); | ||
} else { | ||
if (serviceId) { | ||
projectDir = path.join(process.cwd(), serviceId); | ||
} | ||
debug('projectDir: %s', projectDir); | ||
if (!serviceId || !fs.existsSync(projectDir)) { | ||
projectDir = process.cwd(); | ||
} | ||
} | ||
async.series([ | ||
// check if project already exists | ||
function checkIfProjectExists(next) { | ||
debug('projectDir exists? %s', fs.existsSync(projectDir)); | ||
if (!fs.existsSync(projectDir)) { | ||
return next('Project does not exist'); | ||
} | ||
return next(); | ||
}, | ||
// install module | ||
function (next) { | ||
// run npm install | ||
console.log('Installing project dependencies'); | ||
exec('cd "' + projectDir + '" && npm install ' + moduleName + ' --save', function (error, stdout, stderr) { | ||
next(); | ||
}); | ||
}, | ||
// save module information | ||
function (next) { | ||
var pkgPath = path.join(projectDir, 'package.json'), | ||
cloutPkgPath = path.join(projectDir, 'clout.json'); | ||
// load files | ||
fs.existsSync(pkgPath) && (pkg = require(pkgPath)); | ||
fs.existsSync(cloutPkgPath) && (pkg = require(cloutPkg)); | ||
if (pkg.modules) { | ||
// save here | ||
pkg.modules.push(moduleName); | ||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, '\t')); | ||
return next(); | ||
} | ||
!cloutPkg.modules && (cloutPkg.modules = []); | ||
cloutPkg.modules.push(moduleName); | ||
fs.writeFileSync(cloutPkgPath, JSON.stringify(cloutPkg, null, '\t')); | ||
next(); | ||
} | ||
], function (err) { | ||
if (err) { | ||
return console.error(err.red); | ||
} | ||
console.error('Module Installed'); | ||
}); | ||
} | ||
debug('projectDir: %s', projectDir); | ||
async.series([ | ||
// check if project already exists | ||
function checkIfProjectExists(next) { | ||
debug('projectDir exists? %s', fs.existsSync(projectDir)); | ||
if (!fs.existsSync(projectDir)) { | ||
return next('Project does not exist'); | ||
} | ||
return next(); | ||
}, | ||
// install module | ||
function (next) { | ||
// run npm install | ||
console.log('Installing project dependencies'); | ||
exec(`cd "${projectDir}" && npm install ${moduleName} --save`, (error, stdout, stderr) => { | ||
next(); | ||
}); | ||
}, | ||
// save module information | ||
function (next) { | ||
let pkgPath = path.join(projectDir, 'package.json'), | ||
cloutPkgPath = path.join(projectDir, 'clout.json'); | ||
// load files | ||
fs.existsSync(pkgPath) && (pkg = require(pkgPath)); | ||
fs.existsSync(cloutPkgPath) && (pkg = require(cloutPkg)); | ||
if (pkg.modules) { | ||
// save here | ||
pkg.modules.push(moduleName); | ||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, '\t')); | ||
return next(); | ||
} | ||
!cloutPkg.modules && (cloutPkg.modules = []); | ||
cloutPkg.modules.push(moduleName); | ||
fs.writeFileSync(cloutPkgPath, JSON.stringify(cloutPkg, null, '\t')); | ||
next(); | ||
}, | ||
], (err) => { | ||
if (err) { | ||
return console.error(err.red); | ||
} | ||
console.error('Module Installed'); | ||
}); | ||
}, | ||
}; |
@@ -7,130 +7,130 @@ /*! | ||
const | ||
_ = require('lodash'), | ||
debug = require('debug')('clout:new'), | ||
path = require('path'), | ||
async = require('async'), | ||
ejs = require('ejs'), | ||
utils = require('../utils'), | ||
fs = require('fs-extra'), | ||
exec = require('child_process').exec | ||
_ = require('lodash'), | ||
debug = require('debug')('clout:new'), | ||
path = require('path'), | ||
async = require('async'), | ||
ejs = require('ejs'), | ||
utils = require('../utils'), | ||
fs = require('fs-extra'), | ||
exec = require('child_process').exec; | ||
const | ||
TEMPLATES_DIR = path.join(__dirname, '../../resources/templates/'), | ||
BASE_TEMPLATE = path.join(TEMPLATES_DIR, 'base'); | ||
TEMPLATES_DIR = path.join(__dirname, '../../resources/templates/'), | ||
BASE_TEMPLATE = path.join(TEMPLATES_DIR, 'base'); | ||
module.exports = { | ||
command: 'new', | ||
desc: 'create a new service', | ||
options: [ | ||
['--name', 'service name:'], | ||
['--template', 'template:'], | ||
['--projectDir', 'Project Directory'], | ||
['--workspaceDir', 'Workspace Directory'], | ||
['-f, --force', 'Force project creation'] | ||
], | ||
required: [ | ||
{ | ||
description: 'Service Name:', | ||
option: 'name', | ||
name: 'name', | ||
required: true | ||
} | ||
], | ||
action: function (argv) { | ||
var serviceName = argv.param('name'), | ||
template = argv.param('template') || 'sample', | ||
serviceId = serviceName.replace(' ', '-').toLowerCase(), | ||
projectDir = undefined; | ||
command: 'new', | ||
desc: 'create a new service', | ||
options: [ | ||
['--name', 'service name:'], | ||
['--template', 'template:'], | ||
['--projectDir', 'Project Directory'], | ||
['--workspaceDir', 'Workspace Directory'], | ||
['-f, --force', 'Force project creation'], | ||
], | ||
required: [ | ||
{ | ||
description: 'Service Name:', | ||
option: 'name', | ||
name: 'name', | ||
required: true, | ||
}, | ||
], | ||
action(argv) { | ||
let serviceName = argv.param('name'), | ||
template = argv.param('template') || 'sample', | ||
serviceId = serviceName.replace(' ', '-').toLowerCase(), | ||
projectDir; | ||
debug('serviceName: %s', serviceName); | ||
debug('serviceId: %s', serviceId); | ||
// get projectDirectory | ||
if (argv.param('projectDir')) { | ||
projectDir = path.resolve(projectDir); | ||
} else if (argv.param('workspaceDir')) { | ||
projectDir = path.join(argv.param('workspaceDir'), serviceId); | ||
} else { | ||
projectDir = path.join(process.cwd(), serviceId); | ||
} | ||
debug('serviceName: %s', serviceName); | ||
debug('serviceId: %s', serviceId); | ||
// get projectDirectory | ||
if (argv.param('projectDir')) { | ||
projectDir = path.resolve(projectDir); | ||
} else if (argv.param('workspaceDir')) { | ||
projectDir = path.join(argv.param('workspaceDir'), serviceId); | ||
} else { | ||
projectDir = path.join(process.cwd(), serviceId); | ||
} | ||
debug('projectDir: %s', projectDir); | ||
debug('projectDir: %s', projectDir); | ||
async.series([ | ||
// check if project already exists | ||
function checkIfProjectExists(next) { | ||
debug('projectDir exists? %s', fs.existsSync(projectDir)); | ||
if (!fs.existsSync(projectDir)) { | ||
return next(); | ||
} | ||
console.warn('Project already exists'.yellow); | ||
if (argv.mode('force', 'f')) { | ||
console.warn('Forcing project creation'.yellow); | ||
return next(); | ||
} | ||
prompt.get([{ | ||
description: 'Would you like to overide the existing project:', | ||
option: 'force', | ||
name: 'force' | ||
}], function (err, result) { | ||
if (!result) { | ||
// probably a SIGKILL | ||
console.log(''); | ||
return; | ||
} | ||
if (!!result.force && ['y', 'Y', 'yes', 'YES', 'true', 'TRUE', 't', 'T'].indexOf(result.force) > -1) { | ||
// carry on | ||
return next(); | ||
} | ||
console.error('Project could not be created :('.red); | ||
}); | ||
prompt.start(); | ||
}, | ||
// create Project in directory | ||
function copyBaseProjectAndRender(next) { | ||
debug('copySync BASE_TEMPLATE: %s -> projectDir', BASE_TEMPLATE); | ||
fs.copySync(BASE_TEMPLATE, projectDir); | ||
var EJSData = { | ||
serviceName: serviceName, | ||
serviceId: serviceId, | ||
author: 'Muhammad Dadu' | ||
}; | ||
debug('EJSData: %s', JSON.stringify(EJSData)); | ||
utils.getGlobbedFiles(projectDir + '/*.ejs').forEach(function load(filePath) { | ||
var file = fs.readFileSync(filePath, 'ascii'), | ||
rendered = ejs.render(file, EJSData), | ||
newFilePath = filePath.replace('.ejs', ''); | ||
fs.writeFileSync(newFilePath, rendered); | ||
fs.removeSync(filePath); | ||
}); | ||
next(); | ||
}, | ||
function copyTemplate(next) { | ||
var templateDir = path.join(TEMPLATES_DIR, template); | ||
if (!fs.existsSync(templateDir)) { | ||
return next('Template `' + template + '` not found'); | ||
} | ||
fs.copySync(templateDir, projectDir); | ||
next(); | ||
}, | ||
function (next) { | ||
var cloutConf_json = { | ||
nodes: 1 | ||
}; | ||
fs.writeFileSync(path.join(projectDir, 'clout.json'), JSON.stringify(cloutConf_json, null, '\t')); | ||
next(); | ||
}, | ||
function (next) { | ||
// run npm install | ||
console.log('Installing project dependencies'); | ||
exec('cd "' + projectDir + '" && npm install', function (error, stdout, stderr) { | ||
next(); | ||
}); | ||
} | ||
], function (err) { | ||
if (err) { | ||
return console.error(err.red); | ||
} | ||
console.error('Project Created'); | ||
}); | ||
} | ||
async.series([ | ||
// check if project already exists | ||
function checkIfProjectExists(next) { | ||
debug('projectDir exists? %s', fs.existsSync(projectDir)); | ||
if (!fs.existsSync(projectDir)) { | ||
return next(); | ||
} | ||
console.warn('Project already exists'.yellow); | ||
if (argv.mode('force', 'f')) { | ||
console.warn('Forcing project creation'.yellow); | ||
return next(); | ||
} | ||
prompt.get([{ | ||
description: 'Would you like to overide the existing project:', | ||
option: 'force', | ||
name: 'force', | ||
}], (err, result) => { | ||
if (!result) { | ||
// probably a SIGKILL | ||
console.log(''); | ||
return; | ||
} | ||
if (!!result.force && ['y', 'Y', 'yes', 'YES', 'true', 'TRUE', 't', 'T'].indexOf(result.force) > -1) { | ||
// carry on | ||
return next(); | ||
} | ||
console.error('Project could not be created :('.red); | ||
}); | ||
prompt.start(); | ||
}, | ||
// create Project in directory | ||
function copyBaseProjectAndRender(next) { | ||
debug('copySync BASE_TEMPLATE: %s -> projectDir', BASE_TEMPLATE); | ||
fs.copySync(BASE_TEMPLATE, projectDir); | ||
const EJSData = { | ||
serviceName, | ||
serviceId, | ||
author: 'Muhammad Dadu', | ||
}; | ||
debug('EJSData: %s', JSON.stringify(EJSData)); | ||
utils.getGlobbedFiles(`${projectDir}/*.ejs`).forEach((filePath) => { | ||
let file = fs.readFileSync(filePath, 'ascii'), | ||
rendered = ejs.render(file, EJSData), | ||
newFilePath = filePath.replace('.ejs', ''); | ||
fs.writeFileSync(newFilePath, rendered); | ||
fs.removeSync(filePath); | ||
}); | ||
next(); | ||
}, | ||
function copyTemplate(next) { | ||
const templateDir = path.join(TEMPLATES_DIR, template); | ||
if (!fs.existsSync(templateDir)) { | ||
return next(`Template \`${template}\` not found`); | ||
} | ||
fs.copySync(templateDir, projectDir); | ||
next(); | ||
}, | ||
function (next) { | ||
const cloutConf_json = { | ||
nodes: 1, | ||
}; | ||
fs.writeFileSync(path.join(projectDir, 'clout.json'), JSON.stringify(cloutConf_json, null, '\t')); | ||
next(); | ||
}, | ||
function (next) { | ||
// run npm install | ||
console.log('Installing project dependencies'); | ||
exec(`cd "${projectDir}" && npm install`, (error, stdout, stderr) => { | ||
next(); | ||
}); | ||
}, | ||
], (err) => { | ||
if (err) { | ||
return console.error(err.red); | ||
} | ||
console.error('Project Created'); | ||
}); | ||
}, | ||
}; |
@@ -9,2 +9,3 @@ /*! | ||
*/ | ||
const debug = require('debug')('clout:config'); | ||
const path = require('path'); | ||
@@ -14,3 +15,2 @@ const fs = require('fs-extra'); | ||
const utils = require('./utils'); | ||
const debug = require('debug')('clout:config'); | ||
@@ -22,57 +22,56 @@ /** | ||
class Config { | ||
/** | ||
* @constructor | ||
* @param {object} defaultConf default configuration object | ||
*/ | ||
constructor(defaultConf) { | ||
debug('initializing config'); | ||
this.env = process.env.NODE_ENV || 'development'; | ||
/** | ||
* @constructor | ||
* @param {object} defaultConf default configuration object | ||
*/ | ||
constructor(defaultConf) { | ||
debug('initializing config'); | ||
this.env = process.env.NODE_ENV || 'development'; | ||
if (defaultConf) { | ||
_.merge(this, defaultConf); | ||
} | ||
} | ||
if (defaultConf) { | ||
_.merge(this, defaultConf); | ||
} | ||
} | ||
/** | ||
* Loads configuration | ||
* @param {object} dir directory | ||
*/ | ||
loadFromDir(dir) { | ||
debug('loading config from dir %s', dir); | ||
if (!fs.existsSync(dir)) { | ||
debug('dir does not exist'); | ||
return; | ||
} | ||
/** | ||
* Loads configuration | ||
* @param {object} dir directory | ||
*/ | ||
loadFromDir(dir) { | ||
debug('loading config from dir %s', dir); | ||
if (!fs.existsSync(dir)) { | ||
debug('dir does not exist'); | ||
return; | ||
} | ||
// load configurations | ||
const globDefault = '*default.js'; | ||
const globEnv = `*${this.env}.js`; | ||
// load configurations | ||
var globDefault = '*default.js', | ||
globEnv = '*' + this.env + '.js'; | ||
// 1) load default configuration | ||
utils.getGlobbedFiles(path.join(dir, globDefault)).forEach((file) => { | ||
debug('loading config from: %s', file); | ||
_.merge(this, require(file)); | ||
}); | ||
// 1) load default configuration | ||
utils.getGlobbedFiles(path.join(dir, globDefault)).forEach((file) => { | ||
debug('loading config from: %s', file); | ||
_.merge(this, require(file)); | ||
}); | ||
// 2) load env specific configuration | ||
utils.getGlobbedFiles(path.join(dir, globEnv)).forEach((file) => { | ||
debug('loading config from: %s', file); | ||
_.merge(this, require(file)); | ||
}); | ||
} | ||
// 2) load env specific configuration | ||
utils.getGlobbedFiles(path.join(dir, globEnv)).forEach((file) => { | ||
debug('loading config from: %s', file); | ||
_.merge(this, require(file)); | ||
}); | ||
} | ||
/** | ||
* Add config | ||
* @param {array} config configuration object | ||
*/ | ||
merge(...opts) { | ||
_.merge(...[this, ...opts]); | ||
} | ||
/** | ||
* Add config | ||
* @param {array} config configuration object | ||
*/ | ||
merge(...opts) { | ||
_.merge(...[this, ...opts]); | ||
} | ||
toString() { | ||
return `[clout config] ${JSON.stringify(this, null, ' ')}`; | ||
} | ||
toString() { | ||
return `[clout config] ${JSON.stringify(this, null, ' ')}`; | ||
} | ||
} | ||
module.exports = Config; |
@@ -9,3 +9,2 @@ /*! | ||
*/ | ||
const util = require('util'); | ||
const path = require('path'); | ||
@@ -24,76 +23,75 @@ | ||
class Logger extends winston.Logger { | ||
/** | ||
* @constructor | ||
* @param {object} clout clout-js instance | ||
*/ | ||
constructor(clout) { | ||
debug('initialize logger'); | ||
super(); | ||
/** | ||
* @constructor | ||
* @param {object} clout clout-js instance | ||
*/ | ||
constructor(clout) { | ||
debug('initialize logger'); | ||
super(); | ||
this.clout = clout; | ||
this.transports = []; | ||
this.clout = clout; | ||
this.transports = []; | ||
this.clout.registerHook('start', this.appendToMiddleware, this.clout.CORE_PRIORITY.MIDDLEWARE); | ||
this.clout.registerHook('start', this.appendToMiddleware, this.clout.CORE_PRIORITY.MIDDLEWARE); | ||
if (this.clout.config.logToDir === true) { | ||
this.logToDir(); | ||
} | ||
if (this.clout.config.logToDir === true) { | ||
this.logToDir(); | ||
} | ||
// dont log to console in production | ||
if (clout.config.env !== 'production') { | ||
this.logToConsole(); | ||
} | ||
// dont log to console in production | ||
if (clout.config.env !== 'production') { | ||
this.logToConsole(); | ||
} | ||
this.saveConfiguration(); | ||
} | ||
this.saveConfiguration(); | ||
} | ||
/** | ||
* Appends logger to middleware | ||
*/ | ||
appendToMiddleware(next) { | ||
this.app.use((req, resp, done) => { | ||
req.logger = this.logger; | ||
done(); | ||
}); | ||
next(); | ||
} | ||
/** | ||
* Appends logger to middleware | ||
*/ | ||
appendToMiddleware(next) { | ||
this.app.use((req, resp, next) => { | ||
req.logger = this.logger; | ||
next(); | ||
}); | ||
next(); | ||
} | ||
/** | ||
* Enables logging to console | ||
*/ | ||
logToConsole() { | ||
this.transports.push(new (winston.transports.Console)()); | ||
} | ||
/** | ||
* Enables logging to console | ||
*/ | ||
logToConsole() { | ||
this.transports.push(new (winston.transports.Console)()); | ||
} | ||
/** | ||
* Enables logging to application directory | ||
*/ | ||
logToDir() { | ||
const logDirectory = path.join(this.clout.rootDirectory, 'logs'); | ||
/** | ||
* Enables logging to application directory | ||
*/ | ||
logToDir() { | ||
let logDirectory = path.join(this.clout.rootDirectory, 'logs'); | ||
fs.ensureDirSync(logDirectory); | ||
debug('logDirectory: %s', logDirectory); | ||
debug('add transport', 'DailyRotateFile'); | ||
fs.ensureDirSync(logDirectory); | ||
debug('logDirectory: %s', logDirectory); | ||
debug('add transport', 'DailyRotateFile'); | ||
const dailyRotateFile = new DailyRotateFile({ | ||
filename: path.join(logDirectory, 'clout_'), | ||
datePattern: 'yyyy-MM-dd.log', | ||
}); | ||
let dailyRotateFile = new DailyRotateFile({ | ||
filename: path.join(logDirectory, 'clout_'), | ||
datePattern: 'yyyy-MM-dd.log' | ||
}); | ||
this.transports.push(dailyRotateFile); | ||
} | ||
this.transports.push(dailyRotateFile); | ||
} | ||
/** | ||
* Save configuration and update log level | ||
* @param {String} level log level | ||
*/ | ||
saveConfiguration(level) { | ||
this.configure({ | ||
level: level || process.env.LOG_LEVEL || 'verbose', | ||
transports: this.transports | ||
}); | ||
} | ||
/** | ||
* Save configuration and update log level | ||
* @param {String} level log level | ||
*/ | ||
saveConfiguration(level) { | ||
this.configure({ | ||
level: level || process.env.LOG_LEVEL || 'verbose', | ||
transports: this.transports, | ||
}); | ||
} | ||
} | ||
module.exports = Logger; |
@@ -15,20 +15,20 @@ /*! | ||
const utils = module.exports = {}; | ||
const utils = {}; | ||
utils.expandGlobPatterns = (globPatterns) => { | ||
if (!isInTranspiler) { | ||
return globPatterns; | ||
} | ||
if (!isInTranspiler) { | ||
return globPatterns; | ||
} | ||
return globPatterns.reduce((acc, globPattern) => { | ||
acc.push(globPattern); | ||
return globPatterns.reduce((acc, globPattern) => { | ||
acc.push(globPattern); | ||
if (globPattern.includes('.js')) { | ||
const tsPattern = globPattern.replace('.js', '.ts'); | ||
acc.push(tsPattern); | ||
} | ||
if (globPattern.includes('.js')) { | ||
const tsPattern = globPattern.replace('.js', '.ts'); | ||
acc.push(tsPattern); | ||
} | ||
return acc; | ||
}, []) | ||
} | ||
return acc; | ||
}, []); | ||
}; | ||
@@ -38,9 +38,12 @@ /** | ||
* @param {string|array} globPatterns glob pattern | ||
* @return {array} files array of files matching glob | ||
* @return {array} files array of files matching glob | ||
*/ | ||
utils.getGlobbedFiles = function getGlobbedFiles(globPatterns) { | ||
const patterns = _.isArray(globPatterns) ? globPatterns : [globPatterns]; | ||
const expandedPatterns = utils.expandGlobPatterns(patterns); | ||
const patterns = _.isArray(globPatterns) ? globPatterns : [globPatterns]; | ||
const expandedPatterns = utils.expandGlobPatterns(patterns); | ||
return expandedPatterns.reduce((acc, globPattern) => [...acc, ...glob(globPattern, {sync: true})], []) | ||
return expandedPatterns.reduce( | ||
(acc, globPattern) => [...acc, ...glob(globPattern, { sync: true })], | ||
[], | ||
); | ||
}; | ||
@@ -59,32 +62,32 @@ | ||
utils.getValue = function getValue(keyString, obj) { | ||
let nodes = keyString.split('.'); | ||
let key = obj; | ||
const nodes = keyString.split('.'); | ||
let key = obj; | ||
do { | ||
let node = nodes.shift(); // whos the lucky node? | ||
let hasNode = key && key.hasOwnProperty(node); | ||
do { | ||
const node = nodes.shift(); // whos the lucky node? | ||
const hasNode = key && key[node]; | ||
if (hasNode) { | ||
key = key[node]; // traval | ||
continue; | ||
} | ||
if (hasNode) { | ||
return undefined; | ||
} | ||
nodes = []; // derefference nodes | ||
key = undefined; // nothing found :( | ||
} while (nodes.length > 0); | ||
key = key[node]; // traval | ||
} while (nodes.length > 0); | ||
return key; | ||
return key; | ||
}; | ||
utils.safePromisifyCallFn = (fn, context, [req, resp, done, ...args]) => { | ||
return new Promise((resolve, reject) => { | ||
const done = (err, data) => err ? reject(err) : resolve(data); | ||
const maybePromise = fn.apply(context, [req, resp, done, ...args]) | ||
utils.safePromisifyCallFn = function safePromisifyCallFn(fn, context, [req, resp, , ...args]) { | ||
return new Promise((resolve, reject) => { | ||
const next = (err, data) => (err ? reject(err) : resolve(data)); | ||
const maybePromise = fn.apply(context, [req, resp, next, ...args]); | ||
if (maybePromise && maybePromise.then) { | ||
maybePromise | ||
.then(data => resolve(data)) | ||
.catch(err => reject(err)); | ||
} | ||
}) | ||
} | ||
if (maybePromise && maybePromise.then) { | ||
maybePromise | ||
.then(data => resolve(data)) | ||
.catch(err => reject(err)); | ||
} | ||
}); | ||
}; | ||
module.exports = utils; |
{ | ||
"name": "clout-js", | ||
"version": "2.1.0-beta.2", | ||
"version": "2.1.0-beta.3", | ||
"description": "Clean, simplistic, enterprise grade full-stack NodeJS framework", | ||
@@ -9,2 +9,4 @@ "main": "index.js", | ||
"grunt": "grunt", | ||
"lint": "eslint .", | ||
"pretest": "npm run lint", | ||
"test": "mocha test/*_test.js", | ||
@@ -32,2 +34,7 @@ "test:watch": "mocha test/*_test.js --watch", | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-push": "npm audit; npm run test" | ||
} | ||
}, | ||
"author": "Muhammad Dadu", | ||
@@ -42,3 +49,3 @@ "license": "MIT", | ||
"debug": "^3.1.0", | ||
"ejs": "^2.5.7", | ||
"ejs": "^3.0.1", | ||
"express": "^4.16.2", | ||
@@ -48,5 +55,4 @@ "express-session": "^1.15.6", | ||
"glob": "^7.1.2", | ||
"grunt": "^1.0.2", | ||
"hbs": "^4.0.1", | ||
"lodash": "^4.17.5", | ||
"hbs": "^4.1.0", | ||
"lodash": "^4.17.15", | ||
"prompt": "^1.0.0", | ||
@@ -58,11 +64,16 @@ "q": "^1.5.1", | ||
"devDependencies": { | ||
"@types/express": "^4.16.1", | ||
"eslint": "^4.17.0", | ||
"grunt": "^1.0.2", | ||
"grunt-jsdoc": "^2.2.1", | ||
"@types/express": "^4.17.2", | ||
"eslint": "^4.19.1", | ||
"eslint-config-airbnb": "^17.1.0", | ||
"eslint-plugin-import": "^2.16.0", | ||
"eslint-plugin-jsx-a11y": "^6.2.1", | ||
"eslint-plugin-react": "^7.12.4", | ||
"grunt": "^1.0.4", | ||
"grunt-jsdoc": "^2.4.1", | ||
"grunt-mocha-test": "^0.13.3", | ||
"jsdoc": "^3.5.5", | ||
"husky": "^1.3.1", | ||
"jsdoc": "^3.6.3", | ||
"minami": "^1.2.3", | ||
"mocha": "^5.0.0", | ||
"request": "^2.83.0", | ||
"request": "^2.88.0", | ||
"should": "^13.2.1", | ||
@@ -69,0 +80,0 @@ "sinon": "^4.2.2" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
2718724
17
151
38
16
6016
+ Addedbrace-expansion@2.0.1(transitive)
+ Addedejs@3.1.10(transitive)
+ Addedfilelist@1.0.4(transitive)
+ Addedjake@10.9.1(transitive)
+ Addedminimatch@5.1.6(transitive)
- Removedgrunt@^1.0.2
- Removedabbrev@1.1.1(transitive)
- Removedargparse@1.0.10(transitive)
- Removedarray-each@1.0.1(transitive)
- Removedarray-slice@1.1.0(transitive)
- Removedbraces@3.0.2(transitive)
- Removedcolors@1.1.2(transitive)
- Removeddateformat@4.6.3(transitive)
- Removeddetect-file@1.0.0(transitive)
- Removedejs@2.7.4(transitive)
- Removedesprima@4.0.1(transitive)
- Removedeventemitter2@0.4.14(transitive)
- Removedexit@0.1.2(transitive)
- Removedexpand-tilde@2.0.2(transitive)
- Removedextend@3.0.2(transitive)
- Removedfill-range@7.0.1(transitive)
- Removedfindup-sync@4.0.05.0.0(transitive)
- Removedfined@1.2.0(transitive)
- Removedflagged-respawn@1.0.1(transitive)
- Removedfor-in@1.0.2(transitive)
- Removedfor-own@1.0.0(transitive)
- Removedgetobject@1.0.2(transitive)
- Removedglob@7.1.7(transitive)
- Removedglobal-modules@1.0.0(transitive)
- Removedglobal-prefix@1.0.2(transitive)
- Removedgrunt@1.6.1(transitive)
- Removedgrunt-cli@1.4.3(transitive)
- Removedgrunt-known-options@2.0.0(transitive)
- Removedgrunt-legacy-log@3.0.0(transitive)
- Removedgrunt-legacy-log-utils@2.1.0(transitive)
- Removedgrunt-legacy-util@2.0.1(transitive)
- Removedhomedir-polyfill@1.0.3(transitive)
- Removedhooker@0.2.3(transitive)
- Removediconv-lite@0.6.3(transitive)
- Removedini@1.3.8(transitive)
- Removedinterpret@1.1.0(transitive)
- Removedis-absolute@1.0.0(transitive)
- Removedis-core-module@2.13.1(transitive)
- Removedis-extglob@2.1.1(transitive)
- Removedis-glob@4.0.3(transitive)
- Removedis-number@7.0.0(transitive)
- Removedis-plain-object@2.0.4(transitive)
- Removedis-relative@1.0.0(transitive)
- Removedis-unc-path@1.0.0(transitive)
- Removedis-windows@1.0.2(transitive)
- Removedisexe@2.0.0(transitive)
- Removedisobject@3.0.1(transitive)
- Removedjs-yaml@3.14.1(transitive)
- Removedkind-of@6.0.3(transitive)
- Removedliftup@3.0.1(transitive)
- Removedmake-iterator@1.0.1(transitive)
- Removedmap-cache@0.2.2(transitive)
- Removedmicromatch@4.0.5(transitive)
- Removedminimatch@3.0.8(transitive)
- Removednopt@3.0.64.0.3(transitive)
- Removedobject.defaults@1.1.0(transitive)
- Removedobject.map@1.0.1(transitive)
- Removedobject.pick@1.3.0(transitive)
- Removedos-homedir@1.0.2(transitive)
- Removedos-tmpdir@1.0.2(transitive)
- Removedosenv@0.1.5(transitive)
- Removedparse-filepath@1.0.2(transitive)
- Removedparse-passwd@1.0.0(transitive)
- Removedpath-parse@1.0.7(transitive)
- Removedpath-root@0.1.1(transitive)
- Removedpath-root-regex@0.1.2(transitive)
- Removedpicomatch@2.3.1(transitive)
- Removedrechoir@0.7.1(transitive)
- Removedresolve@1.22.8(transitive)
- Removedresolve-dir@1.0.1(transitive)
- Removedsprintf-js@1.0.31.1.3(transitive)
- Removedsupports-preserve-symlinks-flag@1.0.0(transitive)
- Removedto-regex-range@5.0.1(transitive)
- Removedunc-path-regex@0.1.2(transitive)
- Removedunderscore.string@3.3.6(transitive)
- Removedutil-deprecate@1.0.2(transitive)
- Removedv8flags@3.2.0(transitive)
- Removedwhich@1.3.12.0.2(transitive)
Updatedejs@^3.0.1
Updatedhbs@^4.1.0
Updatedlodash@^4.17.15