UNPKG

react-deploy

Version:

a deployment script for create react app to s3

185 lines (154 loc) 5.46 kB
'use strict'; var RSVP = require('rsvp'); var _ = require('lodash'); var chalk = require('chalk'); /* This is a generic implementation of a pipeline with ordered, promise-aware hooks, * pleasant logging, and failure handling. It should not have any "deployment" domain * logic or semantics, and is a candidate for extraction to its own npm module. */ function Pipeline(hookNames, options) { hookNames = hookNames || []; options = options || {}; options = _.merge({ ui: { logInfoColor: 'blue', logErrorColor: 'red' } }, options); this._ui = options.ui; this._pipelineHooks = hookNames.reduce(function(pipelineHooks, hookName) { pipelineHooks[hookName] = []; return pipelineHooks; }, { didFail: [] }); this.logInfo = chalk[this._ui.logInfoColor]; this.logError = chalk[this._ui.logErrorColor]; this._progressBarLib = options.progressBarLib || require('react-deploy-progress'); } Pipeline.prototype.register = function(hookName, fn) { var ui = this._ui; var pipelineHooks = this._pipelineHooks; if (typeof fn === 'function') { fn = { name: 'anonymous function', fn: fn }; } if (pipelineHooks[hookName]) { if (ui.verbose) { ui.write(this.logInfo('Registering hook -> ' + hookName + '[' + fn.name + ']\n')); } pipelineHooks[hookName].push(fn); } }; Pipeline.prototype.hasHandlersForHook = function(hook) { return this._pipelineHooks[hook].length !== 0; }; Pipeline.prototype.execute = function(context) { context = context || { }; var ui = this._ui; var hooks = this._hooksWithoutDidFail(this.hookNames()); var ProgressBar = this._progressBarLib; if (ui.verbose) { ui.write(this.logInfo('Executing pipeline\n')); } else if (ui.showProgress) { ui.progressBar = new ProgressBar('Deploying [:bar] :percent [plugin: :plugin -> :hook]', { total: this._hooksCount(this._hooksWithoutConfigure(hooks)), cursor: process.platform === 'darwin' ? '🚀 ' : '>' }); } return hooks.reduce(this._addHookExecutionPromiseToPipelinePromiseChain.bind(this, ui), RSVP.resolve(context)) .then(this._notifyPipelineCompletion.bind(this, ui)) .catch(this._handlePipelineFailure.bind(this, ui, context)) .catch(this._abortPipelineExecution.bind(this, ui)); }; Pipeline.prototype.hookNames = function() { return Object.keys(this._pipelineHooks); }; Pipeline.prototype._addHookExecutionPromiseToPipelinePromiseChain = function(ui, promise, hookName) { var self = this; return promise.then(this._notifyPipelineHookExecution.bind(this, ui, hookName)) .then(function(context){ try { return self._executeHook(hookName, context); } catch(error) { return RSVP.reject(error); } }); }; Pipeline.prototype._hooksWithoutDidFail = function(hooks) { return hooks.filter(function(hook) { return hook !== 'didFail'; }); }; Pipeline.prototype._hooksWithoutConfigure = function(hooks) { return hooks.filter(function(hook) { return hook !== 'configure'; }); }; Pipeline.prototype._hooksCount = function(hooks) { return hooks.reduce(function(sum, hookName) { var hookFunctions = this._pipelineHooks[hookName]; return sum + hookFunctions.length; }.bind(this), 0); }; Pipeline.prototype._handlePipelineFailure = function(ui, context, error) { if (ui.verbose) { ui.write(this.logError('|\n')); ui.write(this.logError('+- didFail\n')); } ui.write(this.logError(error + '\n' + (error ? error.stack : null))); return this._executeHook('didFail', context) .then(RSVP.reject.bind(this, error)); }; Pipeline.prototype._abortPipelineExecution = function(ui/*, error */) { if (ui.verbose) { ui.write(this.logInfo('|\n')); } ui.write(this.logError('Pipeline aborted\n')); return RSVP.reject(); }; Pipeline.prototype._notifyPipelineCompletion = function(ui) { if (ui.verbose) { ui.write(this.logInfo('|\n')); ui.write(this.logInfo('Pipeline complete\n')); } }; Pipeline.prototype._notifyPipelineHookExecution = function(ui, hookName, context) { if (ui.verbose) { ui.write(this.logInfo('|\n')); ui.write(this.logInfo('+- ' + hookName + '\n')); } return context; }; Pipeline.prototype._executeHook = function(hookName, context) { var ui = this._ui; var hookFunctions = this._pipelineHooks[hookName]; return hookFunctions.reduce(this._addPluginHookExecutionPromiseToHookPromiseChain.bind(this, ui, context, hookName), RSVP.resolve(context)); }; Pipeline.prototype._addPluginHookExecutionPromiseToHookPromiseChain = function(ui, context, hookName, promise, fnObject) { return promise .then(this._notifyPipelinePluginHookExecution.bind(this, ui, fnObject, hookName)) .then(this._mergePluginHookResultIntoContext.bind(this, context)); }; Pipeline.prototype._notifyPipelinePluginHookExecution = function(ui, fnObject, hookName, context) { if (ui.verbose) { ui.write(this.logInfo('| |\n')); ui.write(this.logInfo('| +- ' + fnObject.name + '\n')); } else if (ui.showProgress) { if (hookName !== 'configure') { ui.progressBar.tick({ hook: hookName, plugin: fnObject.name }); } } return fnObject.fn(context); }; Pipeline.prototype._mergePluginHookResultIntoContext = function(context,result) { return _.mergeWith(context, result, function(a, b) { if (_.isArray(a)) { return a.concat(b); } }); }; module.exports = Pipeline;