tandrewnichols/n-run

View on GitHub
lib/n-run.js

Summary

Maintainability
A
1 hr
Test Coverage
var async = require('async');
var semver = require('semver');
var isRange = require('is-semver-range');
var n = require('n-wrap');
var isIo = require('is-io-version');
var npm = require('npm');
var install = require('n-install-missing');
var installed = require('n-installed');
var _ = require('lodash');
_.mixin({ any: _.some });
var listify = require('listify');
var chalk = require('chalk');
var pluralize = require('pluralize');

var run = function(results, opts, fn) {
  // Get a string, loggable version of the command
  var cmd = results.command.join(' ');
  // Sort versions, so that they run roughly oldest to newest
  var versions = results.versions.sort();
  // Run the versions in series so that the logging doesn't get muddled together
  async.eachSeries(results.versions, function(version, next) {
    // Log useful stuff unless logging is suppressed
    if (!opts.quiet) {
      console.log();
      console.log('Executing', cmd, 'on version', chalk.green(version));
    }
    // Execute the command against the binary
    (isIo(version) ? n.io : n).use(version, results.command, next);
  }, function(err) {
    // Again, log if appropriate
    if (!opts.quiet) {
      if (err) {
        console.log();
        console.log(cmd, 'failed on one or more versions of node');
        console.log(err);
      } else {
        console.log();
        console.log(cmd, 'completed successfully in node versions', listify(versions));
      }
    }
    // Call back
    fn(err);
  });
};

var resolve = function(versions, results, opts, fn) {
  versions = _.map(versions, function(version) {
    if (opts.install) {
      var versionList = results.node.concat(results.io);
      if (opts.install === 'latest') {
        return isRange(version) ? semver.maxSatisfying(versionList, version) : version;
      } else {
        return isRange(version) ? semver.maxSatisfying(results.installed.all, version) || semver.maxSatisfying(versionList, version) : version;
      }
    } else {
      var v = isRange(version) ? semver.maxSatisfying(results.installed.all, version) : version;
      if (!v) {
        console.log('No version matching', version, 'is installed. Skipping . . .');
      }
      return v;
    }
  }).filter(Boolean);

  fn(null, versions);
};

var globalize = function(command, fn) {
  // Prefix the first part of the command with the global bin path
  command[0] = npm.config.prefix + '/bin/' + command[0];
  fn(null, command);
};

var noop = function(next) {
  next();
};

var filter = function(versions, results, opts, next) {
  versions = _.filter(versions, function(version) {
    if (!opts.install && results.installed.all.indexOf(version) === -1) {
      console.log('No version matching', version, 'is installed. Skipping . . .');
      return false;
    }
    return true;
  });
  next(null, versions);
};

var setup = function(command, versions, opts, fn) {
  // Determine ahead of time if we need to resolve any semver ranges
  var needsResolved = _.any(versions, isRange);
  async.auto({
    // Get versions of node
    node: needsResolved && opts.install ? n.ls : noop,
    // Get versions of io.js
    io: needsResolved && opts.install ? n.io.ls : noop,
    // Get installed node and io.js versions
    installed: installed,
    // Resolve any semver ranges
    versions: ['node', 'io', 'installed', function(next, results) {
      return needsResolved ? resolve(versions, results, opts, next) : filter(versions, results, opts, next);
    }],
    // Install any missing node binaries
    install: ['versions', function(next, results) {
      return opts.install ? install(results.versions, opts, next) : next(); 
    }],
    // Load the npm config so we can get a reliable global path
    npm: function(next) {
      return opts.global ? npm.load({ global: true }, next) : next();
    },
    // Prefix the command with the global npm path
    command: ['npm', function(next) {
      return opts.global ? globalize(command, next) : next(null, command);
    }]
  }, function(err, results) {
    return err ? fn(err) : run(results, opts, fn);
  });
};

exports.run = function(command, versions, opts, fn) {
  if (typeof opts === 'function') {
    fn = opts;
    opts = {};
  }

  // Gather options
  opts = _.defaults(opts, { install: false, global: false, quiet: false });
  command = typeof command === 'string' ? command.split(' ') : command;

  setup(command, versions, opts, fn);
};