2019-05-07 13:12:54 +00:00
|
|
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
2016-04-27 22:55:57 +00:00
|
|
|
// Node module: loopback-boot
|
|
|
|
// This file is licensed under the MIT License.
|
|
|
|
// License text available at https://opensource.org/licenses/MIT
|
|
|
|
|
2017-05-08 21:48:48 +00:00
|
|
|
'use strict';
|
|
|
|
|
2017-03-08 09:29:59 +00:00
|
|
|
var _ = require('lodash');
|
2016-04-27 22:55:57 +00:00
|
|
|
var assert = require('assert');
|
|
|
|
var async = require('async');
|
|
|
|
var utils = require('./utils');
|
|
|
|
var path = require('path');
|
|
|
|
var pluginLoader = require('./plugin-loader');
|
|
|
|
var debug = require('debug')('loopback:boot:bootstrapper');
|
|
|
|
var Promise = require('bluebird');
|
2017-03-08 09:29:59 +00:00
|
|
|
var arrayToObject = require('./utils').arrayToObject;
|
2016-04-27 22:55:57 +00:00
|
|
|
|
|
|
|
module.exports = Bootstrapper;
|
|
|
|
|
|
|
|
function createPromiseCallback() {
|
|
|
|
var cb;
|
|
|
|
var promise = new Promise(function(resolve, reject) {
|
|
|
|
cb = function(err, data) {
|
|
|
|
if (err) return reject(err);
|
|
|
|
return resolve(data);
|
|
|
|
};
|
|
|
|
});
|
|
|
|
cb.promise = promise;
|
|
|
|
return cb;
|
|
|
|
}
|
|
|
|
|
|
|
|
var builtinPlugins = [
|
|
|
|
'application', 'datasource', 'model', 'mixin',
|
|
|
|
'middleware', 'component', 'boot-script', 'swagger',
|
|
|
|
];
|
|
|
|
|
|
|
|
var builtinPhases = [
|
|
|
|
'load', 'compile', 'starting', 'start', 'started',
|
|
|
|
];
|
|
|
|
|
|
|
|
function loadAndRegisterPlugins(bootstrapper, options) {
|
|
|
|
var loader = pluginLoader(options);
|
|
|
|
var loaderContext = {};
|
|
|
|
loader.load(loaderContext);
|
|
|
|
loader.compile(loaderContext);
|
|
|
|
|
|
|
|
for (var i in loaderContext.instructions.pluginScripts) {
|
|
|
|
bootstrapper.use('/boot/' + i, loaderContext.instructions.pluginScripts[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new Bootstrapper with options
|
|
|
|
* @param options
|
|
|
|
* @constructor
|
|
|
|
*/
|
|
|
|
function Bootstrapper(options) {
|
|
|
|
this.plugins = [];
|
|
|
|
options = options || {};
|
|
|
|
|
|
|
|
if (typeof options === 'string') {
|
2017-05-08 21:48:48 +00:00
|
|
|
options = {appRootDir: options};
|
2016-04-27 22:55:57 +00:00
|
|
|
}
|
|
|
|
|
2017-03-08 09:29:59 +00:00
|
|
|
// For setting properties without modifying the original object
|
|
|
|
options = Object.create(options);
|
|
|
|
|
2016-04-27 22:55:57 +00:00
|
|
|
var appRootDir = options.appRootDir = options.appRootDir || process.cwd();
|
|
|
|
var env = options.env || process.env.NODE_ENV || 'development';
|
2017-03-08 09:29:59 +00:00
|
|
|
var scriptExtensions = options.scriptExtensions ?
|
|
|
|
arrayToObject(options.scriptExtensions) :
|
|
|
|
require.extensions;
|
2016-04-27 22:55:57 +00:00
|
|
|
|
|
|
|
var appConfigRootDir = options.appConfigRootDir || appRootDir;
|
|
|
|
|
|
|
|
options.rootDir = appConfigRootDir;
|
|
|
|
options.env = env;
|
2017-03-08 09:29:59 +00:00
|
|
|
options.scriptExtensions = scriptExtensions;
|
2016-04-27 22:55:57 +00:00
|
|
|
this.options = options;
|
|
|
|
|
|
|
|
this.phases = options.phases || builtinPhases;
|
|
|
|
this.builtinPlugins = options.plugins || builtinPlugins;
|
|
|
|
assert(Array.isArray(this.phases), 'Invalid phases: ' + this.phases);
|
|
|
|
assert(Array.isArray(this.plugins), 'Invalid plugins: ' +
|
|
|
|
this.builtinPlugins);
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
self.builtinPlugins.forEach(function(p) {
|
|
|
|
var factory = require('./plugins/' + p);
|
|
|
|
self.use('/boot/' + p, factory(options));
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
loadAndRegisterPlugins(self, options);
|
|
|
|
} catch (err) {
|
|
|
|
debug('Cannot load & register plugins: %s', err.stack || err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a handler to a given path
|
|
|
|
* @param {String} path
|
|
|
|
* @param {Function} handler
|
|
|
|
*/
|
|
|
|
Bootstrapper.prototype.use = function(path, handler) {
|
|
|
|
var plugin = {
|
|
|
|
path: path,
|
|
|
|
handler: handler,
|
|
|
|
};
|
|
|
|
this.plugins.push(plugin);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a list of plugins for the given path
|
|
|
|
* @param {String} path
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
Bootstrapper.prototype.getPlugins = function(path) {
|
|
|
|
if (path[path.length - 1] !== '/') {
|
|
|
|
path = path + '/';
|
|
|
|
}
|
|
|
|
return this.plugins.filter(function(p) {
|
|
|
|
return p.path.indexOf(path) === 0;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a list of extensions for the given path
|
|
|
|
* @param {String} path
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
Bootstrapper.prototype.getExtensions = function(path) {
|
|
|
|
if (path[path.length - 1] !== '/') {
|
|
|
|
path = path + '/';
|
|
|
|
}
|
|
|
|
return this.plugins.filter(function(p) {
|
|
|
|
if (p.path.indexOf(path) === -1) return false;
|
|
|
|
var name = p.path.substring(path.length);
|
|
|
|
return name && name.indexOf('/') === -1;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add more phases. The order of phases is decided by the sequence of phase
|
|
|
|
* names
|
|
|
|
* @param {String[]} phases An array of phase names
|
|
|
|
* @returns {String[]} New list of phases
|
|
|
|
*/
|
|
|
|
Bootstrapper.prototype.addPhases = function(phases) {
|
|
|
|
this.phases = utils.mergePhaseNameLists(this.phases, phases || []);
|
|
|
|
return this.phases;
|
|
|
|
};
|
|
|
|
|
|
|
|
function pluginIteratorFactory(context, phase) {
|
|
|
|
return function executePluginPhase(plugin, done) {
|
|
|
|
var result;
|
|
|
|
if (typeof plugin.handler[phase] !== 'function') {
|
|
|
|
debug('Skipping %s.%s', plugin.handler.name, phase);
|
|
|
|
return done();
|
|
|
|
}
|
|
|
|
debug('Invoking %s.%s', plugin.handler.name, phase);
|
|
|
|
try {
|
|
|
|
if (plugin.handler[phase].length === 2) {
|
|
|
|
plugin.handler[phase](context, done);
|
|
|
|
} else {
|
|
|
|
result = plugin.handler[phase](context);
|
|
|
|
Promise.resolve(result)
|
|
|
|
.then(function onPluginPhaseResolved(value) {
|
|
|
|
done(null, value);
|
|
|
|
}, function onPluginPhaseRejected(err) {
|
|
|
|
debug('Unable to invoke %s.%s()', plugin.name, phase, err);
|
|
|
|
done(err);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
debug('Unable to invoke %s.%s()', plugin.name, phase, err);
|
|
|
|
done(err);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Invoke the plugins phase by phase with the given context
|
|
|
|
* @param {Object} context Context object
|
|
|
|
* @param {Function} done Callback function. If not provided, a promise will be
|
|
|
|
* returned
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
Bootstrapper.prototype.run = function(context, done) {
|
|
|
|
if (!done) {
|
|
|
|
done = createPromiseCallback();
|
|
|
|
}
|
|
|
|
var options = this.options;
|
|
|
|
var appRootDir = options.appRootDir = options.appRootDir || process.cwd();
|
|
|
|
var env = options.env || process.env.NODE_ENV || 'development';
|
|
|
|
|
|
|
|
var appConfigRootDir = options.appConfigRootDir || appRootDir;
|
|
|
|
|
|
|
|
options.rootDir = appConfigRootDir;
|
|
|
|
options.env = env;
|
|
|
|
|
|
|
|
context = context || {};
|
|
|
|
|
|
|
|
var phases = context.phases || this.phases;
|
2019-03-22 12:54:38 +00:00
|
|
|
|
|
|
|
if (phases.includes('starting') || phases.includes('start')) {
|
|
|
|
context.app.booting = true;
|
|
|
|
}
|
|
|
|
|
2016-04-27 22:55:57 +00:00
|
|
|
var bootPlugins = this.getExtensions('/boot');
|
|
|
|
async.eachSeries(phases, function(phase, done) {
|
|
|
|
debug('Phase %s', phase);
|
|
|
|
async.eachSeries(bootPlugins, pluginIteratorFactory(context, phase), done);
|
|
|
|
}, function(err) {
|
2019-03-22 12:54:38 +00:00
|
|
|
if (phases.includes('started')) {
|
|
|
|
context.app.booting = false;
|
|
|
|
}
|
|
|
|
|
2016-04-27 22:55:57 +00:00
|
|
|
return done(err, context);
|
|
|
|
});
|
|
|
|
return done.promise;
|
|
|
|
};
|