Merge branch 'release/2.1.0' into production

This commit is contained in:
Raymond Feng 2014-10-09 12:23:15 -07:00
commit 676f347f36
14 changed files with 470 additions and 95 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@
*.pid *.pid
*.swp *.swp
*.swo *.swo
*.iml
node_modules node_modules
checkstyle.xml checkstyle.xml
loopback-boot-*.tgz loopback-boot-*.tgz

View File

@ -12,6 +12,7 @@
"newcap": true, "newcap": true,
"nonew": true, "nonew": true,
"sub": true, "sub": true,
"unused": "vars",
"globals": { "globals": {
"describe": true, "describe": true,
"it": true, "it": true,

View File

@ -1,65 +1,24 @@
### Contributing ### ### 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. 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 projects 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 talking to the maintainers, especially if you are going to spend a
lot of time on it. This project is currently maintained by lot of time on it.
[@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).
* Make something better or fix a bug. * 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][]. [Google Javascript Style Guide][].
* [Sign your patches](#signing-patches) to indicate that your are * Sign the [Contributor License Agreement](https://cla.strongloop.com/strongloop/loopback-boot)
making your contribution available under the terms of the
[Contributor License Agreement](#contributor-license-agreement).
* Submit a pull request through Github. * 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 <random@developer.example.org>
```
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 ### ### Contributor License Agreement ###
``` ```
@ -188,7 +147,5 @@ $ git commit -sm "Replace rainbows by unicorns"
inaccurate in any respect. Email us at callback@strongloop.com. 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 [Google Javascript Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
[license]: LICENSE

View File

@ -23,15 +23,54 @@ var addInstructionsToBrowserify = require('./lib/bundler');
* 2. Configures Models from the `model-config.json` file in the application * 2. Configures Models from the `model-config.json` file in the application
* root directory. * 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`, * 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 * If the object has no `appRootDir` property then it sets the current working
* directory as the application root directory. * 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: * 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 * In both cases, the function loads JavaScript files in the
* `/boot` subdirectory of the application root directory with `require()`. * `/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 * @property {String} [appRootDir] Directory to use when loading JSON and
* JavaScript files. * JavaScript files.
* Defaults to the current directory (`process.cwd()`). * 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} [models] Object containing `Model` configurations.
* @property {Object} [dataSources] Object containing `DataSource` definitions. * @property {Object} [dataSources] Object containing `DataSource` definitions.
* @property {String} [modelsRootDir] Directory to use when loading * @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. * `production`; however the applications are free to use any names.
* @property {Array.<String>} [modelSources] List of directories where to look * @property {Array.<String>} [modelSources] List of directories where to look
* for files containing model definitions. * for files containing model definitions.
* @property {Array.<String>} [bootDirs] List of directories where to look
* for boot scripts.
* @property {Array.<String>} [bootScripts] List of script files to execute
* on boot.
* @end * @end
* *
* @header boot(app, [options]) * @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 // backwards compatibility with loopback's app.boot
options.env = options.env || app.get('env'); options.env = options.env || app.get('env');
var instructions = compile(options); var instructions = compile(options);
execute(app, instructions); execute(app, instructions, callback);
}; };
/** /**

View File

@ -28,7 +28,9 @@ module.exports = function compile(options) {
var appRootDir = options.appRootDir = options.appRootDir || process.cwd(); var appRootDir = options.appRootDir = options.appRootDir || process.cwd();
var env = options.env || process.env.NODE_ENV || 'development'; 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); assertIsValidConfig('app', appConfig);
var modelsRootDir = options.modelsRootDir || appRootDir; var modelsRootDir = options.modelsRootDir || appRootDir;
@ -42,12 +44,18 @@ module.exports = function compile(options) {
assertIsValidConfig('data source', dataSourcesConfig); assertIsValidConfig('data source', dataSourcesConfig);
// require directories // 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 || {}; var modelsMeta = modelsConfig._meta || {};
delete modelsConfig._meta; delete modelsConfig._meta;
var modelSources = modelsMeta.sources || ['./models']; var modelSources = options.modelSources || modelsMeta.sources || ['./models'];
var modelInstructions = buildAllModelInstructions( var modelInstructions = buildAllModelInstructions(
modelsRootDir, modelsConfig, modelSources); modelsRootDir, modelsConfig, modelSources);

View File

@ -4,7 +4,7 @@ var path = require('path');
var ConfigLoader = exports; 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} rootDir Directory where to look for files.
* @param {String} env Environment, usually `process.env.NODE_ENV` * @param {String} env Environment, usually `process.env.NODE_ENV`
* @returns {Object} * @returns {Object}
@ -112,7 +112,7 @@ function mergeConfigurations(configObjects, mergeFn) {
function mergeDataSourceConfig(target, config, fileName) { function mergeDataSourceConfig(target, config, fileName) {
for (var ds in target) { for (var ds in target) {
var err = applyCustomConfig(target[ds], config[ds]); var err = mergeObjects(target[ds], config[ds]);
if (err) { if (err) {
throw new Error('Cannot apply ' + fileName + ' to `' + ds + '`: ' + err); throw new Error('Cannot apply ' + fileName + ' to `' + ds + '`: ' + err);
} }
@ -120,23 +120,73 @@ function mergeDataSourceConfig(target, config, fileName) {
} }
function mergeAppConfig(target, config, fileName) { function mergeAppConfig(target, config, fileName) {
var err = applyCustomConfig(target, config); var err = mergeObjects(target, config);
if (err) { if (err) {
throw new Error('Cannot apply ' + fileName + ': ' + err); throw new Error('Cannot apply ' + fileName + ': ' + err);
} }
} }
function applyCustomConfig(target, config) { function mergeObjects(target, config, keyPrefix) {
for (var key in config) { for (var key in config) {
var value = config[key]; var fullKey = keyPrefix ? keyPrefix + '.' + key : key;
if (typeof value === 'object') { var err = mergeSingleItemOrProperty(target, config, key, fullKey);
return 'override for the option `' + key + '` is not a value type.'; if (err) return err;
}
target[key] = value;
} }
return null; // no error 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 * Try to read a config file with .json extension
* @param cwd Dirname of the file * @param cwd Dirname of the file

View File

@ -2,6 +2,7 @@ var assert = require('assert');
var _ = require('underscore'); var _ = require('underscore');
var semver = require('semver'); var semver = require('semver');
var debug = require('debug')('loopback:boot:executor'); var debug = require('debug')('loopback:boot:executor');
var async = require('async');
/** /**
* Execute bootstrap instructions gathered by `boot.compile`. * Execute bootstrap instructions gathered by `boot.compile`.
@ -12,7 +13,7 @@ var debug = require('debug')('loopback:boot:executor');
* @header boot.execute(instructions) * @header boot.execute(instructions)
*/ */
module.exports = function execute(app, instructions) { module.exports = function execute(app, instructions, callback) {
patchAppLoopback(app); patchAppLoopback(app);
assertLoopBackVersion(app); assertLoopBackVersion(app);
@ -24,9 +25,16 @@ module.exports = function execute(app, instructions) {
setupDataSources(app, instructions); setupDataSources(app, instructions);
setupModels(app, instructions); setupModels(app, instructions);
runBootScripts(app, instructions); // Run the boot scripts in series synchronously or asynchronously
// Please note async supports both styles
enableAnonymousSwagger(app, instructions); async.series([
function(done) {
runBootScripts(app, instructions, done);
},
function(done) {
enableAnonymousSwagger(app, instructions);
done();
}], callback);
}; };
function patchAppLoopback(app) { function patchAppLoopback(app) {
@ -176,13 +184,36 @@ function forEachKeyedObject(obj, fn) {
}); });
} }
function runScripts(app, list) { function runScripts(app, list, callback) {
if (!list || !list.length) return; list = list || [];
var functions = [];
list.forEach(function(filepath) { list.forEach(function(filepath) {
debug('Requiring script %s', filepath);
var exports = tryRequire(filepath); var exports = tryRequire(filepath);
if (typeof exports === 'function') if (typeof exports === 'function') {
exports(app); 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) { function tryRequire(modulePath) {
@ -198,8 +229,8 @@ function tryRequire(modulePath) {
} }
} }
function runBootScripts(app, instructions) { function runBootScripts(app, instructions, callback) {
runScripts(app, instructions.files.boot); runScripts(app, instructions.files.boot, callback);
} }
function enableAnonymousSwagger(app, instructions) { function enableAnonymousSwagger(app, instructions) {

View File

@ -1,6 +1,6 @@
{ {
"name": "loopback-boot", "name": "loopback-boot",
"version": "2.0.0", "version": "2.1.0",
"description": "Convention-based bootstrapper for LoopBack applications", "description": "Convention-based bootstrapper for LoopBack applications",
"keywords": [ "keywords": [
"StrongLoop", "StrongLoop",
@ -23,19 +23,21 @@
"url": "https://github.com/strongloop/loopback-boot/blob/master/LICENSE" "url": "https://github.com/strongloop/loopback-boot/blob/master/LICENSE"
}, },
"dependencies": { "dependencies": {
"async": "~0.9.0",
"commondir": "0.0.1", "commondir": "0.0.1",
"debug": "^1.0.4", "debug": "^2.0.0",
"lodash.clonedeep": "^2.4.1", "lodash.clonedeep": "^2.4.1",
"semver": "^2.3.0", "semver": "^2.3.0",
"toposort": "^0.2.10", "toposort": "^0.2.10",
"underscore": "^1.6.0" "underscore": "^1.6.0"
}, },
"devDependencies": { "devDependencies": {
"browserify": "^4.1.8",
"fs-extra": "^0.10.0",
"jshint": "^2.5.6",
"loopback": "^1.5.0", "loopback": "^1.5.0",
"mocha": "^1.19.0", "mocha": "^1.19.0",
"must": "^0.12.0", "must": "^0.12.0",
"supertest": "^0.13.0", "supertest": "^0.13.0"
"fs-extra": "^0.10.0",
"browserify": "^4.1.8"
} }
} }

View File

@ -7,6 +7,10 @@ var sandbox = require('./helpers/sandbox');
var vm = require('vm'); var vm = require('vm');
describe('browser support', function() { 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) { it('has API for bundling and executing boot instructions', function(done) {
var appDir = path.resolve(__dirname, './fixtures/browser-app'); 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 bundlePath = sandbox.resolve('browser-app-bundle.js');
var out = fs.createWriteStream(bundlePath); var out = fs.createWriteStream(bundlePath);
b.bundle({ debug: true }).pipe(out); b.bundle({ debug: true }).pipe(out);
out.on('error', function(err) { return next(err); }); out.on('error', function(err) { return next(err); });
out.on('close', function() { out.on('close', function() {
next(null, bundlePath); next(null, bundlePath);
@ -68,6 +72,9 @@ function createBrowserLikeContext() {
// used by `debug` module // used by `debug` module
document: { documentElement: { style: {} } }, document: { documentElement: { style: {} } },
// used by `debug` module
navigator: { userAgent: 'sandbox' },
// used by crypto-browserify & friends // used by crypto-browserify & friends
Int32Array: Int32Array, Int32Array: Int32Array,
DataView: DataView, DataView: DataView,

View File

@ -125,24 +125,168 @@ describe('compiler', function() {
expect(db).to.have.property('fromJs', true); 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.createConfigFilesSync();
appdir.writeConfigFileSync('datasources.local.json', { appdir.writeConfigFileSync('datasources.local.json', {
db: { nested: { key: 'value' } } db: { nested: objectValue }
}); });
expect(function() { boot.compile(appdir.PATH); }) var instructions = boot.compile(appdir.PATH);
.to.throw(/`nested` is not a value type/);
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.createConfigFilesSync();
appdir.writeConfigFileSync('datasources.local.json', { 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); }) 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() { it('merges app configs from multiple files', function() {
@ -179,6 +323,23 @@ describe('compiler', function() {
expect(appConfig).to.have.property('fromJs', true); 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() { it('supports `dsRootDir` option', function() {
appdir.createConfigFilesSync(); appdir.createConfigFilesSync();
@ -218,6 +379,28 @@ describe('compiler', function() {
var instructions = boot.compile(appdir.PATH); var instructions = boot.compile(appdir.PATH);
expect(instructions.files.boot).to.eql([initJs]); 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() { it('ignores models/ subdirectory', function() {
appdir.createConfigFilesSync(); appdir.createConfigFilesSync();
@ -267,6 +450,31 @@ describe('compiler', function() {
sourceFile: path.resolve(appdir.PATH, 'models', 'car.js') 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() { it('supports `sources` option in `model-config.json`', function() {
appdir.createConfigFilesSync({}, {}, { appdir.createConfigFilesSync({}, {}, {

View File

@ -3,6 +3,7 @@ var path = require('path');
var loopback = require('loopback'); var loopback = require('loopback');
var assert = require('assert'); var assert = require('assert');
var expect = require('must'); var expect = require('must');
var fs = require('fs-extra');
var sandbox = require('./helpers/sandbox'); var sandbox = require('./helpers/sandbox');
var appdir = require('./helpers/appdir'); var appdir = require('./helpers/appdir');
@ -161,13 +162,62 @@ describe('executor', function() {
describe('with boot and models files', function() { describe('with boot and models files', function() {
beforeEach(function() { beforeEach(function() {
process.bootFlags = process.bootFlags || [];
boot.execute(app, simpleAppInstructions()); boot.execute(app, simpleAppInstructions());
}); });
it('should run `boot/*` files', function() { afterEach(function() {
assert(process.loadedFooJS); delete process.bootFlags;
delete process.loadedFooJS;
}); });
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() { describe('with PaaS and npm env variables', function() {
@ -299,5 +349,7 @@ function someInstructions(values) {
} }
function simpleAppInstructions() { 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);
} }

8
test/fixtures/simple-app/boot/bar.js vendored Normal file
View File

@ -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();
});
};

View File

@ -0,0 +1,5 @@
process.bootFlags.push('barSyncLoaded');
module.exports = function(app) {
process.bootFlags.push('barSyncExecuted');
};

View File

@ -1 +1 @@
process.loadedFooJS = true; process.bootFlags.push('fooLoaded');