diff --git a/.gitignore b/.gitignore index bff8718..ec7a8f1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ *.pid *.swp *.swo +*.iml node_modules checkstyle.xml loopback-boot-*.tgz 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/CONTRIBUTING.md b/CONTRIBUTING.md index a43e851..7864057 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,65 +1,24 @@ - ### Contributing ### -Thank you for your interest in `loopback`, an open source project +Thank you for your interest in `loopback-boot`, an open source project administered by StrongLoop. -Contributing to loopback is easy. In a few simple steps: +Contributing to `loopback-boot` is easy. In a few simple steps: - * Ensure that your effort is aligned with the project’s roadmap by + * Ensure that your effort is aligned with the project's roadmap by talking to the maintainers, especially if you are going to spend a - lot of time on it. This project is currently maintained by - [@ritch](https://github.com/ritch), [@raymondfeng](https://github.com/raymondfeng), - and [@bajtos](https://github.com/bajtos). The preferred channel of communication - is [LoopBack Forum](https://groups.google.com/forum/#!forum/loopbackjs) or - [Github Issues](https://github.com/strongloop/loopback/issues). + lot of time on it. * Make something better or fix a bug. - * Adhere to code style outlined in the + * Adhere to code style outlined in the [Google C++ Style Guide][] and [Google Javascript Style Guide][]. - * [Sign your patches](#signing-patches) to indicate that your are - making your contribution available under the terms of the - [Contributor License Agreement](#contributor-license-agreement). + * Sign the [Contributor License Agreement](https://cla.strongloop.com/strongloop/loopback-boot) * Submit a pull request through Github. -### Signing patches ### - -Like many open source projects, we need a contributor license agreement -from you before we can merge in your changes. - -In summary, by submitting your code, you are granting us a right to use -that code under the terms of this Agreement, including providing it to -others. You are also certifying that you wrote it, and that you are -allowed to license it to us. You are not giving up your copyright in -your work. The license does not change your rights to use your own -contributions for any other purpose. - -Contributor License Agreements are important because they define the -chain of ownership of a piece of software. Some companies won't allow -the use of free software without clear agreements around code ownership. -That's why many open source projects collect similar agreements from -contributors. The CLA here is based on the Apache CLA. - -To signify your agreement to these terms, add the following line to the -bottom of your commit message. Use your real name and an actual e-mail -address. - -``` -Signed-off-by: Random J Developer -``` - -Alternatively you can use the git command line to automatically add this -line, as follows: - -``` -$ git commit -sm "Replace rainbows by unicorns" -``` - - ### Contributor License Agreement ### ``` @@ -188,7 +147,5 @@ $ git commit -sm "Replace rainbows by unicorns" inaccurate in any respect. Email us at callback@strongloop.com. ``` - +[Google C++ Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml [Google Javascript Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml -[license]: LICENSE - diff --git a/index.js b/index.js index f6d8cbd..97e56dc 100644 --- a/index.js +++ b/index.js @@ -23,15 +23,54 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * 2. Configures Models from the `model-config.json` file in the application * root directory. * + * 3. Configures the LoopBack Application object from the `config.json` file + * in the application root directory. These properties can be accessed + * using `app.get('propname')`. + * * If the argument is an object, then it looks for `models`, `dataSources`, - * and `appRootDir` properties of the object. + * 'config', `modelsRootDir`, `dsRootDir`, `appConfigRootDir` and `appRootDir` + * properties of the object. + * * If the object has no `appRootDir` property then it sets the current working * directory as the application root directory. + * + * The execution environment, {env}, is established from, in order, + * - `options.env` + * - `process.env.NODE_ENV`, + * - the literal `development`. + * * Then it: * - * 1. Creates DataSources from the `options.dataSources` object. + * 1. Creates DataSources from the `options.dataSources` object, if provided; + * otherwise, it searches for the files + * - `datasources.json`, + * - `datasources.local.js` or `datasources.local.json` (only one), + * - `datasources.{env}.js` or `datasources.{env}.json` (only one) * - * 2. Configures Models from the `options.models` object. + * in the directory designated by 'options.dsRootDir', if present, or the + * application root directory. It merges the data source definitions from + * the files found. + * + * 2. Creates Models from the `options.models` object, if provided; + * otherwise, it searches for the files + * - `model-config.json`, + * - `model-config.local.js` or `model-config.local.json` (only one), + * - `model-config.{env}.js` or `model-config.{env}.json` (only one) + * + * in the directory designated by 'options.modelsRootDir', if present, or + * the application root directory. It merges the model definitions from the + * files found. + * + * 3. Configures the Application object from the `options.config` object, + * if provided; + * otherwise, it searches for the files + * - `config.json`, + * - `config.local.js` or `config.local.json` (only one), + * - `config.{env}.js` or `config.{env}.json` (only one) + * + * in the directory designated by 'options.appConfigRootDir', if present, or + * the application root directory. It merges the properties from the files + * found. * * In both cases, the function loads JavaScript files in the * `/boot` subdirectory of the application root directory with `require()`. @@ -55,6 +94,8 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * @property {String} [appRootDir] Directory to use when loading JSON and * JavaScript files. * Defaults to the current directory (`process.cwd()`). + * @property {String} [appConfigRootDir] Directory to use when loading + * `config.json`. Defaults to `appRootDir`. * @property {Object} [models] Object containing `Model` configurations. * @property {Object} [dataSources] Object containing `DataSource` definitions. * @property {String} [modelsRootDir] Directory to use when loading @@ -66,17 +107,21 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * `production`; however the applications are free to use any names. * @property {Array.} [modelSources] List of directories where to look * for files containing model definitions. + * @property {Array.} [bootDirs] List of directories where to look + * for boot scripts. + * @property {Array.} [bootScripts] List of script files to execute + * on boot. * @end * * @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/compiler.js b/lib/compiler.js index e75ff6b..d5d870a 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -28,7 +28,9 @@ module.exports = function compile(options) { var appRootDir = options.appRootDir = options.appRootDir || process.cwd(); var env = options.env || process.env.NODE_ENV || 'development'; - var appConfig = options.config || ConfigLoader.loadAppConfig(appRootDir, env); + var appConfigRootDir = options.appConfigRootDir || appRootDir; + var appConfig = options.config || + ConfigLoader.loadAppConfig(appConfigRootDir, env); assertIsValidConfig('app', appConfig); var modelsRootDir = options.modelsRootDir || appRootDir; @@ -42,12 +44,18 @@ module.exports = function compile(options) { assertIsValidConfig('data source', dataSourcesConfig); // require directories - var bootScripts = findScripts(path.join(appRootDir, 'boot')); + var bootDirs = options.bootDirs || []; // precedence + bootDirs = bootDirs.concat(path.join(appRootDir, 'boot')); + + var bootScripts = options.bootScripts || []; + bootDirs.forEach(function(dir) { + bootScripts = bootScripts.concat(findScripts(dir)); + }); var modelsMeta = modelsConfig._meta || {}; delete modelsConfig._meta; - var modelSources = modelsMeta.sources || ['./models']; + var modelSources = options.modelSources || modelsMeta.sources || ['./models']; var modelInstructions = buildAllModelInstructions( modelsRootDir, modelsConfig, modelSources); diff --git a/lib/config-loader.js b/lib/config-loader.js index a28be28..17834eb 100644 --- a/lib/config-loader.js +++ b/lib/config-loader.js @@ -4,7 +4,7 @@ var path = require('path'); var ConfigLoader = exports; /** - * Load application config from `app.json` and friends. + * Load application config from `config.json` and friends. * @param {String} rootDir Directory where to look for files. * @param {String} env Environment, usually `process.env.NODE_ENV` * @returns {Object} @@ -112,7 +112,7 @@ function mergeConfigurations(configObjects, mergeFn) { function mergeDataSourceConfig(target, config, fileName) { for (var ds in target) { - var err = applyCustomConfig(target[ds], config[ds]); + var err = mergeObjects(target[ds], config[ds]); if (err) { throw new Error('Cannot apply ' + fileName + ' to `' + ds + '`: ' + err); } @@ -120,23 +120,73 @@ function mergeDataSourceConfig(target, config, fileName) { } function mergeAppConfig(target, config, fileName) { - var err = applyCustomConfig(target, config); + var err = mergeObjects(target, config); if (err) { throw new Error('Cannot apply ' + fileName + ': ' + err); } } -function applyCustomConfig(target, config) { +function mergeObjects(target, config, keyPrefix) { for (var key in config) { - var value = config[key]; - if (typeof value === 'object') { - return 'override for the option `' + key + '` is not a value type.'; - } - target[key] = value; + var fullKey = keyPrefix ? keyPrefix + '.' + key : key; + var err = mergeSingleItemOrProperty(target, config, key, fullKey); + if (err) return err; } return null; // no error } +function mergeSingleItemOrProperty(target, config, key, fullKey) { + var origValue = target[key]; + var newValue = config[key]; + + if (!hasCompatibleType(origValue, newValue)) { + return 'Cannot merge values of incompatible types for the option `' + + fullKey + '`.'; + } + + if (Array.isArray(origValue)) { + return mergeArrays(origValue, newValue, fullKey); + } + + if (typeof origValue === 'object') { + return mergeObjects(origValue, newValue, fullKey); + } + + target[key] = newValue; + return null; // no error +} + +function mergeArrays(target, config, keyPrefix) { + if (target.length !== config.length) { + return 'Cannot merge array values of different length' + + ' for the option `' + keyPrefix + '`.'; + } + + // Use for(;;) to iterate over undefined items, for(in) would skip them. + for (var ix=0; ix < target.length; ix++) { + var fullKey = keyPrefix + '[' + ix + ']'; + var err = mergeSingleItemOrProperty(target, config, ix, fullKey); + if (err) return err; + } + + return null; // no error +} + +function hasCompatibleType(origValue, newValue) { + if (origValue === null || origValue === undefined) + return true; + + if (Array.isArray(origValue)) + return Array.isArray(newValue); + + if (typeof origValue === 'object') + return typeof newValue === 'object'; + + // Note: typeof Array() is 'object' too, + // we don't need to explicitly check array types + return typeof newValue !== 'object'; +} + /** * Try to read a config file with .json extension * @param cwd Dirname of the file 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..779f7d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-boot", - "version": "2.0.0", + "version": "2.1.0", "description": "Convention-based bootstrapper for LoopBack applications", "keywords": [ "StrongLoop", @@ -23,19 +23,21 @@ "url": "https://github.com/strongloop/loopback-boot/blob/master/LICENSE" }, "dependencies": { + "async": "~0.9.0", "commondir": "0.0.1", - "debug": "^1.0.4", + "debug": "^2.0.0", "lodash.clonedeep": "^2.4.1", "semver": "^2.3.0", "toposort": "^0.2.10", "underscore": "^1.6.0" }, "devDependencies": { + "browserify": "^4.1.8", + "fs-extra": "^0.10.0", + "jshint": "^2.5.6", "loopback": "^1.5.0", "mocha": "^1.19.0", "must": "^0.12.0", - "supertest": "^0.13.0", - "fs-extra": "^0.10.0", - "browserify": "^4.1.8" + "supertest": "^0.13.0" } } diff --git a/test/browser.test.js b/test/browser.test.js index 1df93f9..869b451 100644 --- a/test/browser.test.js +++ b/test/browser.test.js @@ -7,6 +7,10 @@ var sandbox = require('./helpers/sandbox'); var vm = require('vm'); describe('browser support', function() { + this.timeout(60000); // 60s to give browserify enough time to finish + + beforeEach(sandbox.reset); + it('has API for bundling and executing boot instructions', function(done) { var appDir = path.resolve(__dirname, './fixtures/browser-app'); @@ -37,7 +41,7 @@ function browserifyTestApp(appDir, next) { var bundlePath = sandbox.resolve('browser-app-bundle.js'); var out = fs.createWriteStream(bundlePath); b.bundle({ debug: true }).pipe(out); - + out.on('error', function(err) { return next(err); }); out.on('close', function() { next(null, bundlePath); @@ -68,6 +72,9 @@ function createBrowserLikeContext() { // used by `debug` module document: { documentElement: { style: {} } }, + // used by `debug` module + navigator: { userAgent: 'sandbox' }, + // used by crypto-browserify & friends Int32Array: Int32Array, DataView: DataView, diff --git a/test/compiler.test.js b/test/compiler.test.js index 5b6e450..45e17ec 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -125,24 +125,168 @@ describe('compiler', function() { expect(db).to.have.property('fromJs', true); }); - it('refuses to merge Object properties', function() { + it('merges new Object values', function() { + var objectValue = { key: 'value' }; appdir.createConfigFilesSync(); appdir.writeConfigFileSync('datasources.local.json', { - db: { nested: { key: 'value' } } + db: { nested: objectValue } }); - expect(function() { boot.compile(appdir.PATH); }) - .to.throw(/`nested` is not a value type/); + var instructions = boot.compile(appdir.PATH); + + var db = instructions.dataSources.db; + expect(db).to.have.property('nested'); + expect(db.nested).to.eql(objectValue); }); - it('refuses to merge Array properties', function() { + it('deeply merges Object values', function() { + appdir.createConfigFilesSync({}, { + email: { + transport: { + host: 'localhost' + } + } + }); + + appdir.writeConfigFileSync('datasources.local.json', { + email: { + transport: { + host: 'mail.example.com' + } + } + }); + + var instructions = boot.compile(appdir.PATH); + var email = instructions.dataSources.email; + expect(email.transport.host).to.equal('mail.example.com'); + }); + + it('deeply merges Array values of the same length', function() { + appdir.createConfigFilesSync({}, { + rest: { + operations: [ + { + template: { + method: 'POST', + url: 'http://localhost:12345' + } + } + ] + } + + }); + appdir.writeConfigFileSync('datasources.local.json', { + rest: { + operations: [ + { + template: { + url: 'http://api.example.com' + } + } + ] + } + }); + + var instructions = boot.compile(appdir.PATH); + + var rest = instructions.dataSources.rest; + expect(rest.operations[0].template).to.eql({ + method: 'POST', // the value from datasources.json + url: 'http://api.example.com' // overriden in datasources.local.json + }); + }); + + it('merges Array properties', function() { + var arrayValue = ['value']; appdir.createConfigFilesSync(); appdir.writeConfigFileSync('datasources.local.json', { - db: { nested: ['value'] } + db: { nested: arrayValue } + }); + + var instructions = boot.compile(appdir.PATH); + + var db = instructions.dataSources.db; + expect(db).to.have.property('nested'); + expect(db.nested).to.eql(arrayValue); + }); + + it('refuses to merge Array properties of different length', function() { + appdir.createConfigFilesSync({ + nest: { + array: [] + } + }); + + appdir.writeConfigFileSync('config.local.json', { + nest: { + array: [ + { + key: 'value' + } + ] + } }); expect(function() { boot.compile(appdir.PATH); }) - .to.throw(/`nested` is not a value type/); + .to.throw(/array values of different length.*nest\.array/); + }); + + it('refuses to merge Array of different length in Array', function() { + appdir.createConfigFilesSync({ + key: [[]] + }); + + appdir.writeConfigFileSync('config.local.json', { + key: [['value']] + }); + + expect(function() { boot.compile(appdir.PATH); }) + .to.throw(/array values of different length.*key\[0\]/); + }); + + it('returns full key of an incorrect Array value', function() { + appdir.createConfigFilesSync({ + toplevel: [ + { + nested: [] + } + ] + }); + + appdir.writeConfigFileSync('config.local.json', { + toplevel: [ + { + nested: [ 'value' ] + } + ] + }); + + expect(function() { boot.compile(appdir.PATH); }) + .to.throw(/array values of different length.*toplevel\[0\]\.nested/); + }); + + it('refuses to merge incompatible object properties', function() { + appdir.createConfigFilesSync({ + key: [] + }); + appdir.writeConfigFileSync('config.local.json', { + key: {} + }); + + expect(function() { boot.compile(appdir.PATH); }) + .to.throw(/incompatible types.*key/); + }); + + it('refuses to merge incompatible array items', function() { + appdir.createConfigFilesSync({ + key: [[]] + }); + appdir.writeConfigFileSync('config.local.json', { + key: [{}] + }); + + expect(function() { boot.compile(appdir.PATH); }) + .to.throw(/incompatible types.*key\[0\]/); }); it('merges app configs from multiple files', function() { @@ -179,6 +323,23 @@ describe('compiler', function() { expect(appConfig).to.have.property('fromJs', true); }); + it('supports `appConfigRootDir` option', function() { + appdir.createConfigFilesSync({port:3000}); + + var customDir = path.resolve(appdir.PATH, 'custom'); + fs.mkdirsSync(customDir); + fs.renameSync( + path.resolve(appdir.PATH, 'config.json'), + path.resolve(customDir, 'config.json')); + + var instructions = boot.compile({ + appRootDir: appdir.PATH, + appConfigRootDir: path.resolve(appdir.PATH, 'custom') + }); + + expect(instructions.config).to.have.property('port'); + }); + it('supports `dsRootDir` option', function() { appdir.createConfigFilesSync(); @@ -218,6 +379,28 @@ describe('compiler', function() { var instructions = boot.compile(appdir.PATH); expect(instructions.files.boot).to.eql([initJs]); }); + + it('supports `bootDirs` option', function() { + appdir.createConfigFilesSync(); + var initJs = appdir.writeFileSync('custom-boot/init.js', + 'module.exports = function(app) { app.fnCalled = true; };'); + var instructions = boot.compile({ + appRootDir: appdir.PATH, + bootDirs: [path.dirname(initJs)] + }); + expect(instructions.files.boot).to.eql([initJs]); + }); + + it('supports `bootScripts` option', function() { + appdir.createConfigFilesSync(); + var initJs = appdir.writeFileSync('custom-boot/init.js', + 'module.exports = function(app) { app.fnCalled = true; };'); + var instructions = boot.compile({ + appRootDir: appdir.PATH, + bootScripts: [initJs] + }); + expect(instructions.files.boot).to.eql([initJs]); + }); it('ignores models/ subdirectory', function() { appdir.createConfigFilesSync(); @@ -267,6 +450,31 @@ describe('compiler', function() { sourceFile: path.resolve(appdir.PATH, 'models', 'car.js') }); }); + + it('supports `modelSources` option', function() { + appdir.createConfigFilesSync({}, {}, { + Car: { dataSource: 'db' } + }); + appdir.writeConfigFileSync('custom-models/car.json', { name: 'Car' }); + appdir.writeFileSync('custom-models/car.js', ''); + + var instructions = boot.compile({ + appRootDir: appdir.PATH, + modelSources: ['./custom-models'] + }); + + expect(instructions.models).to.have.length(1); + expect(instructions.models[0]).to.eql({ + name: 'Car', + config: { + dataSource: 'db' + }, + definition: { + name: 'Car' + }, + sourceFile: path.resolve(appdir.PATH, 'custom-models', 'car.js') + }); + }); it('supports `sources` option in `model-config.json`', function() { appdir.createConfigFilesSync({}, {}, { 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