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
|
||||
=========================
|
||||
|
||||
|
|
|
@ -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
|
||||
[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.
|
||||
|
||||
|
|
2
index.js
2
index.js
|
@ -109,6 +109,8 @@ var addInstructionsToBrowserify = require('./lib/bundler');
|
|||
* for files containing model definitions.
|
||||
* @property {Object} [middleware] Middleware configuration to use instead
|
||||
* 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
|
||||
* for boot scripts.
|
||||
* @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) {
|
||||
bundleModelScripts(instructions, bundler);
|
||||
bundleComponentScripts(instructions, bundler);
|
||||
bundleOtherScripts(instructions, bundler);
|
||||
bundleInstructions(instructions, bundler);
|
||||
};
|
||||
|
@ -22,19 +23,27 @@ function bundleOtherScripts(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; })
|
||||
.filter(function(f) { return !!f; });
|
||||
|
||||
var modelToFileMapping = instructions.models
|
||||
var instructionToFileMapping = instructions[type]
|
||||
.map(function(m) { return files.indexOf(m.sourceFile); });
|
||||
|
||||
addScriptsToBundle('models', files, bundler);
|
||||
addScriptsToBundle(type, files, bundler);
|
||||
|
||||
// Update `sourceFile` properties with the new paths
|
||||
modelToFileMapping.forEach(function(fileIx, modelIx) {
|
||||
instructionToFileMapping.forEach(function(fileIx, sourceIx) {
|
||||
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 =
|
||||
buildMiddlewareInstructions(middlewareRootDir, middlewareConfig);
|
||||
|
||||
var componentRootDir = appRootDir; // not configurable yet
|
||||
var componentConfig = options.components ||
|
||||
ConfigLoader.loadComponents(componentRootDir, env);
|
||||
var componentInstructions =
|
||||
buildComponentInstructions(componentRootDir, componentConfig);
|
||||
|
||||
// require directories
|
||||
var bootDirs = options.bootDirs || []; // precedence
|
||||
bootDirs = bootDirs.concat(path.join(appRootDir, 'boot'));
|
||||
|
@ -81,6 +87,7 @@ module.exports = function compile(options) {
|
|||
dataSources: dataSourcesConfig,
|
||||
models: modelInstructions,
|
||||
middleware: middlewareInstructions,
|
||||
components: componentInstructions,
|
||||
files: {
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 --*/
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
for (var key in config) {
|
||||
var fullKey = keyPrefix ? keyPrefix + '.' + key : key;
|
||||
|
|
|
@ -16,6 +16,10 @@ var path = require('path');
|
|||
*/
|
||||
|
||||
module.exports = function execute(app, instructions, callback) {
|
||||
callback = callback || function() {};
|
||||
|
||||
app.booting = true;
|
||||
|
||||
patchAppLoopback(app);
|
||||
assertLoopBackVersion(app);
|
||||
|
||||
|
@ -27,6 +31,7 @@ module.exports = function execute(app, instructions, callback) {
|
|||
setupDataSources(app, instructions);
|
||||
setupModels(app, instructions);
|
||||
setupMiddleware(app, instructions);
|
||||
setupComponents(app, instructions);
|
||||
|
||||
// Run the boot scripts in series synchronously or asynchronously
|
||||
// Please note async supports both styles
|
||||
|
@ -37,7 +42,15 @@ module.exports = function execute(app, instructions, callback) {
|
|||
function(done) {
|
||||
enableAnonymousSwagger(app, instructions);
|
||||
done();
|
||||
}], callback);
|
||||
}], function(err) {
|
||||
app.booting = false;
|
||||
|
||||
if (err) return callback(err);
|
||||
|
||||
app.emit('booted');
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
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) {
|
||||
runScripts(app, instructions.files.boot, callback);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loopback-boot",
|
||||
"version": "2.5.2",
|
||||
"version": "2.6.0",
|
||||
"description": "Convention-based bootstrapper for LoopBack applications",
|
||||
"keywords": [
|
||||
"StrongLoop",
|
||||
|
|
|
@ -50,6 +50,10 @@ describe('browser support', function() {
|
|||
expect(app.models.Customer.settings)
|
||||
.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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
boot.execute(app, dummyInstructions);
|
||||
assert(app.models);
|
||||
|
@ -414,6 +440,22 @@ describe('executor', function() {
|
|||
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) {
|
||||
|
@ -439,6 +481,7 @@ function someInstructions(values) {
|
|||
models: values.models || [],
|
||||
dataSources: values.dataSources || { db: { connector: 'memory' } },
|
||||
middleware: values.middleware || { phases: [], middleware: [] },
|
||||
components: values.components || [],
|
||||
files: {
|
||||
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