Merge branch 'release/2.4.0' into production
This commit is contained in:
commit
d3145f5745
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"preset": "google",
|
||||||
|
"requireCurlyBraces": [
|
||||||
|
"do",
|
||||||
|
"try",
|
||||||
|
"catch"
|
||||||
|
],
|
||||||
|
"disallowSpacesInsideObjectBrackets": null,
|
||||||
|
"requireSpaceAfterLineComment": true,
|
||||||
|
"maximumLineLength": {
|
||||||
|
"value": 80,
|
||||||
|
"allowRegex": true
|
||||||
|
},
|
||||||
|
"validateJSDoc": {
|
||||||
|
"checkParamNames": false,
|
||||||
|
"checkRedundantParams": false,
|
||||||
|
"requireParamTypes": true
|
||||||
|
},
|
||||||
|
"excludeFiles": [
|
||||||
|
"node_modules/**",
|
||||||
|
"coverage/**",
|
||||||
|
"test/sandbox/**"
|
||||||
|
]
|
||||||
|
}
|
15
.jshintrc
15
.jshintrc
|
@ -1,24 +1,21 @@
|
||||||
{
|
{
|
||||||
"node": true,
|
"node": true,
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"camelcase" : true,
|
|
||||||
"eqnull" : true,
|
"eqnull" : true,
|
||||||
"indent": 2,
|
"indent": 2,
|
||||||
"undef": true,
|
"undef": true,
|
||||||
"unused": true,
|
"unused": true,
|
||||||
"quotmark": "single",
|
"quotmark": "single",
|
||||||
"maxlen": 80,
|
|
||||||
"trailing": true,
|
|
||||||
"newcap": true,
|
"newcap": true,
|
||||||
"nonew": true,
|
"nonew": true,
|
||||||
"sub": true,
|
"sub": true,
|
||||||
"unused": "vars",
|
"unused": "vars",
|
||||||
"globals": {
|
"globals": {
|
||||||
"describe": true,
|
"describe": false,
|
||||||
"it": true,
|
"it": false,
|
||||||
"before": true,
|
"before": false,
|
||||||
"beforeEach": true,
|
"beforeEach": false,
|
||||||
"after": true,
|
"after": false,
|
||||||
"afterEach": true
|
"afterEach": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
200
CHANGES.md
200
CHANGES.md
|
@ -1,14 +1,194 @@
|
||||||
## Changes in version 1.0
|
2014-11-27, Version 2.4.0
|
||||||
|
=========================
|
||||||
|
|
||||||
- New options: `modelsRootDir`, `dsRootDir`
|
* Implement shorthand notation for middleware paths (Raymond Feng)
|
||||||
|
|
||||||
- Load configuration from files, support dynamic (scripted) options
|
* Load middleware and phases from `middleware.json` (Miroslav Bajtoš)
|
||||||
|
|
||||||
```sh
|
* Add jscs style check, fix violations found (Miroslav Bajtoš)
|
||||||
app.json, app.local.*, app.{env}.*
|
|
||||||
datasources.json, datasources.local.*, datasources.{env}.*
|
|
||||||
```
|
|
||||||
|
|
||||||
- Scripts in `models/` and `boot/` can export `function(app)`,
|
* Clean up .jshintrc (Miroslav Bajtoš)
|
||||||
this function is then called by the bootstrapper. The existing code
|
|
||||||
using `var app = require('../app')` will continue to work.
|
* Use `chai` instead of `must` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2014-11-10, Version 2.3.1
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Bump version (Raymond Feng)
|
||||||
|
|
||||||
|
* Fix the test for built-in models on Windows (Raymond Feng)
|
||||||
|
|
||||||
|
* Fix jsdoc (Raymond Feng)
|
||||||
|
|
||||||
|
|
||||||
|
2014-10-27, Version 2.3.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* compiler: fix coding style violations (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* support coffee-script models and client code (bitmage)
|
||||||
|
|
||||||
|
|
||||||
|
2014-10-22, Version 2.2.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* compiler: support module-relative model sources (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Skip definitions of built-in loopback models (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* package: update dependency versions (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Use loopback 2.x in unit tests. (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2014-10-09, Version 2.1.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Bump version (Raymond Feng)
|
||||||
|
|
||||||
|
* Add support for async boot scripts (Raymond Feng)
|
||||||
|
|
||||||
|
* Clean up jsdoc comments. (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Custom rootDir for app config (johnsoftek)
|
||||||
|
|
||||||
|
* compiler: improve merging of Arrays and Objects (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* config-loader: deeply merge Array and Object vals (Shelby Sanders)
|
||||||
|
|
||||||
|
* gitignore: add Idea's *.iml files (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* package: Add `jshint` to `devDependencies` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Update contribution guidelines (Ryan Graham)
|
||||||
|
|
||||||
|
* test: ensure sandbox dir is present (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: add `global.navigator` for browser tests (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: increase timeout for browserify (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* index: fix jshint error (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* documentation fix (Alex)
|
||||||
|
|
||||||
|
* Fix typo (Fabien Franzen)
|
||||||
|
|
||||||
|
* Implemented modelSources, bootDirs and bootScripts options (Fabien Franzen)
|
||||||
|
|
||||||
|
|
||||||
|
2014-07-22, Version 2.0.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* executor: remove `Base` arg from model function (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* package: update dependency versions (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2014-07-17, Version v2.0.0-beta3
|
||||||
|
================================
|
||||||
|
|
||||||
|
* v2.0.0-beta3 (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* compiler: return a clone of instructions (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: export Int32Array and DataView for browser (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* v2.0.0-beta2 (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Rename `models.json` to `model-config.json` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Remove non-API docs. (Rand McKinney)
|
||||||
|
|
||||||
|
* 2.0.0-beta1 (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: fix jshint warnings (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* compiler: fix references to loopback (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Rename `app.json` to `config.json` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* compiler: Sort models topologically (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* executor: Split model boot into two phases (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* compiler: Move model-sources cfg to models.json (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* package: Bump up the version to 2.0.0-dev (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Rework model configuration (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Remove auto-attach. (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Change models.json to configure existing models (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2014-07-17, Version 1.1.1
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* compiler: return a clone of instructions (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Remove README from API docs (Rand McKinney)
|
||||||
|
|
||||||
|
|
||||||
|
2014-07-17, Version 2.0.0-beta2
|
||||||
|
===============================
|
||||||
|
|
||||||
|
* test: export Int32Array and DataView for browser (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* v2.0.0-beta2 (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Rename `models.json` to `model-config.json` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Remove non-API docs. (Rand McKinney)
|
||||||
|
|
||||||
|
|
||||||
|
2014-06-26, Version 2.0.0-beta1
|
||||||
|
===============================
|
||||||
|
|
||||||
|
* 2.0.0-beta1 (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: fix jshint warnings (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* compiler: fix references to loopback (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Rename `app.json` to `config.json` (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* compiler: Sort models topologically (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* executor: Split model boot into two phases (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* compiler: Move model-sources cfg to models.json (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* package: Bump up the version to 2.0.0-dev (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Rework model configuration (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Remove auto-attach. (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Change models.json to configure existing models (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2014-06-26, Version 1.1.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* docs: move hand-written content to README.md (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* executor: remove direct reference to loopback (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Update link to doc (Rand McKinney)
|
||||||
|
|
||||||
|
* package: Fix repository url (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Drop peer dep on loopback; add a runtime check (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Wrap too long lines (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Add disclaimer to JSDoc and small correction. (crandmck)
|
||||||
|
|
||||||
|
|
||||||
|
2014-06-05, Version 1.0.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* First release!
|
||||||
|
|
4
index.js
4
index.js
|
@ -107,6 +107,8 @@ 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 {Object} [middleware] Middleware configuration to use instead
|
||||||
|
* of `{appRootDir}/middleware.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
|
||||||
|
@ -136,7 +138,7 @@ exports.compileToBrowserify = function(options, bundler) {
|
||||||
addInstructionsToBrowserify(compile(options), bundler);
|
addInstructionsToBrowserify(compile(options), bundler);
|
||||||
};
|
};
|
||||||
|
|
||||||
//-- undocumented low-level API --//
|
/*-- undocumented low-level API --*/
|
||||||
|
|
||||||
exports.ConfigLoader = ConfigLoader;
|
exports.ConfigLoader = ConfigLoader;
|
||||||
exports.compile = compile;
|
exports.compile = compile;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var commondir = require('commondir');
|
var commondir = require('commondir');
|
||||||
|
var cloneDeep = require('lodash.clonedeep');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add boot instructions to a browserify bundler.
|
* Add boot instructions to a browserify bundler.
|
||||||
|
@ -60,6 +61,17 @@ function addScriptsToBundle(name, list, bundler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function bundleInstructions(instructions, bundler) {
|
function bundleInstructions(instructions, bundler) {
|
||||||
|
instructions = cloneDeep(instructions);
|
||||||
|
|
||||||
|
var hasMiddleware = instructions.middleware.phases.length ||
|
||||||
|
instructions.middleware.middleware.length;
|
||||||
|
if (hasMiddleware) {
|
||||||
|
console.warn(
|
||||||
|
'Discarding middleware instructions,' +
|
||||||
|
' loopback client does not support middleware.');
|
||||||
|
}
|
||||||
|
delete instructions.middleware;
|
||||||
|
|
||||||
var instructionsString = JSON.stringify(instructions, null, 2);
|
var instructionsString = JSON.stringify(instructions, null, 2);
|
||||||
|
|
||||||
/* The following code does not work due to a bug in browserify
|
/* The following code does not work due to a bug in browserify
|
||||||
|
|
122
lib/compiler.js
122
lib/compiler.js
|
@ -22,7 +22,7 @@ var Module = require('module');
|
||||||
module.exports = function compile(options) {
|
module.exports = function compile(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
if(typeof options === 'string') {
|
if (typeof options === 'string') {
|
||||||
options = { appRootDir: options };
|
options = { appRootDir: options };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,14 @@ module.exports = function compile(options) {
|
||||||
ConfigLoader.loadDataSources(dsRootDir, env);
|
ConfigLoader.loadDataSources(dsRootDir, env);
|
||||||
assertIsValidConfig('data source', dataSourcesConfig);
|
assertIsValidConfig('data source', dataSourcesConfig);
|
||||||
|
|
||||||
|
// not configurable yet
|
||||||
|
var middlewareRootDir = appRootDir;
|
||||||
|
|
||||||
|
var middlewareConfig = options.middleware ||
|
||||||
|
ConfigLoader.loadMiddleware(middlewareRootDir, env);
|
||||||
|
var middlewareInstructions =
|
||||||
|
buildMiddlewareInstructions(middlewareRootDir, middlewareConfig);
|
||||||
|
|
||||||
// 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'));
|
||||||
|
@ -67,6 +75,7 @@ module.exports = function compile(options) {
|
||||||
config: appConfig,
|
config: appConfig,
|
||||||
dataSources: dataSourcesConfig,
|
dataSources: dataSourcesConfig,
|
||||||
models: modelInstructions,
|
models: modelInstructions,
|
||||||
|
middleware: middlewareInstructions,
|
||||||
files: {
|
files: {
|
||||||
boot: bootScripts
|
boot: bootScripts
|
||||||
}
|
}
|
||||||
|
@ -74,7 +83,7 @@ module.exports = function compile(options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function assertIsValidConfig(name, config) {
|
function assertIsValidConfig(name, config) {
|
||||||
if(config) {
|
if (config) {
|
||||||
assert(typeof config === 'object',
|
assert(typeof config === 'object',
|
||||||
name + ' config must be a valid JSON object');
|
name + ' config must be a valid JSON object');
|
||||||
}
|
}
|
||||||
|
@ -143,7 +152,7 @@ function findScripts(dir) {
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
path.join(require.resolve(filepath));
|
path.join(require.resolve(filepath));
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
debug('Skipping directory %s - %s', filepath, err.code || err);
|
debug('Skipping directory %s - %s', filepath, err.code || err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,7 +164,7 @@ function findScripts(dir) {
|
||||||
function tryReadDir() {
|
function tryReadDir() {
|
||||||
try {
|
try {
|
||||||
return fs.readdirSync.apply(fs, arguments);
|
return fs.readdirSync.apply(fs, arguments);
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -309,7 +318,9 @@ function loadModelDefinition(rootDir, jsonFile, allFiles) {
|
||||||
var basename = path.basename(jsonFile, path.extname(jsonFile));
|
var basename = path.basename(jsonFile, path.extname(jsonFile));
|
||||||
|
|
||||||
// find a matching file with a supported extension like `.js` or `.coffee`
|
// find a matching file with a supported extension like `.js` or `.coffee`
|
||||||
var base, ext, validFileType;
|
var base;
|
||||||
|
var ext;
|
||||||
|
var validFileType;
|
||||||
var sourceFile = allFiles
|
var sourceFile = allFiles
|
||||||
.filter(function(f) {
|
.filter(function(f) {
|
||||||
ext = path.extname(f);
|
ext = path.extname(f);
|
||||||
|
@ -336,3 +347,104 @@ function loadModelDefinition(rootDir, jsonFile, allFiles) {
|
||||||
sourceFile: sourceFile
|
sourceFile: sourceFile
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildMiddlewareInstructions(rootDir, config) {
|
||||||
|
var phasesNames = Object.keys(config);
|
||||||
|
var middlewareList = [];
|
||||||
|
phasesNames.forEach(function(phase) {
|
||||||
|
var phaseConfig = config[phase];
|
||||||
|
Object.keys(phaseConfig).forEach(function(middleware) {
|
||||||
|
var allConfigs = phaseConfig[middleware];
|
||||||
|
if (!Array.isArray(allConfigs))
|
||||||
|
allConfigs = [allConfigs];
|
||||||
|
|
||||||
|
allConfigs.forEach(function(config) {
|
||||||
|
var resolved = resolveMiddlewarePath(rootDir, middleware);
|
||||||
|
|
||||||
|
var middlewareConfig = cloneDeep(config);
|
||||||
|
middlewareConfig.phase = phase;
|
||||||
|
|
||||||
|
var item = {
|
||||||
|
sourceFile: resolved.sourceFile,
|
||||||
|
config: middlewareConfig
|
||||||
|
};
|
||||||
|
if (resolved.fragment) {
|
||||||
|
item.fragment = resolved.fragment;
|
||||||
|
}
|
||||||
|
middlewareList.push(item);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var flattenedPhaseNames = phasesNames
|
||||||
|
.map(function getBaseName(name) {
|
||||||
|
return name.replace(/:[^:]+$/, '');
|
||||||
|
})
|
||||||
|
.filter(function differsFromPreviousItem(value, ix, source) {
|
||||||
|
// Skip duplicate entries. That happens when
|
||||||
|
// `name:before` and `name:after` are both translated to `name`
|
||||||
|
return ix === 0 || value !== source[ix - 1];
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
phases: flattenedPhaseNames,
|
||||||
|
middleware: middlewareList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveMiddlewarePath(rootDir, middleware) {
|
||||||
|
var resolved = {};
|
||||||
|
|
||||||
|
var segments = middleware.split('#');
|
||||||
|
var pathName = segments[0];
|
||||||
|
var fragment = segments[1];
|
||||||
|
|
||||||
|
if (fragment) {
|
||||||
|
resolved.fragment = fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathName.indexOf('./') === 0 || pathName.indexOf('../') === 0) {
|
||||||
|
// Relative path
|
||||||
|
pathName = path.resolve(rootDir, pathName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fragment) {
|
||||||
|
resolved.sourceFile = require.resolve(pathName);
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
var err;
|
||||||
|
|
||||||
|
// Try to require the module and check if <module>.<fragment> is a valid
|
||||||
|
// function
|
||||||
|
var m = require(pathName);
|
||||||
|
if (typeof m[fragment] === 'function') {
|
||||||
|
resolved.sourceFile = require.resolve(pathName);
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* module/server/middleware/fragment
|
||||||
|
* module/middleware/fragment
|
||||||
|
*/
|
||||||
|
var candidates = [
|
||||||
|
pathName + '/server/middleware/' + fragment,
|
||||||
|
pathName + '/middleware/' + fragment,
|
||||||
|
// TODO: [rfeng] Should we support the following flavors?
|
||||||
|
// pathName + '/lib/' + fragment,
|
||||||
|
// pathName + '/' + fragment
|
||||||
|
];
|
||||||
|
|
||||||
|
for (var ix in candidates) {
|
||||||
|
try {
|
||||||
|
resolved.sourceFile = require.resolve(candidates[ix]);
|
||||||
|
delete resolved.fragment;
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// Report the error for the first candidate when no candidate matches
|
||||||
|
if (!err) err = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,16 @@ ConfigLoader.loadModels = function(rootDir, env) {
|
||||||
return tryReadJsonConfig(rootDir, 'model-config') || {};
|
return tryReadJsonConfig(rootDir, 'model-config') || {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load middleware config from `middleware.json` and friends.
|
||||||
|
* @param {String} rootDir Directory where to look for files.
|
||||||
|
* @param {String} env Environment, usually `process.env.NODE_ENV`
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
ConfigLoader.loadMiddleware = function(rootDir, env) {
|
||||||
|
return loadNamed(rootDir, env, 'middleware', mergeMiddlewareConfig);
|
||||||
|
};
|
||||||
|
|
||||||
/*-- Implementation --*/
|
/*-- Implementation --*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,7 +113,7 @@ function loadConfigFiles(files) {
|
||||||
*/
|
*/
|
||||||
function mergeConfigurations(configObjects, mergeFn) {
|
function mergeConfigurations(configObjects, mergeFn) {
|
||||||
var result = configObjects.shift() || {};
|
var result = configObjects.shift() || {};
|
||||||
while(configObjects.length) {
|
while (configObjects.length) {
|
||||||
var next = configObjects.shift();
|
var next = configObjects.shift();
|
||||||
mergeFn(result, next, next._filename);
|
mergeFn(result, next, next._filename);
|
||||||
}
|
}
|
||||||
|
@ -126,6 +136,32 @@ function mergeAppConfig(target, config, fileName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeMiddlewareConfig(target, config, fileName) {
|
||||||
|
var err;
|
||||||
|
for (var phase in config) {
|
||||||
|
if (phase in target) {
|
||||||
|
err = mergePhaseConfig(target[phase], config[phase], phase);
|
||||||
|
} else {
|
||||||
|
err = 'The phase "' + phase + '" is not defined in the main config.';
|
||||||
|
}
|
||||||
|
if (err)
|
||||||
|
throw new Error('Cannot apply ' + fileName + ': ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergePhaseConfig(target, config, phase) {
|
||||||
|
var err;
|
||||||
|
for (var middleware in config) {
|
||||||
|
if (middleware in target) {
|
||||||
|
err = mergeObjects(target[middleware], config[middleware]);
|
||||||
|
} else {
|
||||||
|
err = 'The middleware "' + middleware + '" in phase "' + phase + '"' +
|
||||||
|
'is not defined in the main config.';
|
||||||
|
}
|
||||||
|
if (err) return 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;
|
||||||
|
@ -163,7 +199,7 @@ function mergeArrays(target, config, keyPrefix) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use for(;;) to iterate over undefined items, for(in) would skip them.
|
// Use for(;;) to iterate over undefined items, for(in) would skip them.
|
||||||
for (var ix=0; ix < target.length; ix++) {
|
for (var ix = 0; ix < target.length; ix++) {
|
||||||
var fullKey = keyPrefix + '[' + ix + ']';
|
var fullKey = keyPrefix + '[' + ix + ']';
|
||||||
var err = mergeSingleItemOrProperty(target, config, ix, fullKey);
|
var err = mergeSingleItemOrProperty(target, config, ix, fullKey);
|
||||||
if (err) return err;
|
if (err) return err;
|
||||||
|
@ -189,15 +225,15 @@ function hasCompatibleType(origValue, newValue) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {string} cwd Dirname of the file
|
||||||
* @param fileName Name of the file without extension
|
* @param {string} fileName Name of the file without extension
|
||||||
* @returns {Object|undefined} Content of the file, undefined if not found.
|
* @returns {Object|undefined} Content of the file, undefined if not found.
|
||||||
*/
|
*/
|
||||||
function tryReadJsonConfig(cwd, fileName) {
|
function tryReadJsonConfig(cwd, fileName) {
|
||||||
try {
|
try {
|
||||||
return require(path.join(cwd, fileName + '.json'));
|
return require(path.join(cwd, fileName + '.json'));
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
if(e.code !== 'MODULE_NOT_FOUND') {
|
if (e.code !== 'MODULE_NOT_FOUND') {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ module.exports = function execute(app, instructions, callback) {
|
||||||
|
|
||||||
setupDataSources(app, instructions);
|
setupDataSources(app, instructions);
|
||||||
setupModels(app, instructions);
|
setupModels(app, instructions);
|
||||||
|
setupMiddleware(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
|
||||||
|
@ -45,10 +46,10 @@ function patchAppLoopback(app) {
|
||||||
// patch the app object to make loopback-boot work with older versions too
|
// patch the app object to make loopback-boot work with older versions too
|
||||||
try {
|
try {
|
||||||
app.loopback = require('loopback');
|
app.loopback = require('loopback');
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
if (err.code === 'MODULE_NOT_FOUND') {
|
if (err.code === 'MODULE_NOT_FOUND') {
|
||||||
console.error(
|
console.error(
|
||||||
'When using loopback-boot with loopback <1.9, '+
|
'When using loopback-boot with loopback <1.9, ' +
|
||||||
'the loopback module must be available for `require(\'loopback\')`.');
|
'the loopback module must be available for `require(\'loopback\')`.');
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -69,7 +70,7 @@ function assertLoopBackVersion(app) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHost(app, instructions) {
|
function setHost(app, instructions) {
|
||||||
//jshint camelcase:false
|
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||||
var host =
|
var host =
|
||||||
process.env.npm_config_host ||
|
process.env.npm_config_host ||
|
||||||
process.env.OPENSHIFT_SLS_IP ||
|
process.env.OPENSHIFT_SLS_IP ||
|
||||||
|
@ -79,14 +80,14 @@ function setHost(app, instructions) {
|
||||||
process.env.npm_package_config_host ||
|
process.env.npm_package_config_host ||
|
||||||
app.get('host');
|
app.get('host');
|
||||||
|
|
||||||
if(host !== undefined) {
|
if (host !== undefined) {
|
||||||
assert(typeof host === 'string', 'app.host must be a string');
|
assert(typeof host === 'string', 'app.host must be a string');
|
||||||
app.set('host', host);
|
app.set('host', host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPort(app, instructions) {
|
function setPort(app, instructions) {
|
||||||
//jshint camelcase:false
|
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||||
var port = _.find([
|
var port = _.find([
|
||||||
process.env.npm_config_port,
|
process.env.npm_config_port,
|
||||||
process.env.OPENSHIFT_SLS_PORT,
|
process.env.OPENSHIFT_SLS_PORT,
|
||||||
|
@ -98,7 +99,7 @@ function setPort(app, instructions) {
|
||||||
3000
|
3000
|
||||||
], _.isFinite);
|
], _.isFinite);
|
||||||
|
|
||||||
if(port !== undefined) {
|
if (port !== undefined) {
|
||||||
var portType = typeof port;
|
var portType = typeof port;
|
||||||
assert(portType === 'string' || portType === 'number',
|
assert(portType === 'string' || portType === 'number',
|
||||||
'app.port must be a string or number');
|
'app.port must be a string or number');
|
||||||
|
@ -122,9 +123,9 @@ function setApiRoot(app, instructions) {
|
||||||
|
|
||||||
function applyAppConfig(app, instructions) {
|
function applyAppConfig(app, instructions) {
|
||||||
var appConfig = instructions.config;
|
var appConfig = instructions.config;
|
||||||
for(var configKey in appConfig) {
|
for (var configKey in appConfig) {
|
||||||
var cur = app.get(configKey);
|
var cur = app.get(configKey);
|
||||||
if(cur === undefined || cur === null) {
|
if (cur === undefined || cur === null) {
|
||||||
app.set(configKey, appConfig[configKey]);
|
app.set(configKey, appConfig[configKey]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,7 +199,7 @@ function isBuiltinLoopBackModel(app, data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function forEachKeyedObject(obj, fn) {
|
function forEachKeyedObject(obj, fn) {
|
||||||
if(typeof obj !== 'object') return;
|
if (typeof obj !== 'object') return;
|
||||||
|
|
||||||
Object.keys(obj).forEach(function(key) {
|
Object.keys(obj).forEach(function(key) {
|
||||||
fn(key, obj[key]);
|
fn(key, obj[key]);
|
||||||
|
@ -240,8 +241,8 @@ function runScripts(app, list, callback) {
|
||||||
function tryRequire(modulePath) {
|
function tryRequire(modulePath) {
|
||||||
try {
|
try {
|
||||||
return require.apply(this, arguments);
|
return require.apply(this, arguments);
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
if(e.code === 'MODULE_NOT_FOUND') {
|
if (e.code === 'MODULE_NOT_FOUND') {
|
||||||
debug('Warning: cannot require %s - module not found.', modulePath);
|
debug('Warning: cannot require %s - module not found.', modulePath);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -250,6 +251,37 @@ function tryRequire(modulePath) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupMiddleware(app, instructions) {
|
||||||
|
if (!instructions.middleware) {
|
||||||
|
// the browserified client does not support middleware
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phases can be empty
|
||||||
|
var phases = instructions.middleware.phases || [];
|
||||||
|
assert(Array.isArray(phases),
|
||||||
|
'instructions.middleware.phases must be an array');
|
||||||
|
|
||||||
|
var middleware = instructions.middleware.middleware;
|
||||||
|
assert(Array.isArray(middleware),
|
||||||
|
'instructions.middleware.middleware must be an object');
|
||||||
|
|
||||||
|
debug('Defining middleware phases %j', phases);
|
||||||
|
app.defineMiddlewarePhases(phases);
|
||||||
|
|
||||||
|
middleware.forEach(function(data) {
|
||||||
|
debug('Configuring middleware %j%s', data.sourceFile,
|
||||||
|
data.fragment ? ('#' + data.fragment) : '');
|
||||||
|
var factory = require(data.sourceFile);
|
||||||
|
if (data.fragment) {
|
||||||
|
factory = factory[data.fragment];
|
||||||
|
}
|
||||||
|
assert(typeof factory === 'function',
|
||||||
|
'Middleware factory must be a function');
|
||||||
|
app.middlewareFromConfig(factory, data.config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function runBootScripts(app, instructions, callback) {
|
function runBootScripts(app, instructions, callback) {
|
||||||
runScripts(app, instructions.files.boot, callback);
|
runScripts(app, instructions.files.boot, callback);
|
||||||
}
|
}
|
||||||
|
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-boot",
|
"name": "loopback-boot",
|
||||||
"version": "2.3.1",
|
"version": "2.4.0",
|
||||||
"description": "Convention-based bootstrapper for LoopBack applications",
|
"description": "Convention-based bootstrapper for LoopBack applications",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"StrongLoop",
|
"StrongLoop",
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"browser": "browser.js",
|
"browser": "browser.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pretest": "jshint .",
|
"pretest": "jscs . && jshint .",
|
||||||
"test": "mocha"
|
"test": "mocha"
|
||||||
},
|
},
|
||||||
"license": {
|
"license": {
|
||||||
|
@ -32,15 +32,15 @@
|
||||||
"underscore": "^1.6.0"
|
"underscore": "^1.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"browserify": "^6.1.0",
|
|
||||||
"fs-extra": "^0.12.0",
|
|
||||||
"browserify": "^4.1.8",
|
"browserify": "^4.1.8",
|
||||||
|
"chai": "^1.10.0",
|
||||||
"coffee-script": "^1.8.0",
|
"coffee-script": "^1.8.0",
|
||||||
"coffeeify": "^0.7.0",
|
"coffeeify": "^0.7.0",
|
||||||
|
"fs-extra": "^0.12.0",
|
||||||
|
"jscs": "^1.7.3",
|
||||||
"jshint": "^2.5.6",
|
"jshint": "^2.5.6",
|
||||||
"loopback": "^2.5.0",
|
"loopback": "^2.5.0",
|
||||||
"mocha": "^1.19.0",
|
"mocha": "^1.19.0",
|
||||||
"must": "^0.12.0",
|
|
||||||
"supertest": "^0.14.0"
|
"supertest": "^0.14.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
var boot = require('../');
|
var boot = require('../');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var expect = require('must');
|
var expect = require('chai').expect;
|
||||||
var browserify = require('browserify');
|
var browserify = require('browserify');
|
||||||
var sandbox = require('./helpers/sandbox');
|
var sandbox = require('./helpers/sandbox');
|
||||||
var vm = require('vm');
|
var vm = require('vm');
|
||||||
|
@ -76,7 +76,7 @@ describe('browser support', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
function browserifyTestApp(appDir, strategy, next) {
|
function browserifyTestApp(appDir, strategy, next) {
|
||||||
//set default args
|
// set default args
|
||||||
if (((typeof strategy) === 'function') && !next) {
|
if (((typeof strategy) === 'function') && !next) {
|
||||||
next = strategy;
|
next = strategy;
|
||||||
strategy = undefined;
|
strategy = undefined;
|
||||||
|
@ -91,7 +91,7 @@ function browserifyTestApp(appDir, strategy, 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().pipe(out);
|
b.bundle().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);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
var boot = require('../');
|
var boot = require('../');
|
||||||
var fs = require('fs-extra');
|
var fs = require('fs-extra');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var expect = require('must');
|
var expect = require('chai').expect;
|
||||||
var sandbox = require('./helpers/sandbox');
|
var sandbox = require('./helpers/sandbox');
|
||||||
var appdir = require('./helpers/appdir');
|
var appdir = require('./helpers/appdir');
|
||||||
|
|
||||||
|
@ -12,7 +12,10 @@ describe('compiler', function() {
|
||||||
beforeEach(appdir.init);
|
beforeEach(appdir.init);
|
||||||
|
|
||||||
describe('from options', function() {
|
describe('from options', function() {
|
||||||
var options, instructions, appConfig;
|
var options;
|
||||||
|
var instructions;
|
||||||
|
var appConfig;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
options = {
|
options = {
|
||||||
config: {
|
config: {
|
||||||
|
@ -256,7 +259,7 @@ describe('compiler', function() {
|
||||||
appdir.writeConfigFileSync('config.local.json', {
|
appdir.writeConfigFileSync('config.local.json', {
|
||||||
toplevel: [
|
toplevel: [
|
||||||
{
|
{
|
||||||
nested: [ 'value' ]
|
nested: ['value']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -337,7 +340,7 @@ describe('compiler', function() {
|
||||||
appConfigRootDir: path.resolve(appdir.PATH, 'custom')
|
appConfigRootDir: path.resolve(appdir.PATH, 'custom')
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(instructions.config).to.have.property('port');
|
expect(instructions.config).to.have.property('port');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports `dsRootDir` option', function() {
|
it('supports `dsRootDir` option', function() {
|
||||||
|
@ -379,7 +382,7 @@ 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() {
|
it('supports `bootDirs` option', function() {
|
||||||
appdir.createConfigFilesSync();
|
appdir.createConfigFilesSync();
|
||||||
var initJs = appdir.writeFileSync('custom-boot/init.js',
|
var initJs = appdir.writeFileSync('custom-boot/init.js',
|
||||||
|
@ -390,7 +393,7 @@ describe('compiler', function() {
|
||||||
});
|
});
|
||||||
expect(instructions.files.boot).to.eql([initJs]);
|
expect(instructions.files.boot).to.eql([initJs]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports `bootScripts` option', function() {
|
it('supports `bootScripts` option', function() {
|
||||||
appdir.createConfigFilesSync();
|
appdir.createConfigFilesSync();
|
||||||
var initJs = appdir.writeFileSync('custom-boot/init.js',
|
var initJs = appdir.writeFileSync('custom-boot/init.js',
|
||||||
|
@ -475,19 +478,19 @@ describe('compiler', function() {
|
||||||
sourceFile: path.resolve(appdir.PATH, 'models', 'car.coffee')
|
sourceFile: path.resolve(appdir.PATH, 'models', 'car.coffee')
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports `modelSources` option', function() {
|
it('supports `modelSources` option', function() {
|
||||||
appdir.createConfigFilesSync({}, {}, {
|
appdir.createConfigFilesSync({}, {}, {
|
||||||
Car: { dataSource: 'db' }
|
Car: { dataSource: 'db' }
|
||||||
});
|
});
|
||||||
appdir.writeConfigFileSync('custom-models/car.json', { name: 'Car' });
|
appdir.writeConfigFileSync('custom-models/car.json', { name: 'Car' });
|
||||||
appdir.writeFileSync('custom-models/car.js', '');
|
appdir.writeFileSync('custom-models/car.js', '');
|
||||||
|
|
||||||
var instructions = boot.compile({
|
var instructions = boot.compile({
|
||||||
appRootDir: appdir.PATH,
|
appRootDir: appdir.PATH,
|
||||||
modelSources: ['./custom-models']
|
modelSources: ['./custom-models']
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(instructions.models).to.have.length(1);
|
expect(instructions.models).to.have.length(1);
|
||||||
expect(instructions.models[0]).to.eql({
|
expect(instructions.models[0]).to.eql({
|
||||||
name: 'Car',
|
name: 'Car',
|
||||||
|
@ -670,6 +673,288 @@ describe('compiler', function() {
|
||||||
expect(instructions.config).to.not.have.property('modified');
|
expect(instructions.config).to.not.have.property('modified');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('for middleware', function() {
|
||||||
|
|
||||||
|
function testMiddlewareRegistration(middlewareId, sourceFile) {
|
||||||
|
var json = {
|
||||||
|
initial: {
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
json.custom[middlewareId] = {
|
||||||
|
params: 'some-config-data'
|
||||||
|
};
|
||||||
|
|
||||||
|
appdir.writeConfigFileSync('middleware.json', json);
|
||||||
|
|
||||||
|
var instructions = boot.compile(appdir.PATH);
|
||||||
|
|
||||||
|
expect(instructions.middleware).to.eql({
|
||||||
|
phases: ['initial', 'custom'],
|
||||||
|
middleware: [
|
||||||
|
{
|
||||||
|
sourceFile: sourceFile,
|
||||||
|
config: {
|
||||||
|
phase: 'custom',
|
||||||
|
params: 'some-config-data'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceFileForUrlNotFound;
|
||||||
|
beforeEach(function() {
|
||||||
|
fs.copySync(SIMPLE_APP, appdir.PATH);
|
||||||
|
sourceFileForUrlNotFound = require.resolve(
|
||||||
|
'loopback/server/middleware/url-not-found');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits middleware instructions', function() {
|
||||||
|
testMiddlewareRegistration('loopback/server/middleware/url-not-found',
|
||||||
|
sourceFileForUrlNotFound);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits middleware instructions for fragment', function() {
|
||||||
|
testMiddlewareRegistration('loopback#url-not-found',
|
||||||
|
sourceFileForUrlNotFound);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails when a module middleware cannot be resolved', function() {
|
||||||
|
appdir.writeConfigFileSync('middleware.json', {
|
||||||
|
final: {
|
||||||
|
'loopback/path-does-not-exist': { }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(function() { boot.compile(appdir.PATH); })
|
||||||
|
.to.throw(/path-does-not-exist/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails when a module middleware fragment cannot be resolved',
|
||||||
|
function() {
|
||||||
|
appdir.writeConfigFileSync('middleware.json', {
|
||||||
|
final: {
|
||||||
|
'loopback#path-does-not-exist': { }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
boot.compile(appdir.PATH);
|
||||||
|
})
|
||||||
|
.to.throw(/path-does-not-exist/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves paths relatively to appRootDir', function() {
|
||||||
|
appdir.writeConfigFileSync('./middleware.json', {
|
||||||
|
routes: {
|
||||||
|
// resolves to ./middleware.json
|
||||||
|
'./middleware': { }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var instructions = boot.compile(appdir.PATH);
|
||||||
|
|
||||||
|
expect(instructions.middleware).to.eql({
|
||||||
|
phases: ['routes'],
|
||||||
|
middleware: [{
|
||||||
|
sourceFile: path.resolve(appdir.PATH, 'middleware.json'),
|
||||||
|
config: { phase: 'routes' }
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('merges config.params', function() {
|
||||||
|
appdir.writeConfigFileSync('./middleware.json', {
|
||||||
|
routes: {
|
||||||
|
'./middleware': {
|
||||||
|
params: {
|
||||||
|
key: 'initial value'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
appdir.writeConfigFileSync('./middleware.local.json', {
|
||||||
|
routes: {
|
||||||
|
'./middleware': {
|
||||||
|
params: {
|
||||||
|
key: 'custom value'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var instructions = boot.compile(appdir.PATH);
|
||||||
|
|
||||||
|
expect(instructions.middleware.middleware[0].config.params).to.eql({
|
||||||
|
key: 'custom value'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('merges config.enabled', function() {
|
||||||
|
appdir.writeConfigFileSync('./middleware.json', {
|
||||||
|
routes: {
|
||||||
|
'./middleware': {
|
||||||
|
params: {
|
||||||
|
key: 'initial value'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
appdir.writeConfigFileSync('./middleware.local.json', {
|
||||||
|
routes: {
|
||||||
|
'./middleware': {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var instructions = boot.compile(appdir.PATH);
|
||||||
|
|
||||||
|
expect(instructions.middleware.middleware[0].config)
|
||||||
|
.to.have.property('enabled', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('flattens sub-phases', function() {
|
||||||
|
appdir.writeConfigFileSync('middleware.json', {
|
||||||
|
'initial:after': {
|
||||||
|
},
|
||||||
|
'custom:before': {
|
||||||
|
'loopback/server/middleware/url-not-found': {
|
||||||
|
params: 'some-config-data'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'custom:after': {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var instructions = boot.compile(appdir.PATH);
|
||||||
|
|
||||||
|
expect(instructions.middleware.phases, 'phases')
|
||||||
|
.to.eql(['initial', 'custom']);
|
||||||
|
expect(instructions.middleware.middleware, 'middleware')
|
||||||
|
.to.eql([{
|
||||||
|
sourceFile:
|
||||||
|
require.resolve('loopback/server/middleware/url-not-found'),
|
||||||
|
config: {
|
||||||
|
phase: 'custom:before',
|
||||||
|
params: 'some-config-data'
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports multiple instances of the same middleware', function() {
|
||||||
|
|
||||||
|
appdir.writeConfigFileSync('middleware.json', {
|
||||||
|
'final': {
|
||||||
|
'./middleware': [
|
||||||
|
{
|
||||||
|
params: 'first'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: 'second'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var instructions = boot.compile(appdir.PATH);
|
||||||
|
|
||||||
|
expect(instructions.middleware.middleware)
|
||||||
|
.to.eql([
|
||||||
|
{
|
||||||
|
sourceFile: path.resolve(appdir.PATH, 'middleware.json'),
|
||||||
|
config: {
|
||||||
|
phase: 'final',
|
||||||
|
params: 'first'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceFile: path.resolve(appdir.PATH, 'middleware.json'),
|
||||||
|
config: {
|
||||||
|
phase: 'final',
|
||||||
|
params: 'second'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports shorthand notation for middleware paths', function() {
|
||||||
|
appdir.writeConfigFileSync('middleware.json', {
|
||||||
|
'final': {
|
||||||
|
'loopback#url-not-found': {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var instructions = boot.compile(appdir.PATH);
|
||||||
|
|
||||||
|
expect(instructions.middleware.middleware[0].sourceFile)
|
||||||
|
.to.equal(require.resolve('loopback/server/middleware/url-not-found'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports shorthand notation for relative paths', function() {
|
||||||
|
appdir.writeConfigFileSync('middleware.json', {
|
||||||
|
'routes': {
|
||||||
|
'./middleware/index#myMiddleware': {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var instructions = boot.compile(appdir.PATH);
|
||||||
|
|
||||||
|
expect(instructions.middleware.middleware[0].sourceFile)
|
||||||
|
.to.equal(path.resolve(appdir.PATH,
|
||||||
|
'./middleware/index.js'));
|
||||||
|
expect(instructions.middleware.middleware[0]).have.property(
|
||||||
|
'fragment',
|
||||||
|
'myMiddleware');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports shorthand notation when the fragment name matches a property',
|
||||||
|
function() {
|
||||||
|
appdir.writeConfigFileSync('middleware.json', {
|
||||||
|
'final': {
|
||||||
|
'loopback#errorHandler': {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var instructions = boot.compile(appdir.PATH);
|
||||||
|
|
||||||
|
expect(instructions.middleware.middleware[0]).have.property(
|
||||||
|
'sourceFile',
|
||||||
|
require.resolve('loopback'));
|
||||||
|
expect(instructions.middleware.middleware[0]).have.property(
|
||||||
|
'fragment',
|
||||||
|
'errorHandler');
|
||||||
|
});
|
||||||
|
|
||||||
|
// FIXME: [rfeng] The following test is disabled until
|
||||||
|
// https://github.com/strongloop/loopback-boot/issues/73 is fixed
|
||||||
|
it.skip('resolves modules relative to appRootDir', function() {
|
||||||
|
var HANDLER_FILE = 'node_modules/handler/index.js';
|
||||||
|
appdir.writeFileSync(
|
||||||
|
HANDLER_FILE,
|
||||||
|
'module.exports = function(req, res, next) { next(); }');
|
||||||
|
|
||||||
|
appdir.writeConfigFileSync('middleware.json', {
|
||||||
|
'initial': {
|
||||||
|
'handler': {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var instructions = boot.compile(appdir.PATH);
|
||||||
|
|
||||||
|
expect(instructions.middleware.middleware[0]).have.property(
|
||||||
|
'sourceFile',
|
||||||
|
appdir.resolve(HANDLER_FILE));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getNameProperty(obj) {
|
function getNameProperty(obj) {
|
||||||
|
|
|
@ -2,10 +2,11 @@ var boot = require('../');
|
||||||
var path = require('path');
|
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('chai').expect;
|
||||||
var fs = require('fs-extra');
|
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');
|
||||||
|
var supertest = require('supertest');
|
||||||
|
|
||||||
var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app');
|
var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app');
|
||||||
|
|
||||||
|
@ -18,6 +19,13 @@ describe('executor', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
app = loopback();
|
app = loopback();
|
||||||
|
|
||||||
|
// process.bootFlags is used by simple-app/boot/*.js scripts
|
||||||
|
process.bootFlags = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
delete process.bootFlags;
|
||||||
});
|
});
|
||||||
|
|
||||||
var dummyInstructions = someInstructions({
|
var dummyInstructions = someInstructions({
|
||||||
|
@ -171,14 +179,13 @@ describe('executor', function() {
|
||||||
};
|
};
|
||||||
builtinModel.definition.redefined = true;
|
builtinModel.definition.redefined = true;
|
||||||
|
|
||||||
boot.execute(app, someInstructions({ models: [ builtinModel ] }));
|
boot.execute(app, someInstructions({ models: [builtinModel] }));
|
||||||
|
|
||||||
expect(app.models.User.settings.redefined, 'redefined').to.not.equal(true);
|
expect(app.models.User.settings.redefined, 'redefined').to.not.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
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());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -212,14 +219,6 @@ describe('executor', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with boot with callback', function() {
|
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) {
|
it('should run `boot/*` files asynchronously', function(done) {
|
||||||
boot.execute(app, simpleAppInstructions(), function() {
|
boot.execute(app, simpleAppInstructions(), function() {
|
||||||
expect(process.bootFlags).to.eql([
|
expect(process.bootFlags).to.eql([
|
||||||
|
@ -233,7 +232,6 @@ describe('executor', function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with PaaS and npm env variables', function() {
|
describe('with PaaS and npm env variables', function() {
|
||||||
|
@ -266,7 +264,7 @@ describe('executor', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should prioritize sources', function() {
|
it('should prioritize sources', function() {
|
||||||
/*jshint camelcase:false */
|
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||||
process.env.npm_config_host = randomHost();
|
process.env.npm_config_host = randomHost();
|
||||||
process.env.OPENSHIFT_SLS_IP = randomHost();
|
process.env.OPENSHIFT_SLS_IP = randomHost();
|
||||||
process.env.OPENSHIFT_NODEJS_IP = randomHost();
|
process.env.OPENSHIFT_NODEJS_IP = randomHost();
|
||||||
|
@ -323,11 +321,100 @@ describe('executor', function() {
|
||||||
'module.exports = function(app) { app.fnCalled = true; };');
|
'module.exports = function(app) { app.fnCalled = true; };');
|
||||||
|
|
||||||
delete app.fnCalled;
|
delete app.fnCalled;
|
||||||
boot.execute(app, someInstructions({ files: { boot: [ file ] } }));
|
boot.execute(app, someInstructions({ files: { boot: [file] } }));
|
||||||
expect(app.fnCalled, 'exported fn was called').to.be.true();
|
expect(app.fnCalled, 'exported fn was called').to.be.true();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
it('configures middleware', function(done) {
|
||||||
|
var pushNamePath = require.resolve('./helpers/push-name-middleware');
|
||||||
|
|
||||||
|
boot.execute(app, someInstructions({
|
||||||
|
middleware: {
|
||||||
|
phases: ['initial', 'custom'],
|
||||||
|
middleware: [
|
||||||
|
{
|
||||||
|
sourceFile: pushNamePath,
|
||||||
|
config: {
|
||||||
|
phase: 'initial',
|
||||||
|
params: 'initial'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceFile: pushNamePath,
|
||||||
|
config: {
|
||||||
|
phase: 'custom',
|
||||||
|
params: 'custom'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceFile: pushNamePath,
|
||||||
|
config: {
|
||||||
|
phase: 'routes',
|
||||||
|
params: 'routes'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceFile: pushNamePath,
|
||||||
|
config: {
|
||||||
|
phase: 'routes',
|
||||||
|
enabled: false,
|
||||||
|
params: 'disabled'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
supertest(app)
|
||||||
|
.get('/')
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
var names = (res.headers.names || '').split(',');
|
||||||
|
expect(names).to.eql(['initial', 'custom', 'routes']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('configures middleware using shortform', function(done) {
|
||||||
|
|
||||||
|
boot.execute(app, someInstructions({
|
||||||
|
middleware: {
|
||||||
|
middleware: [
|
||||||
|
{
|
||||||
|
sourceFile: require.resolve('loopback'),
|
||||||
|
fragment: 'static',
|
||||||
|
config: {
|
||||||
|
phase: 'files',
|
||||||
|
params: path.join(__dirname, './fixtures/simple-app/client/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
supertest(app)
|
||||||
|
.get('/')
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(res.text).to.eql('<!DOCTYPE html>\n<html>\n<head lang="en">\n' +
|
||||||
|
' <meta charset="UTF-8">\n <title>simple-app</title>\n' +
|
||||||
|
'</head>\n<body>\n<h1>simple-app</h1>\n</body>\n</html>');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('configures middleware (end-to-end)', function(done) {
|
||||||
|
boot.execute(app, simpleAppInstructions());
|
||||||
|
|
||||||
|
supertest(app)
|
||||||
|
.get('/')
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(res.headers.names).to.equal('custom-middleware');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function assertValidDataSource(dataSource) {
|
function assertValidDataSource(dataSource) {
|
||||||
// has methods
|
// has methods
|
||||||
|
@ -340,7 +427,7 @@ function assertValidDataSource(dataSource) {
|
||||||
assert.isFunc(dataSource, 'operations');
|
assert.isFunc(dataSource, 'operations');
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.isFunc = function (obj, name) {
|
assert.isFunc = function(obj, name) {
|
||||||
assert(obj, 'cannot assert function ' + name +
|
assert(obj, 'cannot assert function ' + name +
|
||||||
' on object that does not exist');
|
' on object that does not exist');
|
||||||
assert(typeof obj[name] === 'function', name + ' is not a function');
|
assert(typeof obj[name] === 'function', name + ' is not a function');
|
||||||
|
@ -351,6 +438,7 @@ function someInstructions(values) {
|
||||||
config: values.config || {},
|
config: values.config || {},
|
||||||
models: values.models || [],
|
models: values.models || [],
|
||||||
dataSources: values.dataSources || { db: { connector: 'memory' } },
|
dataSources: values.dataSources || { db: { connector: 'memory' } },
|
||||||
|
middleware: values.middleware || { phases: [], middleware: [] },
|
||||||
files: {
|
files: {
|
||||||
boot: []
|
boot: []
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,3 @@ process.bootFlags.push('barSyncLoaded');
|
||||||
module.exports = function(app) {
|
module.exports = function(app) {
|
||||||
process.bootFlags.push('barSyncExecuted');
|
process.bootFlags.push('barSyncExecuted');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
process.bootFlags.push('fooLoaded');
|
process.bootFlags.push('fooLoaded');
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head lang="en">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>simple-app</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>simple-app</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"initial": {
|
||||||
|
"../../helpers/push-name-middleware": {
|
||||||
|
"params": "custom-middleware"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
exports.myMiddleware = function(name) {
|
||||||
|
return function(req, res, next) {
|
||||||
|
req._names = req._names || [];
|
||||||
|
req._names.push(name);
|
||||||
|
res.setHeader('names', req._names.join(','));
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Exporting a middleware as a property of the main module
|
||||||
|
*/
|
||||||
|
exports.myMiddleware = function(req, res, next) {
|
||||||
|
res.setHeader('X-MY-MIDDLEWARE', 'myMiddleware');
|
||||||
|
next();
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "my-module",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "my-module",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
|
@ -42,8 +42,12 @@ appdir.writeConfigFileSync = function(name, json) {
|
||||||
};
|
};
|
||||||
|
|
||||||
appdir.writeFileSync = function(name, content) {
|
appdir.writeFileSync = function(name, content) {
|
||||||
var filePath = path.resolve(PATH, name);
|
var filePath = this.resolve(name);
|
||||||
fs.mkdirsSync(path.dirname(filePath));
|
fs.mkdirsSync(path.dirname(filePath));
|
||||||
fs.writeFileSync(filePath, content, 'utf-8');
|
fs.writeFileSync(filePath, content, 'utf-8');
|
||||||
return filePath;
|
return filePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
appdir.resolve = function(name) {
|
||||||
|
return path.resolve(PATH, name);
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
module.exports = function(name) {
|
||||||
|
return function(req, res, next) {
|
||||||
|
req._names = req._names || [];
|
||||||
|
req._names.push(name);
|
||||||
|
res.setHeader('names', req._names.join(','));
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in New Issue