serverless/lib/classes/PluginManager.js
2016-10-10 10:34:59 +02:00

200 lines
5.6 KiB
JavaScript

'use strict';
const path = require('path');
const BbPromise = require('bluebird');
const _ = require('lodash');
class PluginManager {
constructor(serverless) {
this.serverless = serverless;
this.provider = null;
this.cliOptions = {};
this.cliCommands = [];
this.plugins = [];
this.commands = {};
this.hooks = {};
}
setProvider(provider) {
this.provider = provider;
}
setCliOptions(options) {
this.cliOptions = options;
}
setCliCommands(commands) {
this.cliCommands = commands;
}
addPlugin(Plugin) {
const pluginInstance = new Plugin(this.serverless, this.cliOptions);
// ignore plugins that specify a different provider than the current one
if (pluginInstance.provider && (pluginInstance.provider !== this.provider)) {
return;
}
this.loadCommands(pluginInstance);
this.loadHooks(pluginInstance);
this.plugins.push(pluginInstance);
}
loadAllPlugins(servicePlugins) {
this.loadCorePlugins();
this.loadServicePlugins(servicePlugins);
}
loadPlugins(plugins) {
plugins.forEach((plugin) => {
const Plugin = require(plugin); // eslint-disable-line global-require
this.addPlugin(Plugin);
});
}
loadCorePlugins() {
const pluginsDirectoryPath = path.join(__dirname, '../plugins');
const corePlugins = this.serverless.utils
.readFileSync(path.join(pluginsDirectoryPath, 'Plugins.json')).plugins
.map((corePluginPath) => path.join(pluginsDirectoryPath, corePluginPath));
this.loadPlugins(corePlugins);
}
loadServicePlugins(servicePlugs) {
const servicePlugins = (typeof servicePlugs !== 'undefined' ? servicePlugs : []);
// we want to load plugins installed locally in the service
if (this.serverless && this.serverless.config && this.serverless.config.servicePath) {
module.paths.unshift(path.join(this.serverless.config.servicePath, 'node_modules'));
}
this.loadPlugins(servicePlugins);
// restore module paths
if (this.serverless && this.serverless.config && this.serverless.config.servicePath) {
module.paths.shift();
}
}
loadCommand(pluginName, details, key) {
const commands = _.mapValues(details.commands, (subDetails, subKey) =>
this.loadCommand(pluginName, subDetails, `${key}:${subKey}`)
);
return _.assign({}, details, { key, pluginName, commands });
}
loadCommands(pluginInstance) {
const pluginName = pluginInstance.constructor.name;
_.forEach(pluginInstance.commands, (details, key) => {
const command = this.loadCommand(pluginName, details, key);
this.commands[key] = _.merge({}, this.commands[key], command);
});
}
loadHooks(pluginInstance) {
_.forEach(pluginInstance.hooks, (hook, event) => {
this.hooks[event] = this.hooks[event] || [];
this.hooks[event].push(hook);
});
}
getCommands() {
return this.commands;
}
getCommand(commandsArray) {
return _.reduce(commandsArray, (current, name, index) => {
if (name in current.commands) {
return current.commands[name];
}
const commandName = commandsArray.slice(0, index + 1).join(' ');
const errorMessage = [
`Command "${commandName}" not found`,
' Run "serverless help" for a list of all available commands.',
].join();
throw new this.serverless.classes.Error(errorMessage);
}, { commands: this.commands });
}
getEvents(command) {
return _.flatMap(command.lifecycleEvents, (event) => [
`before:${command.key}:${event}`,
`${command.key}:${event}`,
`after:${command.key}:${event}`,
]);
}
getPlugins() {
return this.plugins;
}
run(commandsArray) {
const command = this.getCommand(commandsArray);
this.convertShortcutsIntoOptions(command);
this.validateOptions(command);
const events = this.getEvents(command);
const hooks = _.flatMap(events, (event) => this.hooks[event] || []);
if (hooks.length === 0) {
const errorMessage = 'The command you entered did not catch on any hooks';
throw new this.serverless.classes.Error(errorMessage);
}
return BbPromise.reduce(hooks, (__, hook) => hook(), null);
}
validateCommand(commandsArray) {
this.getCommand(commandsArray);
}
validateOptions(command) {
_.forEach(command.options, (value, key) => {
if (value.required && (this.cliOptions[key] === true || !(this.cliOptions[key]))) {
let requiredThings = `the --${key} option`;
if (value.shortcut) {
requiredThings += ` / -${value.shortcut} shortcut`;
}
let errorMessage = `This command requires ${requiredThings}.`;
if (value.usage) {
errorMessage = `${errorMessage} Usage: ${value.usage}`;
}
throw new this.serverless.classes.Error(errorMessage);
}
if (_.isPlainObject(value.customValidation) &&
value.customValidation.regularExpression instanceof RegExp &&
typeof value.customValidation.errorMessage === 'string' &&
!value.customValidation.regularExpression.test(this.cliOptions[key])) {
throw new this.serverless.classes.Error(value.customValidation.errorMessage);
}
});
}
convertShortcutsIntoOptions(command) {
_.forEach(command.options, (optionObject, optionKey) => {
if (optionObject.shortcut && _.includes(Object.keys(this.cliOptions),
optionObject.shortcut)) {
Object.keys(this.cliOptions).forEach((option) => {
if (option === optionObject.shortcut) {
this.cliOptions[optionKey] = this.cliOptions[option];
}
});
}
});
}
}
module.exports = PluginManager;