Merge branch 'release/2.6.0' into production
This commit is contained in:
commit
33b3729b62
10
CHANGES.md
10
CHANGES.md
|
@ -1,3 +1,13 @@
|
||||||
|
2015-01-08, Version 2.6.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Add "booting" flag and emit "booted" event (Simon Ho)
|
||||||
|
|
||||||
|
* Configure components via `component-config.json` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix bad CLA URL in CONTRIBUTING.md (Ryan Graham)
|
||||||
|
|
||||||
|
|
||||||
2014-12-19, Version 2.5.2
|
2014-12-19, Version 2.5.2
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ Contributing to `loopback-boot` is easy. In a few simple steps:
|
||||||
* Adhere to code style outlined in the [Google C++ Style Guide][] and
|
* Adhere to code style outlined in the [Google C++ Style Guide][] and
|
||||||
[Google Javascript Style Guide][].
|
[Google Javascript Style Guide][].
|
||||||
|
|
||||||
* Sign the [Contributor License Agreement](https://cla.strongloop.com/strongloop/loopback-boot)
|
* Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback-boot)
|
||||||
|
|
||||||
* Submit a pull request through Github.
|
* Submit a pull request through Github.
|
||||||
|
|
||||||
|
|
2
index.js
2
index.js
|
@ -109,6 +109,8 @@ var addInstructionsToBrowserify = require('./lib/bundler');
|
||||||
* for files containing model definitions.
|
* for files containing model definitions.
|
||||||
* @property {Object} [middleware] Middleware configuration to use instead
|
* @property {Object} [middleware] Middleware configuration to use instead
|
||||||
* of `{appRootDir}/middleware.json`
|
* of `{appRootDir}/middleware.json`
|
||||||
|
* @property {Object} [components] Component configuration to use instead
|
||||||
|
* of `{appRootDir}/component-config.json`
|
||||||
* @property {Array.<String>} [bootDirs] List of directories where to look
|
* @property {Array.<String>} [bootDirs] List of directories where to look
|
||||||
* for boot scripts.
|
* for boot scripts.
|
||||||
* @property {Array.<String>} [bootScripts] List of script files to execute
|
* @property {Array.<String>} [bootScripts] List of script files to execute
|
||||||
|
|
|
@ -11,6 +11,7 @@ var cloneDeep = require('lodash').cloneDeep;
|
||||||
|
|
||||||
module.exports = function addInstructionsToBrowserify(instructions, bundler) {
|
module.exports = function addInstructionsToBrowserify(instructions, bundler) {
|
||||||
bundleModelScripts(instructions, bundler);
|
bundleModelScripts(instructions, bundler);
|
||||||
|
bundleComponentScripts(instructions, bundler);
|
||||||
bundleOtherScripts(instructions, bundler);
|
bundleOtherScripts(instructions, bundler);
|
||||||
bundleInstructions(instructions, bundler);
|
bundleInstructions(instructions, bundler);
|
||||||
};
|
};
|
||||||
|
@ -22,19 +23,27 @@ function bundleOtherScripts(instructions, bundler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function bundleModelScripts(instructions, bundler) {
|
function bundleModelScripts(instructions, bundler) {
|
||||||
var files = instructions.models
|
bundleSourceFiles(instructions, 'models', bundler);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bundleComponentScripts(instructions, bundler) {
|
||||||
|
bundleSourceFiles(instructions, 'components', bundler);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bundleSourceFiles(instructions, type, bundler) {
|
||||||
|
var files = instructions[type]
|
||||||
.map(function(m) { return m.sourceFile; })
|
.map(function(m) { return m.sourceFile; })
|
||||||
.filter(function(f) { return !!f; });
|
.filter(function(f) { return !!f; });
|
||||||
|
|
||||||
var modelToFileMapping = instructions.models
|
var instructionToFileMapping = instructions[type]
|
||||||
.map(function(m) { return files.indexOf(m.sourceFile); });
|
.map(function(m) { return files.indexOf(m.sourceFile); });
|
||||||
|
|
||||||
addScriptsToBundle('models', files, bundler);
|
addScriptsToBundle(type, files, bundler);
|
||||||
|
|
||||||
// Update `sourceFile` properties with the new paths
|
// Update `sourceFile` properties with the new paths
|
||||||
modelToFileMapping.forEach(function(fileIx, modelIx) {
|
instructionToFileMapping.forEach(function(fileIx, sourceIx) {
|
||||||
if (fileIx === -1) return;
|
if (fileIx === -1) return;
|
||||||
instructions.models[modelIx].sourceFile = files[fileIx];
|
instructions[type][sourceIx].sourceFile = files[fileIx];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,12 @@ module.exports = function compile(options) {
|
||||||
var middlewareInstructions =
|
var middlewareInstructions =
|
||||||
buildMiddlewareInstructions(middlewareRootDir, middlewareConfig);
|
buildMiddlewareInstructions(middlewareRootDir, middlewareConfig);
|
||||||
|
|
||||||
|
var componentRootDir = appRootDir; // not configurable yet
|
||||||
|
var componentConfig = options.components ||
|
||||||
|
ConfigLoader.loadComponents(componentRootDir, env);
|
||||||
|
var componentInstructions =
|
||||||
|
buildComponentInstructions(componentRootDir, componentConfig);
|
||||||
|
|
||||||
// require directories
|
// require directories
|
||||||
var bootDirs = options.bootDirs || []; // precedence
|
var bootDirs = options.bootDirs || []; // precedence
|
||||||
bootDirs = bootDirs.concat(path.join(appRootDir, 'boot'));
|
bootDirs = bootDirs.concat(path.join(appRootDir, 'boot'));
|
||||||
|
@ -81,6 +87,7 @@ module.exports = function compile(options) {
|
||||||
dataSources: dataSourcesConfig,
|
dataSources: dataSourcesConfig,
|
||||||
models: modelInstructions,
|
models: modelInstructions,
|
||||||
middleware: middlewareInstructions,
|
middleware: middlewareInstructions,
|
||||||
|
components: componentInstructions,
|
||||||
files: {
|
files: {
|
||||||
boot: bootScripts
|
boot: bootScripts
|
||||||
}
|
}
|
||||||
|
@ -471,3 +478,20 @@ function resolveMiddlewareParams(rootDir, params) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildComponentInstructions(rootDir, componentConfig) {
|
||||||
|
return Object.keys(componentConfig).map(function(name) {
|
||||||
|
var sourceFile;
|
||||||
|
if (name.indexOf('./') === 0 || name.indexOf('../') === 0) {
|
||||||
|
// Relative path
|
||||||
|
sourceFile = path.resolve(rootDir, name);
|
||||||
|
} else {
|
||||||
|
sourceFile = require.resolve(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceFile: sourceFile,
|
||||||
|
config: componentConfig[name]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -44,6 +44,16 @@ ConfigLoader.loadMiddleware = function(rootDir, env) {
|
||||||
return loadNamed(rootDir, env, 'middleware', mergeMiddlewareConfig);
|
return loadNamed(rootDir, env, 'middleware', mergeMiddlewareConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load component config from `component-config.json` and friends.
|
||||||
|
* @param {String} rootDir Directory where to look for files.
|
||||||
|
* @param {String} env Environment, usually `process.env.NODE_ENV`
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
ConfigLoader.loadComponents = function(rootDir, env) {
|
||||||
|
return loadNamed(rootDir, env, 'component-config', mergeComponentConfig);
|
||||||
|
};
|
||||||
|
|
||||||
/*-- Implementation --*/
|
/*-- Implementation --*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -162,6 +172,15 @@ function mergePhaseConfig(target, config, phase) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeComponentConfig(target, config, fileName) {
|
||||||
|
for (var c in target) {
|
||||||
|
var err = mergeObjects(target[c], config[c]);
|
||||||
|
if (err) {
|
||||||
|
throw new Error('Cannot apply ' + fileName + ' to `' + c + '`: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function mergeObjects(target, config, keyPrefix) {
|
function mergeObjects(target, config, keyPrefix) {
|
||||||
for (var key in config) {
|
for (var key in config) {
|
||||||
var fullKey = keyPrefix ? keyPrefix + '.' + key : key;
|
var fullKey = keyPrefix ? keyPrefix + '.' + key : key;
|
||||||
|
|
|
@ -16,6 +16,10 @@ var path = require('path');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = function execute(app, instructions, callback) {
|
module.exports = function execute(app, instructions, callback) {
|
||||||
|
callback = callback || function() {};
|
||||||
|
|
||||||
|
app.booting = true;
|
||||||
|
|
||||||
patchAppLoopback(app);
|
patchAppLoopback(app);
|
||||||
assertLoopBackVersion(app);
|
assertLoopBackVersion(app);
|
||||||
|
|
||||||
|
@ -27,6 +31,7 @@ module.exports = function execute(app, instructions, callback) {
|
||||||
setupDataSources(app, instructions);
|
setupDataSources(app, instructions);
|
||||||
setupModels(app, instructions);
|
setupModels(app, instructions);
|
||||||
setupMiddleware(app, instructions);
|
setupMiddleware(app, instructions);
|
||||||
|
setupComponents(app, instructions);
|
||||||
|
|
||||||
// Run the boot scripts in series synchronously or asynchronously
|
// Run the boot scripts in series synchronously or asynchronously
|
||||||
// Please note async supports both styles
|
// Please note async supports both styles
|
||||||
|
@ -37,7 +42,15 @@ module.exports = function execute(app, instructions, callback) {
|
||||||
function(done) {
|
function(done) {
|
||||||
enableAnonymousSwagger(app, instructions);
|
enableAnonymousSwagger(app, instructions);
|
||||||
done();
|
done();
|
||||||
}], callback);
|
}], function(err) {
|
||||||
|
app.booting = false;
|
||||||
|
|
||||||
|
if (err) return callback(err);
|
||||||
|
|
||||||
|
app.emit('booted');
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function patchAppLoopback(app) {
|
function patchAppLoopback(app) {
|
||||||
|
@ -282,6 +295,14 @@ function setupMiddleware(app, instructions) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupComponents(app, instructions) {
|
||||||
|
instructions.components.forEach(function(data) {
|
||||||
|
debug('Configuring component %j', data.sourceFile);
|
||||||
|
var configFn = require(data.sourceFile);
|
||||||
|
configFn(app, data.config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function runBootScripts(app, instructions, callback) {
|
function runBootScripts(app, instructions, callback) {
|
||||||
runScripts(app, instructions.files.boot, callback);
|
runScripts(app, instructions.files.boot, callback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-boot",
|
"name": "loopback-boot",
|
||||||
"version": "2.5.2",
|
"version": "2.6.0",
|
||||||
"description": "Convention-based bootstrapper for LoopBack applications",
|
"description": "Convention-based bootstrapper for LoopBack applications",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"StrongLoop",
|
"StrongLoop",
|
||||||
|
|
|
@ -50,6 +50,10 @@ describe('browser support', function() {
|
||||||
expect(app.models.Customer.settings)
|
expect(app.models.Customer.settings)
|
||||||
.to.have.property('_customized', 'Customer');
|
.to.have.property('_customized', 'Customer');
|
||||||
|
|
||||||
|
// configured in fixtures/browser-app/component-config.json
|
||||||
|
// and fixtures/browser-app/components/dummy-component.js
|
||||||
|
expect(app.dummyComponentOptions).to.eql({ option: 'value' });
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1038,6 +1038,35 @@ describe('compiler', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('for components', function() {
|
||||||
|
it('loads component configs from multiple files', function() {
|
||||||
|
appdir.createConfigFilesSync();
|
||||||
|
appdir.writeConfigFileSync('component-config.json', {
|
||||||
|
debug: { option: 'value' }
|
||||||
|
});
|
||||||
|
appdir.writeConfigFileSync('component-config.local.json', {
|
||||||
|
debug: { local: 'applied' }
|
||||||
|
});
|
||||||
|
|
||||||
|
var env = process.env.NODE_ENV || 'development';
|
||||||
|
appdir.writeConfigFileSync('component-config.' + env + '.json', {
|
||||||
|
debug: { env: 'applied' }
|
||||||
|
});
|
||||||
|
|
||||||
|
var instructions = boot.compile(appdir.PATH);
|
||||||
|
|
||||||
|
var component = instructions.components[0];
|
||||||
|
expect(component).to.eql({
|
||||||
|
sourceFile: require.resolve('debug'),
|
||||||
|
config: {
|
||||||
|
option: 'value',
|
||||||
|
local: 'applied',
|
||||||
|
env: 'applied'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getNameProperty(obj) {
|
function getNameProperty(obj) {
|
||||||
|
|
|
@ -52,6 +52,32 @@ describe('executor', function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when booting', function() {
|
||||||
|
it('should set the booting status', function(done) {
|
||||||
|
expect(app.booting).to.be.undefined();
|
||||||
|
boot.execute(app, dummyInstructions, function(err) {
|
||||||
|
expect(err).to.be.undefined();
|
||||||
|
expect(app.booting).to.be.false();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit the `booted` event', function(done) {
|
||||||
|
app.on('booted', function() {
|
||||||
|
// This test fails with a timeout when the `booted` event has not been
|
||||||
|
// emitted correctly
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
boot.execute(app, dummyInstructions, function(err) {
|
||||||
|
expect(err).to.be.undefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work when called synchronously', function() {
|
||||||
|
boot.execute(app, dummyInstructions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('configures models', function() {
|
it('configures models', function() {
|
||||||
boot.execute(app, dummyInstructions);
|
boot.execute(app, dummyInstructions);
|
||||||
assert(app.models);
|
assert(app.models);
|
||||||
|
@ -414,6 +440,22 @@ describe('executor', function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('configures components', function() {
|
||||||
|
appdir.writeConfigFileSync('component-config.json', {
|
||||||
|
'./components/test-component': {
|
||||||
|
option: 'value'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
appdir.writeFileSync('components/test-component/index.js',
|
||||||
|
'module.exports = ' +
|
||||||
|
'function(app, options) { app.componentOptions = options; }');
|
||||||
|
|
||||||
|
boot(app, appdir.PATH);
|
||||||
|
|
||||||
|
expect(app.componentOptions).to.eql({ option: 'value' });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function assertValidDataSource(dataSource) {
|
function assertValidDataSource(dataSource) {
|
||||||
|
@ -439,6 +481,7 @@ function someInstructions(values) {
|
||||||
models: values.models || [],
|
models: values.models || [],
|
||||||
dataSources: values.dataSources || { db: { connector: 'memory' } },
|
dataSources: values.dataSources || { db: { connector: 'memory' } },
|
||||||
middleware: values.middleware || { phases: [], middleware: [] },
|
middleware: values.middleware || { phases: [], middleware: [] },
|
||||||
|
components: values.components || [],
|
||||||
files: {
|
files: {
|
||||||
boot: []
|
boot: []
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"./components/dummy-component": {
|
||||||
|
"option": "value"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = function(app, options) {
|
||||||
|
app.dummyComponentOptions = options;
|
||||||
|
};
|
Loading…
Reference in New Issue