UNPKG

mobile-cli-lib

Version:
242 lines (241 loc) 11.3 kB
"use strict"; var path = require("path"); var util = require("util"); var helpers_1 = require("../helpers"); var Future = require("fibers/future"); var Hook = (function () { function Hook(name, fullPath) { this.name = name; this.fullPath = fullPath; } return Hook; }()); var HooksService = (function () { function HooksService($childProcess, $fs, $logger, $errors, $config, $staticConfig, $injector, $projectHelper, $options) { this.$childProcess = $childProcess; this.$fs = $fs; this.$logger = $logger; this.$errors = $errors; this.$config = $config; this.$staticConfig = $staticConfig; this.$injector = $injector; this.$projectHelper = $projectHelper; this.$options = $options; } Object.defineProperty(HooksService.prototype, "hookArgsName", { get: function () { return "hookArgs"; }, enumerable: true, configurable: true }); HooksService.prototype.initialize = function () { this.cachedHooks = {}; var relativeToLibPath = path.join(__dirname, "../../"); this.hooksDirectories = [ path.join(relativeToLibPath, HooksService.HOOKS_DIRECTORY_NAME), path.join(relativeToLibPath, "common", HooksService.HOOKS_DIRECTORY_NAME) ]; if (this.$projectHelper.projectDir) { this.hooksDirectories.push(path.join(this.$projectHelper.projectDir, HooksService.HOOKS_DIRECTORY_NAME)); } this.$logger.trace("Hooks directories: " + util.inspect(this.hooksDirectories)); }; HooksService.formatHookName = function (commandName) { return commandName.replace(/\|[\s\S]*$/, ""); }; HooksService.prototype.executeBeforeHooks = function (commandName, hookArguments) { var beforeHookName = "before-" + HooksService.formatHookName(commandName); var traceMessage = "BeforeHookName for command " + commandName + " is " + beforeHookName; return this.executeHooks(beforeHookName, traceMessage, hookArguments); }; HooksService.prototype.executeAfterHooks = function (commandName, hookArguments) { var afterHookName = "after-" + HooksService.formatHookName(commandName); var traceMessage = "AfterHookName for command " + commandName + " is " + afterHookName; return this.executeHooks(afterHookName, traceMessage, hookArguments); }; HooksService.prototype.executeHooks = function (hookName, traceMessage, hookArguments) { var _this = this; return (function () { if (_this.$config.DISABLE_HOOKS || !_this.$options.hooks) { return; } if (!_this.hooksDirectories) { _this.initialize(); } _this.$logger.trace(traceMessage); try { _.each(_this.hooksDirectories, function (hooksDirectory) { _this.executeHooksInDirectory(hooksDirectory, hookName, hookArguments).wait(); }); } catch (err) { _this.$logger.trace("Failed during hook execution."); _this.$errors.failWithoutHelp(err.message); } }).future()(); }; HooksService.prototype.executeHooksInDirectory = function (directoryPath, hookName, hookArguments) { var _this = this; return (function () { var hooks = _this.getHooksByName(directoryPath, hookName).wait(); hooks.forEach(function (hook) { _this.$logger.info("Executing %s hook from %s", hookName, hook.fullPath); var command = _this.getSheBangInterpreter(hook).wait(); var inProc = false; if (!command) { command = hook.fullPath; if (path.extname(hook.fullPath).toLowerCase() === ".js") { command = process.argv[0]; inProc = _this.shouldExecuteInProcess(_this.$fs.readText(hook.fullPath).wait()); } } if (inProc) { _this.$logger.trace("Executing %s hook at location %s in-process", hookName, hook.fullPath); var hookEntryPoint = require(hook.fullPath); _this.$logger.trace("Validating " + hookName + " arguments."); var invalidArguments = _this.validateHookArguments(hookEntryPoint); if (invalidArguments.length) { _this.$logger.warn(hookName + " will NOT be executed because it has invalid arguments - " + invalidArguments.join(", ").grey + "."); return; } var maybePromise = _this.$injector.resolve(hookEntryPoint, hookArguments); if (maybePromise) { _this.$logger.trace('Hook promises to signal completion'); var hookCompletion_1 = new Future(); maybePromise.then(function () { return hookCompletion_1.return(); }, function (err) { if (_.isBoolean(err.stopExecution) && err.errorAsWarning === true) { _this.$logger.warn(err.message); hookCompletion_1.return(); } else { hookCompletion_1.throw(err); } }); hookCompletion_1.wait(); } _this.$logger.trace('Hook completed'); } else { var environment = _this.prepareEnvironment(hook.fullPath); _this.$logger.trace("Executing %s hook at location %s with environment ", hookName, hook.fullPath, environment); var output = _this.$childProcess.spawnFromEvent(command, [hook.fullPath], "close", environment, { throwError: false }).wait(); if (output.exitCode !== 0) { throw new Error(output.stdout + output.stderr); } } }); }).future()(); }; HooksService.prototype.getHooksByName = function (directoryPath, hookName) { var _this = this; return (function () { var allBaseHooks = _this.getHooksInDirectory(directoryPath).wait(); var baseHooks = _.filter(allBaseHooks, function (hook) { return hook.name.toLowerCase() === hookName.toLowerCase(); }); var moreHooks = _this.getHooksInDirectory(path.join(directoryPath, hookName)).wait(); return baseHooks.concat(moreHooks); }).future()(); }; HooksService.prototype.getHooksInDirectory = function (directoryPath) { var _this = this; return (function () { if (!_this.cachedHooks[directoryPath]) { var hooks = []; if (directoryPath && _this.$fs.exists(directoryPath).wait() && _this.$fs.getFsStats(directoryPath).wait().isDirectory()) { var directoryContent = _this.$fs.readDirectory(directoryPath).wait(); var files = _.filter(directoryContent, function (entry) { var fullPath = path.join(directoryPath, entry); var isFile = _this.$fs.getFsStats(fullPath).wait().isFile(); return isFile; }); hooks = _.map(files, function (file) { var fullPath = path.join(directoryPath, file); return new Hook(_this.getBaseFilename(file), fullPath); }); } _this.cachedHooks[directoryPath] = hooks; } return _this.cachedHooks[directoryPath]; }).future()(); }; HooksService.prototype.prepareEnvironment = function (hookFullPath) { var clientName = this.$staticConfig.CLIENT_NAME.toUpperCase(); var environment = {}; environment[util.format("%s-COMMANDLINE", clientName)] = process.argv.join(' '); environment[util.format("%s-HOOK_FULL_PATH", clientName)] = hookFullPath; environment[util.format("%s-VERSION", clientName)] = this.$staticConfig.version; return { cwd: this.$projectHelper.projectDir, stdio: 'inherit', env: _.extend({}, process.env, environment) }; }; HooksService.prototype.getSheBangInterpreter = function (hook) { var _this = this; return (function () { var interpreter = null; var shMatch = []; var fileContent = _this.$fs.readText(hook.fullPath).wait(); if (fileContent) { var sheBangMatch = fileContent.split('\n')[0].match(/^#!(?:\/usr\/bin\/env )?([^\r\n]+)/m); if (sheBangMatch) { interpreter = sheBangMatch[1]; } if (interpreter) { shMatch = interpreter.match(/bin\/((?:ba)?sh)$/); } if (shMatch) { interpreter = shMatch[1]; } } return interpreter; }).future()(); }; HooksService.prototype.getBaseFilename = function (fileName) { return fileName.substr(0, fileName.length - path.extname(fileName).length); }; HooksService.prototype.shouldExecuteInProcess = function (scriptSource) { try { var esprima = require('esprima'); var ast = esprima.parse(scriptSource); var inproc_1 = false; ast.body.forEach(function (statement) { if (statement.type !== 'ExpressionStatement' || statement.expression.type !== 'AssignmentExpression') { return; } var left = statement.expression.left; if (left.type === 'MemberExpression' && left.object && left.object.type === 'Identifier' && left.object.name === 'module' && left.property && left.property.type === 'Identifier' && left.property.name === 'exports') { inproc_1 = true; } }); return inproc_1; } catch (err) { return false; } }; HooksService.prototype.validateHookArguments = function (hookConstructor) { var _this = this; var invalidArguments = []; helpers_1.annotate(hookConstructor); _.each(hookConstructor.$inject.args, function (argument) { try { if (argument !== _this.hookArgsName) { _this.$injector.resolve(argument); } } catch (err) { _this.$logger.trace("Cannot resolve " + argument + ", reason: " + err); invalidArguments.push(argument); } }); return invalidArguments; }; HooksService.HOOKS_DIRECTORY_NAME = "hooks"; return HooksService; }()); exports.HooksService = HooksService; $injector.register("hooksService", HooksService);