diff --git a/lib/copy-dependencies.js b/lib/copy-dependencies.js new file mode 100644 index 0000000..6c36fa6 --- /dev/null +++ b/lib/copy-dependencies.js @@ -0,0 +1,16 @@ +const Path = require('path') +const FS = require('fs-extra') + +module.exports = function (options, done) { + var source = Path.resolve(options.input, 'node_modules') + var destination = Path.resolve(options.directory, + 'bundle/programs/server/node_modules' + ) + + FS.readdir(source, function (err) { + if (err && err.code === 'ENOENT') return done() + if (err) return done(err) + + FS.copy(source, destination, done) + }) +} diff --git a/lib/demeteorizer.js b/lib/demeteorizer.js index 95dc95a..2b62945 100644 --- a/lib/demeteorizer.js +++ b/lib/demeteorizer.js @@ -1,14 +1,24 @@ const Hoek = require('hoek') const Build = require('./build') +const Install = require('./install') +const CopyDependencies = require('./copy-dependencies') const UpdatePackage = require('./update-package') module.exports = (options, done) => { Hoek.assert(options !== undefined, 'You must provide a valid options object') - Build(options, (err) => { + Install(options, (err) => { if (err) return done(err) - UpdatePackage(options, done) + Build(options, (err) => { + if (err) return done(err) + + CopyDependencies(options, (err) => { + if (err) return done(err) + + UpdatePackage(options, done) + }) + }) }) } diff --git a/lib/install.js b/lib/install.js new file mode 100644 index 0000000..e518dbf --- /dev/null +++ b/lib/install.js @@ -0,0 +1,26 @@ +const FS = require('fs') +const Path = require('path') +const Exec = require('./exec') + +module.exports = function (options, done) { + var pkgPath = Path.resolve(options.input, 'package.json') + + FS.access(pkgPath, FS.F_OK, function (err) { + var args, install + + if (err) return done() + + args = ['install', '--production'] + + install = Exec.spawn('npm', args, { cwd: options.input, stdio: 'inherit' }) + + install.on('error', function (err) { + return done(err) + }) + + install.on('close', function (code) { + if (code !== 0) return done(new Error('NPM install failed.')) + done() + }) + }) +} diff --git a/package.json b/package.json index 9a447ac..c768d69 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,10 @@ }, "dependencies": { "commander": "2.9.0", + "cross-spawn": "3.0.1", + "fs-extra": "0.30.0", "hoek": "4.x.x", - "semver": "5.1.0", - "cross-spawn": "4.0.2" + "semver": "5.1.0" }, "devDependencies": { "code": "2.x.x", diff --git a/test/copy-dependencies.js b/test/copy-dependencies.js new file mode 100644 index 0000000..fcbec4e --- /dev/null +++ b/test/copy-dependencies.js @@ -0,0 +1,91 @@ +const Path = require('path') +const Code = require('code') +const Lab = require('lab') +const Proxyquire = require('proxyquire') +const Sinon = require('sinon') + +const FsStub = { readdir: Sinon.stub(), copy: Sinon.stub() } +const CopyDependencies = Proxyquire('../lib/copy-dependencies', { + 'fs-extra': FsStub +}) + +var lab = exports.lab = Lab.script() + +var describe = lab.describe +var beforeEach = lab.beforeEach +var afterEach = lab.afterEach +var it = lab.it +var expect = Code.expect + +describe('move-dependencies', function () { + var directory = Path.resolve(__dirname, '.demeteorized') + var options = { input: __dirname, directory: directory } + + beforeEach(function (done) { + done() + }) + + afterEach(function (done) { + FsStub.readdir.reset() + FsStub.copy.reset() + done() + }) + + it('exports a function', function (done) { + expect(CopyDependencies).to.be.a.function() + done() + }) + + describe('node_modules directory', function () { + describe('directory does not exist', function () { + beforeEach(function (done) { + FsStub.readdir.yields({ code: 'ENOENT' }) + done() + }) + + it('exists early when directory does not exist', function (done) { + CopyDependencies(options, function (err) { + expect(err).to.not.exist() + expect(FsStub.copy.called).to.be.false() + done() + }) + }) + }) + + describe('error reading directory', function () { + beforeEach(function (done) { + FsStub.readdir.yields(new Error('fs error')) + done() + }) + + it('returns error when reading node_modules directory', function (done) { + CopyDependencies(options, function (err) { + expect(err.message).to.equal('fs error') + expect(FsStub.copy.called).to.be.false() + done() + }) + }) + }) + + describe('copying directory', function () { + beforeEach(function (done) { + FsStub.readdir.yields() + FsStub.copy.yields() + done() + }) + + it('copies node_modules into demeteorized application', function (done) { + var source = Path.resolve(options.input, 'node_modules') + var destination = Path.resolve(options.directory, + 'bundle/programs/server/node_modules' + ) + + CopyDependencies(options, function (err) { + expect(err).to.not.exist() + expect(FsStub.copy.calledWith(source, destination)).to.be.true() + done() + }) + }) + }) + }) +}) diff --git a/test/install.js b/test/install.js new file mode 100644 index 0000000..89ce166 --- /dev/null +++ b/test/install.js @@ -0,0 +1,116 @@ +const EventEmitter = require('events').EventEmitter + +const Code = require('code') +const Lab = require('lab') +const Proxyquire = require('proxyquire') +const Sinon = require('sinon') + +const cpStub = {} +const FsStub = { access: Sinon.stub(), F_OK: 0 } + +const Install = Proxyquire('../lib/install', { + 'fs': FsStub, + './exec': cpStub +}) + +var lab = exports.lab = Lab.script() + +var describe = lab.describe +var beforeEach = lab.beforeEach +var it = lab.it +var expect = Code.expect + +describe('install', function () { + var emitter, options + + beforeEach(function (done) { + emitter = new EventEmitter() + cpStub.spawn = Sinon.stub().returns(emitter) + done() + }) + + it('exports a function', function (done) { + expect(Install).to.be.a.function() + done() + }) + + describe('no package.json', function () { + beforeEach(function (done) { + options = { input: __dirname } + FsStub.access.yields(new Error('FS error')) + done() + }) + + it('returns early and does not install', function (done) { + Install(options, function (err) { + expect(err).to.not.exist() + expect(cpStub.spawn.called).to.be.false() + done() + }) + }) + }) + + describe('default options', function () { + beforeEach(function (done) { + options = { input: __dirname } + FsStub.access.yields() + done() + }) + + it('installs when default options are present', function (done) { + Install(options, function () { + expect(cpStub.spawn.calledWith('npm', [ + 'install', + '--production' + ])).to.be.true() + done() + }) + + emitter.emit('close', 0) + }) + }) + + describe('exit code', function () { + beforeEach(function (done) { + options = { input: __dirname } + FsStub.access.yields() + done() + }) + + it('returns an error on failed exit', function (done) { + Install(options, function (err) { + expect(err.message).to.equal('NPM install failed.') + done() + }) + + emitter.emit('close', 1) + }) + + it('returns an error when the command fails', function (done) { + Install(options, function (err) { + expect(err.message).to.equal('failed') + done() + }) + + emitter.emit('error', new Error('failed')) + }) + + it('returns an error when command fails', function (done) { + Install(options, function (err) { + expect(err.message).to.equal('failed') + done() + }) + + emitter.emit('error', new Error('failed')) + }) + + it('returns no error on success', function (done) { + Install(options, function (err) { + expect(err).to.not.exist() + done() + }) + + emitter.emit('close', 0) + }) + }) +})