var mout = require('mout'); var cmd = require('../util/cmd'); var Q = require('q'); var shellquote = require('shell-quote'); var orderByDependencies = function (packages, installed, json) { var ordered = []; installed = mout.object.keys(installed); var depsSatisfied = function (packageName) { return mout.array.difference(mout.object.keys(packages[packageName].dependencies), installed, ordered).length === 0; }; var depsFromBowerJson = json && json.dependencies ? mout.object.keys(json.dependencies) : []; var packageNames = mout.object.keys(packages); //get the list of the packages that are specified in bower.json in that order //its nice to maintain that order for users var desiredOrder = mout.array.intersection(depsFromBowerJson, packageNames); //then add to the end any remaining packages that werent in bower.json desiredOrder = desiredOrder.concat(mout.array.difference(packageNames, desiredOrder)); //the desired order isn't necessarily a correct dependency specific order //so we ensure that below var resolvedOne = true; while (resolvedOne) { resolvedOne = false; for (var i = 0; i < desiredOrder.length; i++) { var packageName = desiredOrder[i]; if (depsSatisfied(packageName)) { ordered.push(packageName); mout.array.remove(desiredOrder, packageName); //as soon as we resolve a package start the loop again resolvedOne = true; break; } } if (!resolvedOne && desiredOrder.length > 0) { //if we're here then some package(s) doesn't have all its deps satisified //so lets just jam those names on the end ordered = ordered.concat(desiredOrder); } } return ordered; }; var run = function (cmdString, action, logger, config) { logger.action(action, cmdString); //pass env + BOWER_PID so callees can identify a preinstall+postinstall from the same bower instance var env = mout.object.mixIn({ 'BOWER_PID': process.pid }, process.env); var args = shellquote.parse(cmdString, env); var cmdName = args[0]; mout.array.remove(args, cmdName); //no rest() in mout var options = { cwd: config.cwd, env: env }; var promise = cmd(cmdName, args, options); promise.progress(function (progress) { progress.split('\n').forEach(function (line) { if (line) { logger.action(action, line); } }); }); return promise; }; var hook = function (action, ordered, config, logger, packages, installed, json) { if (mout.object.keys(packages).length === 0 || !config.scripts || !config.scripts[action]) { /*jshint newcap: false */ return Q(); } var orderedPackages = ordered ? orderByDependencies(packages, installed, json) : mout.object.keys(packages); var cmdString = mout.string.replace(config.scripts[action], '%', orderedPackages.join(' ')); return run(cmdString, action, logger, config); }; module.exports = { preuninstall: mout.function.partial(hook, 'preuninstall', false), preinstall: mout.function.partial(hook, 'preinstall', true), postinstall: mout.function.partial(hook, 'postinstall', true), //only exposed for test _orderByDependencies: orderByDependencies };