index.js
'use strict';
var util = require('util');
var semver = require('semver');
var child = require('child_process');
var concat = require('concat-stream');
var Promise = require('bluebird');
var name = require('./package.json').name;
var VERSION_REGEX = /(\w+)-(.+)/;
/**
* Get the system version of Node, if present.
* Otherwise, return a rejected Promise.
*/
var getSystemNode = function() {
return new Promise(function(resolve, reject) {
var stdout, stderr;
var cmd = child.spawn(process.env.SHELL,
['-c', 'source $NVM_DIR/nvm.sh; nvm run --silent system --version;']);
cmd.stdout.pipe(concat(function(data) {
stdout = data;
}));
cmd.stderr.pipe(concat(function(data) {
stderr = data;
}));
cmd.on('close', function(code) {
if (code === 0) { resolve({ stdout: stdout, stderr: stderr }); }
else {
reject(new Error('Could not find system version of node.'));
}
});
});
};
/**
* Run an nvm command.
*
* @private
* @function
* @param {String} command
* @return {Promise}
*/
var nvmCommand = function(command) {
return new Promise(function(resolve, reject) {
var stdout, stderr;
var cmd = child.spawn(process.env.SHELL,
['-c', 'source $NVM_DIR/nvm.sh; nvm ' + command]);
cmd.stdout.pipe(concat(function(data) {
stdout = data;
}));
cmd.stderr.pipe(concat(function(data) {
stderr = data;
}));
cmd.on('close', function(code) {
if (code === 0) { resolve({ stdout: stdout, stderr: stderr }); }
else {
reject(new Error(util.format('nvm exited with status: %d\n%s',
code, String(stdout).trim() + String(stderr).trim())));
}
});
});
};
/**
* Prase versions
*
* @private
* @function
* @param {String} output
* @return {Array.<String>}
*/
var parseVersions = function(output) {
var string = String(output.stdout)
.replace(/\x1b[^m]*m/g, '')
.replace(/^->/gm, '');
return string.split('\n')
.map(function(line) { return line.trim(); })
.filter(function(line) { return line && !line.match(/current|system|->/); });
};
/**
* List versions.
*
* @private
* @function
* @return {Promise}
*/
var listVersions = function() {
// find all of the versions of node installed by nvm.
return Promise.resolve()
.then(function() { return nvmCommand('list'); })
.then(parseVersions);
};
/**
* Extract a name from a version (to support iojs)
*
* @private
* @function
* @return {Promise}
*/
var versionName = function(version) {
var match = version.match(VERSION_REGEX);
return match ? match[1] : null;
};
/**
* Extract just the version number from a version.
*
* @private
* @function
* @param {String} version
* @return {String}
*/
var versionNumber = function(version) {
var match = version.match(VERSION_REGEX);
return match ? match[2] : version;
};
/**
* Find a version.
*
* @param {Array.<String>} versions
* @param {String} matching
* @return {String}
*/
var findVersion = function(versions, matching) {
if (matching === 'system') {
return matching;
}
var highestMatch = null;
var mName = versionName(matching);
var mNumber = versionNumber(matching);
versions.forEach(function(v) {
var vName = versionName(v);
var vNumber = versionNumber(v);
if (vName === mName && semver.satisfies(vNumber, mNumber)) {
if (!highestMatch) { highestMatch = v; }
else if (semver.gt(vNumber, versionNumber(highestMatch))) {
highestMatch = v;
}
}
});
return highestMatch;
};
/**
* Parse the results of the nvm version call.
*
* @param {Promise} matching
*/
var parseMatching = function(matching) {
return String(matching.stdout).trim();
};
/**
* Use nvm to resolve a version string (which could be a version number or an
alias) to an installed version number (or N/A).
*
* @param {String} matching
*/
var resolveVersion = function(matching) {
return Promise.resolve()
.then(function() { return nvmCommand('version "' + matching + '"'); })
.then(parseMatching);
};
/**
* Get installed version matching a given version.
*
* @param {String} matching
* @return {Promise}
*/
var installedVersion = function(matching) {
return Promise.resolve()
.then(function() {
return Promise.all([
resolveVersion(matching),
listVersions(),
]);
})
.spread(function(parsedVersion, versions) {
var parsedMatching = parsedVersion !== 'N/A' ? parsedVersion : matching;
return findVersion(versions, parsedMatching);
});
};
/**
* Match a specific version.
*
* @param {String} version
* @return {Promise}
*/
var match = function(version) {
return Promise.resolve()
.then(function() { return installedVersion(version); })
.then(function(useVersion) {
return useVersion === 'system' ?
getSystemNode()
.then(parseMatching)
.then(function(use) {
return 'system: ' + use;
}) :
useVersion;
})
.then(function(useVersion) {
var use = (useVersion && useVersion.indexOf('system') === 0) ?
'system' : useVersion;
var command = util.format('nvm use %s > /dev/null;', use);
var result = { version: useVersion, command: command };
return use ? result :
Promise.reject(new Error('no version matching ' + version));
});
};
module.exports = {
name: name,
match: match,
_parseVersions: parseVersions,
_findVersion: findVersion,
};