Merge branch 'release/2.1.0' into production
This commit is contained in:
commit
676f347f36
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 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
|
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
|
|
||||||
|
|
||||||
|
|
55
index.js
55
index.js
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
12
package.json
12
package.json
|
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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');
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
@ -219,6 +380,28 @@ describe('compiler', function() {
|
||||||
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();
|
||||||
appdir.writeFileSync('models/my-model.js', '');
|
appdir.writeFileSync('models/my-model.js', '');
|
||||||
|
@ -268,6 +451,31 @@ describe('compiler', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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({}, {}, {
|
||||||
_meta: {
|
_meta: {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
process.bootFlags.push('barSyncLoaded');
|
||||||
|
module.exports = function(app) {
|
||||||
|
process.bootFlags.push('barSyncExecuted');
|
||||||
|
};
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
process.loadedFooJS = true;
|
process.bootFlags.push('fooLoaded');
|
Loading…
Reference in New Issue