Skip to content

Commit

Permalink
Refactor setup command, make version argument a semver range
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Van Camp committed Feb 2, 2016
1 parent a2ac816 commit bc0cd02
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 75 deletions.
184 changes: 111 additions & 73 deletions commands/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@ var execSync = childProcess.execSync;
var os = require('os');
var chalk = require('chalk');
var inquirer = require('inquirer');
var semver = require('semver');

var NODECG_GIT_URL = 'https://github.com/nodecg/nodecg.git';

module.exports = function initCommand(program) {
program
.command('setup [version]')
.option('-u, --update', 'Update the local NodeCG installation')
.option('--skip-npm', 'Skip installing npm dependencies')
.option('--skip-dependencies', 'Skip installing npm & bower dependencies')
.description('Install a new NodeCG instance')
.action(action);
};

function action(version, options) {
// We prefix our release tags with 'v'
if (version && version.charAt(0) !== 'v') {
version = 'v' + version;
}
// If NodeCG is already installed but the `-u` flag was not supplied, display an error and return.
var isUpdate = false;

// If NodeCG is already installed and the `-u` flag was supplied, update NodeCG
// If NodeCG exists in the cwd, but the `-u` flag was not supplied, display an error and return.
// If it was supplied, fetch the latest tags and set the `isUpdate` flag to true for later use.
if (util.pathContainsNodeCG(process.cwd())) {
if (!options.update) {
console.log('NodeCG is already installed in this directory.');
Expand All @@ -33,7 +33,8 @@ function action(version, options) {
return;
}

// Fetch the latest tags from GitHub
isUpdate = true;

process.stdout.write('Fetching the list of releases... ');
try {
execSync('git fetch');
Expand All @@ -43,71 +44,99 @@ function action(version, options) {
console.error(e.stack);
return;
}
}

if (version) {
process.stdout.write('Searching for release ' + chalk.magenta(version) + ' ... ');
} else {
process.stdout.write('Checking against local install for updates... ');
}

var tags;
// If this is a clean, empty directory, then we need to clone a fresh copy of NodeCG into the cwd.
else {
process.stdout.write('Cloning NodeCG... ');
try {
tags = execSync('git tag').toString().trim().split('\n');
execSync('git clone ' + NODECG_GIT_URL + ' .', {stdio: ['pipe', 'pipe', 'pipe']});
process.stdout.write(chalk.green('done!') + os.EOL);
} catch (e) {
process.stdout.write(chalk.red('failed!') + os.EOL);
console.error(e.stack);
return;
}
}

if (version) {
process.stdout.write('Finding latest release that satisfies semver range ' + chalk.magenta(version) + '... ');
} else if (isUpdate) {
process.stdout.write('Checking against local install for updates... ');
} else {
process.stdout.write('Finding latest release... ');
}

var tags;
try {
tags = execSync('git tag').toString().trim().split('\n');
} catch (e) {
process.stdout.write(chalk.red('failed!') + os.EOL);
console.error(e.stack);
return;
}

var current = 'v' + require(process.cwd() + '/package.json').version;
var target;

// If a version was specified, look for it in the list of tags
if (version && tags.indexOf(version) < 0) {
// If a version (or semver range) was supplied, find the latest release that satisfies the range.
// Else, make the target the newest version.
if (version) {
var maxSatisfying = semver.maxSatisfying(tags, version);
if (!maxSatisfying) {
process.stdout.write(chalk.red('failed!') + os.EOL);
console.error('The desired release, ' + chalk.magenta(version) + ', could not be found.');
console.error('No releases match the supplied semver range (' + chalk.magenta(version) + ')');
return;
}
target = maxSatisfying;
} else {
target = semver.maxSatisfying(tags, '');
}

// Now that we know our version exists, our target is either the version or the newest tag.
var updatingToLatest = Boolean(!version);
var target = version || tags.pop();
process.stdout.write(chalk.green('done!') + os.EOL);
process.stdout.write(chalk.green('done!') + os.EOL);

if (isUpdate) {
var current = require(process.cwd() + '/package.json').version;

if (semver.eq(target, current)) {
console.log('The target version (%s) is equal to the current version (%s). No action will be taken.',
chalk.magenta(target), chalk.magenta(current));
return;
}

if (target < current) {
else if (semver.lt(target, current)) {
console.log(chalk.red('WARNING: ') + 'The target version (%s) is older than the current version (%s)',
chalk.magenta(target), chalk.magenta(current));

inquirer.prompt([{
name: 'installOlder',
message: 'Are you sure you wish to continue?',
type: 'confirm'
}], function (answers) {
if (answers.installOlder) {
checkoutAfterFetch();
}
});
} else {
checkoutAfterFetch();
}
checkoutUpdate(current, target, options.skipDependencies, true);

return;
}
/*
I'm unsure why, but using inquirer here causes the process to never exit, because
a net socket is left open. I've no idea why Inquirer is causing a socket to open,
nor why it is not being destroyed. The below workaround forces the socket to be destroyed.
function checkoutAfterFetch() {
if (target === current) {
console.log('Already on version %s', chalk.magenta(current));
return;
Lange - 2/2/2016
*/
var handles = process._getActiveHandles();
handles[2].destroy();
}
});
}

if (updatingToLatest && current >= target) {
console.log('No updates found! Your current version (%s) is the latest.', chalk.magenta(current));
return;
else {
checkoutUpdate(current, target, options.skipDependencies);
}
}

// Now that we know for sure if the target tag exists (and if its newer or older than current),
// we can `git pull` and `git checkout <tag>` if appropriate.
process.stdout.write('Updating from ' + chalk.magenta(current) + ' to ' + chalk.magenta(target) + '... ');
else {
// Check out the target version.
process.stdout.write('Checking out version ' + target + '... ');
try {
execSync('git pull origin master', {stdio: ['pipe', 'pipe', 'pipe']});
execSync('git checkout ' + target, {stdio: ['pipe', 'pipe', 'pipe']});
process.stdout.write(chalk.green('done!') + os.EOL);
} catch (e) {
Expand All @@ -116,50 +145,59 @@ function action(version, options) {
return;
}

if (target) {
console.log('NodeCG updated to', chalk.magenta(target));
// Install NodeCG's production dependencies (`npm install --production`)
// This operation takes a very long time, so we don't test it.
/* istanbul ignore if */
if (!options.skipDependencies) {
installDependencies();
}

console.log('NodeCG (%s) installed to', target, process.cwd());
}
}

// If we're here, then NodeCG must not be installed yet.
// Clone it into the cwd.
process.stdout.write('Cloning NodeCG... ');
function installDependencies() {
process.stdout.write('Installing production npm dependencies... ');
try {
execSync('git clone ' + NODECG_GIT_URL + ' .', {stdio: ['pipe', 'pipe', 'pipe']});
execSync('npm install --production', { stdio: ['pipe', 'pipe', 'pipe'] });
process.stdout.write(chalk.green('done!') + os.EOL);
} catch (e) {
process.stdout.write(chalk.red('failed!') + os.EOL);
console.error(e.stack);
return;
}

// If [version] was supplied, checkout that version
if (version) {
process.stdout.write('Checking out version ' + version + '... ');
try {
execSync('git checkout ' + version, {stdio: ['pipe', 'pipe', 'pipe']});
process.stdout.write(chalk.green('done!') + os.EOL);
} catch (e) {
process.stdout.write(chalk.red('failed!') + os.EOL);
console.error(e.stack);
return;
}
process.stdout.write('Installing production bower dependencies... ');
try {
execSync('bower install --production', { stdio: ['pipe', 'pipe', 'pipe'] });
process.stdout.write(chalk.green('done!') + os.EOL);
} catch (e) {
process.stdout.write(chalk.red('failed!') + os.EOL);
console.error(e.stack);
}
}

// Install NodeCG's production dependencies (`npm install --production`)
// This operation takes a very long time, so we don't test it.
/* istanbul ignore if */
if (!options.skipNpm) {
process.stdout.write('Installing production npm dependencies... ');
try {
execSync('npm install --production', { stdio: ['pipe', 'pipe', 'pipe'] });
process.stdout.write(chalk.green('done!') + os.EOL);
} catch (e) {
process.stdout.write(chalk.red('failed!') + os.EOL);
console.error(e.stack);
return;
function checkoutUpdate(current, target, skipDependencies, downgrade) {
// Now that we know for sure if the target tag exists (and if its newer or older than current),
// we can `git pull` and `git checkout <tag>` if appropriate.
var Verb = downgrade ? 'Downgrading' : 'Upgrading';
process.stdout.write(Verb + ' from ' + chalk.magenta(current) + ' to ' + chalk.magenta(target) + '... ');

try {
execSync('git pull origin master', {stdio: ['pipe', 'pipe', 'pipe']});
execSync('git checkout ' + target, {stdio: ['pipe', 'pipe', 'pipe']});
process.stdout.write(chalk.green('done!') + os.EOL);
if (!skipDependencies) {
installDependencies();
}
} catch (e) {
process.stdout.write(chalk.red('failed!') + os.EOL);
console.error(e.stack);
return;
}

console.log('NodeCG (%s) installed to', version ? version : 'latest', process.cwd());
if (target) {
var verb = downgrade ? 'downgraded' : 'upgraded';
console.log('NodeCG %s to', verb, chalk.magenta(target));
}
}
4 changes: 2 additions & 2 deletions test/commands/1_setup.mspec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ describe('setup command', function () {
this.timeout(20000);
fs.mkdirSync('tmp');
process.chdir('tmp');
program.runWith('setup 0.5.0 --skip-npm');
program.runWith('setup 0.5.0 --skip-dependencies');
assert.equal(fs.existsSync('./package.json'), true);
assert.equal(JSON.parse(fs.readFileSync('./package.json')).name, 'nodecg');
});

it('should let the user change versions', function () {
this.timeout(16000);
program.runWith('setup 0.5.1 -u --skip-npm');
program.runWith('setup 0.5.1 -u --skip-dependencies');
assert.equal(JSON.parse(fs.readFileSync('./package.json')).version, '0.5.1');
});
});

0 comments on commit bc0cd02

Please sign in to comment.