From 5ea787b3a1ad644976fcae5ec87dc9e1f746b03f Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 25 Aug 2015 13:01:52 -0400 Subject: [PATCH] Added `add` command and tests. --- cli/bitcore-node.js | 18 +++-- lib/scaffold/add.js | 102 +++++++++++++++++++++++++++ test/scaffold/add.integration.js | 114 ++++++++++++++++++++++++++----- 3 files changed, 211 insertions(+), 23 deletions(-) diff --git a/cli/bitcore-node.js b/cli/bitcore-node.js index 168cac74..e53ffdb2 100755 --- a/cli/bitcore-node.js +++ b/cli/bitcore-node.js @@ -55,17 +55,21 @@ program }); program - .command('add ') + .command('add ') .alias('install') .description('Install a module for the current node') - .action(function(module){ - var config = findConfig(); - if (!config) { + .action(function(modules){ + var configInfo = findConfig(process.cwd()); + if (!configInfo) { throw new Error('Could not find configuration, see `bitcore-node create --help`'); } - add(config, module); - console.log('Successfully added module: ', module); - console.log(module); + var opts = { + path: configInfo.path, + modules: modules + }; + add(opts, function() { + console.log('Successfully added modules: ', modules.join(', ')); + }); }).on('--help', function() { console.log(' Examples:'); console.log(); diff --git a/lib/scaffold/add.js b/lib/scaffold/add.js index eb109abb..d8d159d9 100644 --- a/lib/scaffold/add.js +++ b/lib/scaffold/add.js @@ -1,2 +1,104 @@ 'use strict'; +var async = require('async'); +var fs = require('fs'); +var path = require('path'); +var spawn = require('child_process').spawn; +var bitcore = require('bitcore'); +var $ = bitcore.util.preconditions; +var _ = bitcore.deps._; + +/** + * @param {String} configFilePath - The absolute path to the configuration file + * @param {String} module - The name of the module + * @param {Function} done + */ +function addConfig(configFilePath, module, done) { + $.checkState(path.isAbsolute(configFilePath), 'An absolute path is expected'); + fs.readFile(configFilePath, function(err, data) { + if (err) { + return done(err); + } + var config = JSON.parse(data); + $.checkState( + Array.isArray(config.modules), + 'Configuration file is expected to have a modules array.' + ); + config.modules.push(module); + config.modules = _.unique(config.modules); + config.modules.sort(function(a, b) { + return a > b; + }); + fs.writeFile(configFilePath, JSON.stringify(config, null, 2), done); + }); +} + +/** + * @param {String} configDir - The absolute configuration directory path + * @param {String} module - The name of the module + * @param {Function} done + */ +function addModule(configDir, module, done) { + $.checkState(path.isAbsolute(configDir), 'An absolute path is expected'); + var npm = spawn('npm', ['install', module, '--save'], {cwd: configDir}); + + npm.stdout.on('data', function(data) { + process.stdout.write(data); + }); + + npm.stderr.on('data', function(data) { + process.stderr.write(data); + }); + + npm.on('close', function(code) { + if (code !== 0) { + return done(new Error('There was an error installing module: ' + module)); + } else { + return done(); + } + }); +} + +/** + * @param {String} options.cwd - The current working directory + * @param {String} options.dirname - The bitcore-node configuration directory + * @param {Array} options.modules - An array of strings of module names + * @param {Function} done - A callback function called when finished + */ +function add(options, done) { + $.checkArgument(_.isObject(options)); + $.checkArgument(_.isFunction(done)); + $.checkArgument( + _.isString(options.path) && path.isAbsolute(options.path), + 'An absolute path is expected' + ); + $.checkArgument(Array.isArray(options.modules)); + + var configPath = options.path; + var modules = options.modules; + + var bitcoreConfigPath = path.resolve(configPath, 'bitcore-node.json'); + var packagePath = path.resolve(configPath, 'package.json'); + + if (!fs.existsSync(bitcoreConfigPath) || !fs.existsSync(packagePath)) { + return done( + new Error('Directory does not have a bitcore-node.json and/or package.json file.') + ); + } + + async.eachSeries( + modules, + function(module, next) { + // npm install --save + addModule(configPath, module, function(err) { + if (err) { + return next(err); + } + // add module to bitcore-node.json + addConfig(bitcoreConfigPath, module, next); + }); + }, done + ); +} + +module.exports = add; diff --git a/test/scaffold/add.integration.js b/test/scaffold/add.integration.js index 73a0d2c0..bfea5917 100644 --- a/test/scaffold/add.integration.js +++ b/test/scaffold/add.integration.js @@ -2,37 +2,119 @@ var should = require('chai').should(); var sinon = require('sinon'); +var path = require('path'); +var fs = require('fs'); +var proxyquire = require('proxyquire'); +var mkdirp = require('mkdirp'); +var rimraf = require('rimraf'); +var add = require('../../lib/scaffold/add'); describe('#add', function() { - before(function() { - // setup testing directories + var basePath = path.resolve(__dirname, '..'); + var testDir = path.resolve(basePath, 'temporary-test-data'); + var startConfig = { + name: 'My Node', + modules: [] + }; + var startPackage = {}; + + before(function(done) { + mkdirp(testDir + '/s0/s1', function(err) { + if (err) { + throw err; + } + fs.writeFile( + testDir + '/s0/s1/bitcore-node.json', + JSON.stringify(startConfig), + function(err) { + if (err) { + throw err; + } + fs.writeFile( + testDir + '/s0/s1/package.json', + JSON.stringify(startPackage), + done + ); + } + ); + }); }); - after(function() { + after(function(done) { // cleanup testing directories + rimraf(testDir, function(err) { + if (err) { + throw err; + } + done(); + }); }); describe('will modify scaffold files', function() { - it('will give an error if expected files do not exist', function() { - + it('will give an error if expected files do not exist', function(done) { + add({ + path: path.resolve(testDir, 's0'), + modules: ['a', 'b', 'c'] + }, function(err) { + should.exist(err); + err.message.match(/^Invalid state/); + done(); + }); }); - it('will update bitcore-node.json modules', function() { + it('will receive error from `npm install`', function(done) { + var spawn = sinon.stub().returns({ + stdout: { + on: sinon.stub() + }, + stderr: { + on: sinon.stub() + }, + on: sinon.stub().callsArgWith(1, 1) + }); + var addtest = proxyquire('../../lib/scaffold/add', { + 'child_process': { + spawn: spawn + } + }); + addtest({ + path: path.resolve(testDir, 's0/s1/'), + modules: ['a', 'b', 'c'] + }, function(err) { + should.exist(err); + err.message.should.equal('There was an error installing module: a'); + done(); + }); }); - it('will update package.json modules', function() { - - }); - - it('will install the necessary node.js modules', function() { - - }); - - it('will install dependencies', function() { - + it('will update bitcore-node.json modules', function(done) { + var spawn = sinon.stub().returns({ + stdout: { + on: sinon.stub() + }, + stderr: { + on: sinon.stub() + }, + on: sinon.stub().callsArgWith(1, 0) + }); + var addtest = proxyquire('../../lib/scaffold/add', { + 'child_process': { + spawn: spawn + } + }); + addtest({ + path: path.resolve(testDir, 's0/s1/'), + modules: ['a', 'b', 'c'] + }, function(err) { + should.not.exist(err); + var configPath = path.resolve(testDir, 's0/s1/bitcore-node.json'); + var config = JSON.parse(fs.readFileSync(configPath)); + config.modules.should.deep.equal(['a','b','c']); + done(); + }); }); });