From 94cb4d6342a17fbaab999c27634651a73f90386d Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 9 Oct 2014 12:18:36 -0700 Subject: [PATCH] Add support for async boot scripts --- .jshintrc | 1 + index.js | 4 +- lib/executor.js | 51 ++++++++++++++++---- package.json | 1 + test/executor.test.js | 60 ++++++++++++++++++++++-- test/fixtures/simple-app/boot/bar.js | 8 ++++ test/fixtures/simple-app/boot/barSync.js | 5 ++ test/fixtures/simple-app/boot/foo.js | 2 +- 8 files changed, 115 insertions(+), 17 deletions(-) create mode 100644 test/fixtures/simple-app/boot/bar.js create mode 100644 test/fixtures/simple-app/boot/barSync.js diff --git a/.jshintrc b/.jshintrc index 569b12d..3f23a40 100644 --- a/.jshintrc +++ b/.jshintrc @@ -12,6 +12,7 @@ "newcap": true, "nonew": true, "sub": true, +"unused": "vars", "globals": { "describe": true, "it": true, diff --git a/index.js b/index.js index 772a321..dd84419 100644 --- a/index.js +++ b/index.js @@ -75,12 +75,12 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * @header boot(app, [options]) */ -exports = module.exports = function bootLoopBackApp(app, options) { +exports = module.exports = function bootLoopBackApp(app, options, callback) { // backwards compatibility with loopback's app.boot options.env = options.env || app.get('env'); var instructions = compile(options); - execute(app, instructions); + execute(app, instructions, callback); }; /** diff --git a/lib/executor.js b/lib/executor.js index d04e833..ceef61f 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -2,6 +2,7 @@ var assert = require('assert'); var _ = require('underscore'); var semver = require('semver'); var debug = require('debug')('loopback:boot:executor'); +var async = require('async'); /** * Execute bootstrap instructions gathered by `boot.compile`. @@ -12,7 +13,7 @@ var debug = require('debug')('loopback:boot:executor'); * @header boot.execute(instructions) */ -module.exports = function execute(app, instructions) { +module.exports = function execute(app, instructions, callback) { patchAppLoopback(app); assertLoopBackVersion(app); @@ -24,9 +25,16 @@ module.exports = function execute(app, instructions) { setupDataSources(app, instructions); setupModels(app, instructions); - runBootScripts(app, instructions); - - enableAnonymousSwagger(app, instructions); + // Run the boot scripts in series synchronously or asynchronously + // Please note async supports both styles + async.series([ + function(done) { + runBootScripts(app, instructions, done); + }, + function(done) { + enableAnonymousSwagger(app, instructions); + done(); + }], callback); }; function patchAppLoopback(app) { @@ -176,13 +184,36 @@ function forEachKeyedObject(obj, fn) { }); } -function runScripts(app, list) { - if (!list || !list.length) return; +function runScripts(app, list, callback) { + list = list || []; + var functions = []; list.forEach(function(filepath) { + debug('Requiring script %s', filepath); var exports = tryRequire(filepath); - if (typeof exports === 'function') - exports(app); + if (typeof exports === 'function') { + debug('Exported function detected %s', filepath); + functions.push({ + path: filepath, + func: exports + }); + } }); + + async.eachSeries(functions, function(f, done) { + debug('Running script %s', f.path); + if (f.func.length >= 2) { + debug('Starting async function %s', f.path); + f.func(app, function(err) { + debug('Async function finished %s', f.path); + done(err); + }); + } else { + debug('Starting sync function %s', f.path); + f.func(app); + debug('Sync function finished %s', f.path); + done(); + } + }, callback); } function tryRequire(modulePath) { @@ -198,8 +229,8 @@ function tryRequire(modulePath) { } } -function runBootScripts(app, instructions) { - runScripts(app, instructions.files.boot); +function runBootScripts(app, instructions, callback) { + runScripts(app, instructions.files.boot, callback); } function enableAnonymousSwagger(app, instructions) { diff --git a/package.json b/package.json index 8fbb725..2c2c8cb 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "url": "https://github.com/strongloop/loopback-boot/blob/master/LICENSE" }, "dependencies": { + "async": "~0.9.0", "commondir": "0.0.1", "debug": "^1.0.4", "lodash.clonedeep": "^2.4.1", diff --git a/test/executor.test.js b/test/executor.test.js index 8c53715..4a63929 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -3,6 +3,7 @@ var path = require('path'); var loopback = require('loopback'); var assert = require('assert'); var expect = require('must'); +var fs = require('fs-extra'); var sandbox = require('./helpers/sandbox'); var appdir = require('./helpers/appdir'); @@ -161,13 +162,62 @@ describe('executor', function() { describe('with boot and models files', function() { beforeEach(function() { + process.bootFlags = process.bootFlags || []; boot.execute(app, simpleAppInstructions()); }); - it('should run `boot/*` files', function() { - assert(process.loadedFooJS); - delete process.loadedFooJS; + afterEach(function() { + delete process.bootFlags; }); + + it('should run `boot/*` files', function(done) { + // scripts are loaded by the order of file names + expect(process.bootFlags).to.eql([ + 'barLoaded', + 'barSyncLoaded', + 'fooLoaded', + 'barStarted' + ]); + + // bar finished happens in the next tick + // barSync executed after bar finished + setTimeout(function() { + expect(process.bootFlags).to.eql([ + 'barLoaded', + 'barSyncLoaded', + 'fooLoaded', + 'barStarted', + 'barFinished', + 'barSyncExecuted' + ]); + done(); + }, 10); + }); + }); + + describe('with boot with callback', function() { + beforeEach(function() { + process.bootFlags = process.bootFlags || []; + }); + + afterEach(function() { + delete process.bootFlags; + }); + + it('should run `boot/*` files asynchronously', function(done) { + boot.execute(app, simpleAppInstructions(), function() { + expect(process.bootFlags).to.eql([ + 'barLoaded', + 'barSyncLoaded', + 'fooLoaded', + 'barStarted', + 'barFinished', + 'barSyncExecuted' + ]); + done(); + }); + }); + }); describe('with PaaS and npm env variables', function() { @@ -299,5 +349,7 @@ function someInstructions(values) { } function simpleAppInstructions() { - return boot.compile(SIMPLE_APP); + // Copy it so that require will happend again + fs.copySync(SIMPLE_APP, appdir.PATH); + return boot.compile(appdir.PATH); } diff --git a/test/fixtures/simple-app/boot/bar.js b/test/fixtures/simple-app/boot/bar.js new file mode 100644 index 0000000..4e732a1 --- /dev/null +++ b/test/fixtures/simple-app/boot/bar.js @@ -0,0 +1,8 @@ +process.bootFlags.push('barLoaded'); +module.exports = function(app, callback) { + process.bootFlags.push('barStarted'); + process.nextTick(function() { + process.bootFlags.push('barFinished'); + callback(); + }); +}; diff --git a/test/fixtures/simple-app/boot/barSync.js b/test/fixtures/simple-app/boot/barSync.js new file mode 100644 index 0000000..64c0b0b --- /dev/null +++ b/test/fixtures/simple-app/boot/barSync.js @@ -0,0 +1,5 @@ +process.bootFlags.push('barSyncLoaded'); +module.exports = function(app) { + process.bootFlags.push('barSyncExecuted'); +}; + diff --git a/test/fixtures/simple-app/boot/foo.js b/test/fixtures/simple-app/boot/foo.js index 7e74863..6641f03 100644 --- a/test/fixtures/simple-app/boot/foo.js +++ b/test/fixtures/simple-app/boot/foo.js @@ -1 +1 @@ -process.loadedFooJS = true; +process.bootFlags.push('fooLoaded'); \ No newline at end of file