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,
|
||||
"browser": true,
|
||||
"camelcase" : true,
|
||||
"eqnull" : true,
|
||||
"indent": 2,
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"quotmark": "single",
|
||||
"maxlen": 80,
|
||||
"trailing": true,
|
||||
"newcap": true,
|
||||
"nonew": true,
|
||||
"sub": true,
|
||||
"unused": "vars",
|
||||
"globals": {
|
||||
"describe": true,
|
||||
"it": true,
|
||||
"before": true,
|
||||
"beforeEach": true,
|
||||
"after": true,
|
||||
"afterEach": true
|
||||
"describe": false,
|
||||
"it": false,
|
||||
"before": false,
|
||||
"beforeEach": false,
|
||||
"after": false,
|
||||
"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
|
||||
app.json, app.local.*, app.{env}.*
|
||||
datasources.json, datasources.local.*, datasources.{env}.*
|
||||
```
|
||||
* Add jscs style check, fix violations found (Miroslav Bajtoš)
|
||||
|
||||
- Scripts in `models/` and `boot/` can export `function(app)`,
|
||||
this function is then called by the bootstrapper. The existing code
|
||||
using `var app = require('../app')` will continue to work.
|
||||
* Clean up .jshintrc (Miroslav Bajtoš)
|
||||
|
||||
* 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.
|
||||
* @property {Array.<String>} [modelSources] List of directories where to look
|
||||
* 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
|
||||
* for boot scripts.
|
||||
* @property {Array.<String>} [bootScripts] List of script files to execute
|
||||
|
@ -136,7 +138,7 @@ exports.compileToBrowserify = function(options, bundler) {
|
|||
addInstructionsToBrowserify(compile(options), bundler);
|
||||
};
|
||||
|
||||
//-- undocumented low-level API --//
|
||||
/*-- undocumented low-level API --*/
|
||||
|
||||
exports.ConfigLoader = ConfigLoader;
|
||||
exports.compile = compile;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var commondir = require('commondir');
|
||||
var cloneDeep = require('lodash.clonedeep');
|
||||
|
||||
/**
|
||||
* Add boot instructions to a browserify bundler.
|
||||
|
@ -60,6 +61,17 @@ function addScriptsToBundle(name, list, 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);
|
||||
|
||||
/* 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) {
|
||||
options = options || {};
|
||||
|
||||
if(typeof options === 'string') {
|
||||
if (typeof options === 'string') {
|
||||
options = { appRootDir: options };
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,14 @@ module.exports = function compile(options) {
|
|||
ConfigLoader.loadDataSources(dsRootDir, env);
|
||||
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
|
||||
var bootDirs = options.bootDirs || []; // precedence
|
||||
bootDirs = bootDirs.concat(path.join(appRootDir, 'boot'));
|
||||
|
@ -67,6 +75,7 @@ module.exports = function compile(options) {
|
|||
config: appConfig,
|
||||
dataSources: dataSourcesConfig,
|
||||
models: modelInstructions,
|
||||
middleware: middlewareInstructions,
|
||||
files: {
|
||||
boot: bootScripts
|
||||
}
|
||||
|
@ -74,7 +83,7 @@ module.exports = function compile(options) {
|
|||
};
|
||||
|
||||
function assertIsValidConfig(name, config) {
|
||||
if(config) {
|
||||
if (config) {
|
||||
assert(typeof config === 'object',
|
||||
name + ' config must be a valid JSON object');
|
||||
}
|
||||
|
@ -143,7 +152,7 @@ function findScripts(dir) {
|
|||
} else {
|
||||
try {
|
||||
path.join(require.resolve(filepath));
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
debug('Skipping directory %s - %s', filepath, err.code || err);
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +164,7 @@ function findScripts(dir) {
|
|||
function tryReadDir() {
|
||||
try {
|
||||
return fs.readdirSync.apply(fs, arguments);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -309,7 +318,9 @@ function loadModelDefinition(rootDir, jsonFile, allFiles) {
|
|||
var basename = path.basename(jsonFile, path.extname(jsonFile));
|
||||
|
||||
// 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
|
||||
.filter(function(f) {
|
||||
ext = path.extname(f);
|
||||
|
@ -336,3 +347,104 @@ function loadModelDefinition(rootDir, jsonFile, allFiles) {
|
|||
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') || {};
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 --*/
|
||||
|
||||
/**
|
||||
|
@ -103,7 +113,7 @@ function loadConfigFiles(files) {
|
|||
*/
|
||||
function mergeConfigurations(configObjects, mergeFn) {
|
||||
var result = configObjects.shift() || {};
|
||||
while(configObjects.length) {
|
||||
while (configObjects.length) {
|
||||
var next = configObjects.shift();
|
||||
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) {
|
||||
for (var key in config) {
|
||||
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.
|
||||
for (var ix=0; ix < target.length; ix++) {
|
||||
for (var ix = 0; ix < target.length; ix++) {
|
||||
var fullKey = keyPrefix + '[' + ix + ']';
|
||||
var err = mergeSingleItemOrProperty(target, config, ix, fullKey);
|
||||
if (err) return err;
|
||||
|
@ -189,15 +225,15 @@ function hasCompatibleType(origValue, newValue) {
|
|||
|
||||
/**
|
||||
* Try to read a config file with .json extension
|
||||
* @param cwd Dirname of the file
|
||||
* @param fileName Name of the file without extension
|
||||
* @param {string} cwd Dirname of the file
|
||||
* @param {string} fileName Name of the file without extension
|
||||
* @returns {Object|undefined} Content of the file, undefined if not found.
|
||||
*/
|
||||
function tryReadJsonConfig(cwd, fileName) {
|
||||
try {
|
||||
return require(path.join(cwd, fileName + '.json'));
|
||||
} catch(e) {
|
||||
if(e.code !== 'MODULE_NOT_FOUND') {
|
||||
} catch (e) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND') {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ module.exports = function execute(app, instructions, callback) {
|
|||
|
||||
setupDataSources(app, instructions);
|
||||
setupModels(app, instructions);
|
||||
setupMiddleware(app, instructions);
|
||||
|
||||
// Run the boot scripts in series synchronously or asynchronously
|
||||
// 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
|
||||
try {
|
||||
app.loopback = require('loopback');
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
if (err.code === 'MODULE_NOT_FOUND') {
|
||||
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\')`.');
|
||||
}
|
||||
throw err;
|
||||
|
@ -69,7 +70,7 @@ function assertLoopBackVersion(app) {
|
|||
}
|
||||
|
||||
function setHost(app, instructions) {
|
||||
//jshint camelcase:false
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
var host =
|
||||
process.env.npm_config_host ||
|
||||
process.env.OPENSHIFT_SLS_IP ||
|
||||
|
@ -79,14 +80,14 @@ function setHost(app, instructions) {
|
|||
process.env.npm_package_config_host ||
|
||||
app.get('host');
|
||||
|
||||
if(host !== undefined) {
|
||||
if (host !== undefined) {
|
||||
assert(typeof host === 'string', 'app.host must be a string');
|
||||
app.set('host', host);
|
||||
}
|
||||
}
|
||||
|
||||
function setPort(app, instructions) {
|
||||
//jshint camelcase:false
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
var port = _.find([
|
||||
process.env.npm_config_port,
|
||||
process.env.OPENSHIFT_SLS_PORT,
|
||||
|
@ -98,7 +99,7 @@ function setPort(app, instructions) {
|
|||
3000
|
||||
], _.isFinite);
|
||||
|
||||
if(port !== undefined) {
|
||||
if (port !== undefined) {
|
||||
var portType = typeof port;
|
||||
assert(portType === 'string' || portType === 'number',
|
||||
'app.port must be a string or number');
|
||||
|
@ -122,9 +123,9 @@ function setApiRoot(app, instructions) {
|
|||
|
||||
function applyAppConfig(app, instructions) {
|
||||
var appConfig = instructions.config;
|
||||
for(var configKey in appConfig) {
|
||||
for (var configKey in appConfig) {
|
||||
var cur = app.get(configKey);
|
||||
if(cur === undefined || cur === null) {
|
||||
if (cur === undefined || cur === null) {
|
||||
app.set(configKey, appConfig[configKey]);
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +199,7 @@ function isBuiltinLoopBackModel(app, data) {
|
|||
}
|
||||
|
||||
function forEachKeyedObject(obj, fn) {
|
||||
if(typeof obj !== 'object') return;
|
||||
if (typeof obj !== 'object') return;
|
||||
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
fn(key, obj[key]);
|
||||
|
@ -240,8 +241,8 @@ function runScripts(app, list, callback) {
|
|||
function tryRequire(modulePath) {
|
||||
try {
|
||||
return require.apply(this, arguments);
|
||||
} catch(e) {
|
||||
if(e.code === 'MODULE_NOT_FOUND') {
|
||||
} catch (e) {
|
||||
if (e.code === 'MODULE_NOT_FOUND') {
|
||||
debug('Warning: cannot require %s - module not found.', modulePath);
|
||||
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) {
|
||||
runScripts(app, instructions.files.boot, callback);
|
||||
}
|
||||
|
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loopback-boot",
|
||||
"version": "2.3.1",
|
||||
"version": "2.4.0",
|
||||
"description": "Convention-based bootstrapper for LoopBack applications",
|
||||
"keywords": [
|
||||
"StrongLoop",
|
||||
|
@ -15,7 +15,7 @@
|
|||
"main": "index.js",
|
||||
"browser": "browser.js",
|
||||
"scripts": {
|
||||
"pretest": "jshint .",
|
||||
"pretest": "jscs . && jshint .",
|
||||
"test": "mocha"
|
||||
},
|
||||
"license": {
|
||||
|
@ -32,15 +32,15 @@
|
|||
"underscore": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^6.1.0",
|
||||
"fs-extra": "^0.12.0",
|
||||
"browserify": "^4.1.8",
|
||||
"chai": "^1.10.0",
|
||||
"coffee-script": "^1.8.0",
|
||||
"coffeeify": "^0.7.0",
|
||||
"fs-extra": "^0.12.0",
|
||||
"jscs": "^1.7.3",
|
||||
"jshint": "^2.5.6",
|
||||
"loopback": "^2.5.0",
|
||||
"mocha": "^1.19.0",
|
||||
"must": "^0.12.0",
|
||||
"supertest": "^0.14.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
var boot = require('../');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var expect = require('must');
|
||||
var expect = require('chai').expect;
|
||||
var browserify = require('browserify');
|
||||
var sandbox = require('./helpers/sandbox');
|
||||
var vm = require('vm');
|
||||
|
@ -76,7 +76,7 @@ describe('browser support', function() {
|
|||
});
|
||||
|
||||
function browserifyTestApp(appDir, strategy, next) {
|
||||
//set default args
|
||||
// set default args
|
||||
if (((typeof strategy) === 'function') && !next) {
|
||||
next = strategy;
|
||||
strategy = undefined;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
var boot = require('../');
|
||||
var fs = require('fs-extra');
|
||||
var path = require('path');
|
||||
var expect = require('must');
|
||||
var expect = require('chai').expect;
|
||||
var sandbox = require('./helpers/sandbox');
|
||||
var appdir = require('./helpers/appdir');
|
||||
|
||||
|
@ -12,7 +12,10 @@ describe('compiler', function() {
|
|||
beforeEach(appdir.init);
|
||||
|
||||
describe('from options', function() {
|
||||
var options, instructions, appConfig;
|
||||
var options;
|
||||
var instructions;
|
||||
var appConfig;
|
||||
|
||||
beforeEach(function() {
|
||||
options = {
|
||||
config: {
|
||||
|
@ -256,7 +259,7 @@ describe('compiler', function() {
|
|||
appdir.writeConfigFileSync('config.local.json', {
|
||||
toplevel: [
|
||||
{
|
||||
nested: [ 'value' ]
|
||||
nested: ['value']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
@ -337,7 +340,7 @@ describe('compiler', function() {
|
|||
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() {
|
||||
|
@ -670,6 +673,288 @@ describe('compiler', function() {
|
|||
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) {
|
||||
|
|
|
@ -2,10 +2,11 @@ var boot = require('../');
|
|||
var path = require('path');
|
||||
var loopback = require('loopback');
|
||||
var assert = require('assert');
|
||||
var expect = require('must');
|
||||
var expect = require('chai').expect;
|
||||
var fs = require('fs-extra');
|
||||
var sandbox = require('./helpers/sandbox');
|
||||
var appdir = require('./helpers/appdir');
|
||||
var supertest = require('supertest');
|
||||
|
||||
var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app');
|
||||
|
||||
|
@ -18,6 +19,13 @@ describe('executor', function() {
|
|||
|
||||
beforeEach(function() {
|
||||
app = loopback();
|
||||
|
||||
// process.bootFlags is used by simple-app/boot/*.js scripts
|
||||
process.bootFlags = [];
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
delete process.bootFlags;
|
||||
});
|
||||
|
||||
var dummyInstructions = someInstructions({
|
||||
|
@ -171,14 +179,13 @@ describe('executor', function() {
|
|||
};
|
||||
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);
|
||||
});
|
||||
|
||||
describe('with boot and models files', function() {
|
||||
beforeEach(function() {
|
||||
process.bootFlags = process.bootFlags || [];
|
||||
boot.execute(app, simpleAppInstructions());
|
||||
});
|
||||
|
||||
|
@ -212,14 +219,6 @@ describe('executor', 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) {
|
||||
boot.execute(app, simpleAppInstructions(), function() {
|
||||
expect(process.bootFlags).to.eql([
|
||||
|
@ -233,7 +232,6 @@ describe('executor', function() {
|
|||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with PaaS and npm env variables', function() {
|
||||
|
@ -266,7 +264,7 @@ describe('executor', function() {
|
|||
});
|
||||
|
||||
it('should prioritize sources', function() {
|
||||
/*jshint camelcase:false */
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
process.env.npm_config_host = randomHost();
|
||||
process.env.OPENSHIFT_SLS_IP = randomHost();
|
||||
process.env.OPENSHIFT_NODEJS_IP = randomHost();
|
||||
|
@ -323,11 +321,100 @@ describe('executor', function() {
|
|||
'module.exports = function(app) { app.fnCalled = true; };');
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
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) {
|
||||
// has methods
|
||||
|
@ -340,7 +427,7 @@ function assertValidDataSource(dataSource) {
|
|||
assert.isFunc(dataSource, 'operations');
|
||||
}
|
||||
|
||||
assert.isFunc = function (obj, name) {
|
||||
assert.isFunc = function(obj, name) {
|
||||
assert(obj, 'cannot assert function ' + name +
|
||||
' on object that does not exist');
|
||||
assert(typeof obj[name] === 'function', name + ' is not a function');
|
||||
|
@ -351,6 +438,7 @@ function someInstructions(values) {
|
|||
config: values.config || {},
|
||||
models: values.models || [],
|
||||
dataSources: values.dataSources || { db: { connector: 'memory' } },
|
||||
middleware: values.middleware || { phases: [], middleware: [] },
|
||||
files: {
|
||||
boot: []
|
||||
}
|
||||
|
|
|
@ -2,4 +2,3 @@ process.bootFlags.push('barSyncLoaded');
|
|||
module.exports = function(app) {
|
||||
process.bootFlags.push('barSyncExecuted');
|
||||
};
|
||||
|
||||
|
|
|
@ -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) {
|
||||
var filePath = path.resolve(PATH, name);
|
||||
var filePath = this.resolve(name);
|
||||
fs.mkdirsSync(path.dirname(filePath));
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
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