From e2aff71bf90a0bb7787b5f568a32c17ebc51fb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 13 Nov 2014 15:31:35 +0100 Subject: [PATCH 1/6] Use `chai` instead of `must` As of 1.10, chai supports nonary assertions, there are no more reasons for using a different variant. --- package.json | 5 ++--- test/browser.test.js | 4 ++-- test/compiler.test.js | 12 ++++++------ test/executor.test.js | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 7eba26f..e5c8750 100644 --- a/package.json +++ b/package.json @@ -32,15 +32,14 @@ "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", "jshint": "^2.5.6", "loopback": "^2.5.0", "mocha": "^1.19.0", - "must": "^0.12.0", "supertest": "^0.14.0" } } diff --git a/test/browser.test.js b/test/browser.test.js index 985c26f..01b3132 100644 --- a/test/browser.test.js +++ b/test/browser.test.js @@ -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'); @@ -91,7 +91,7 @@ function browserifyTestApp(appDir, strategy, next) { var bundlePath = sandbox.resolve('browser-app-bundle.js'); var out = fs.createWriteStream(bundlePath); b.bundle().pipe(out); - + out.on('error', function(err) { return next(err); }); out.on('close', function() { next(null, bundlePath); diff --git a/test/compiler.test.js b/test/compiler.test.js index ba362bd..2bf2e0a 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -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'); @@ -379,7 +379,7 @@ describe('compiler', function() { var instructions = boot.compile(appdir.PATH); expect(instructions.files.boot).to.eql([initJs]); }); - + it('supports `bootDirs` option', function() { appdir.createConfigFilesSync(); var initJs = appdir.writeFileSync('custom-boot/init.js', @@ -390,7 +390,7 @@ describe('compiler', function() { }); expect(instructions.files.boot).to.eql([initJs]); }); - + it('supports `bootScripts` option', function() { appdir.createConfigFilesSync(); var initJs = appdir.writeFileSync('custom-boot/init.js', @@ -475,19 +475,19 @@ describe('compiler', function() { sourceFile: path.resolve(appdir.PATH, 'models', 'car.coffee') }); }); - + it('supports `modelSources` option', function() { appdir.createConfigFilesSync({}, {}, { Car: { dataSource: 'db' } }); appdir.writeConfigFileSync('custom-models/car.json', { name: 'Car' }); appdir.writeFileSync('custom-models/car.js', ''); - + var instructions = boot.compile({ appRootDir: appdir.PATH, modelSources: ['./custom-models'] }); - + expect(instructions.models).to.have.length(1); expect(instructions.models[0]).to.eql({ name: 'Car', diff --git a/test/executor.test.js b/test/executor.test.js index c4f248c..d1d6daf 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -2,7 +2,7 @@ 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'); From 83723379a232a803355436cf97b12ac330b1d792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 13 Nov 2014 15:33:23 +0100 Subject: [PATCH 2/6] Clean up .jshintrc jshint does not support `trailing` as of v1.5.0. Remove `maxlen` too, jscs provides better implementation. Fix definition of global mocha variables - mark them as immutable. --- .jshintrc | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.jshintrc b/.jshintrc index 3f23a40..9260a27 100644 --- a/.jshintrc +++ b/.jshintrc @@ -7,18 +7,16 @@ "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 } } From 5b5071864ba1686cde245a707f1d35848bd6ac86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 13 Nov 2014 15:54:59 +0100 Subject: [PATCH 3/6] Add jscs style check, fix violations found --- .jscsrc | 24 ++++++++++++++++++++++++ .jshintrc | 1 - index.js | 2 +- lib/compiler.js | 12 +++++++----- lib/config-loader.js | 12 ++++++------ lib/executor.js | 22 +++++++++++----------- package.json | 3 ++- test/browser.test.js | 2 +- test/compiler.test.js | 9 ++++++--- test/executor.test.js | 9 ++++----- test/fixtures/simple-app/boot/barSync.js | 1 - test/fixtures/simple-app/boot/foo.js | 2 +- 12 files changed, 63 insertions(+), 36 deletions(-) create mode 100644 .jscsrc diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 0000000..30b1a58 --- /dev/null +++ b/.jscsrc @@ -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/**" + ] +} diff --git a/.jshintrc b/.jshintrc index 9260a27..6f9fcc5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,7 +1,6 @@ { "node": true, "browser": true, -"camelcase" : true, "eqnull" : true, "indent": 2, "undef": true, diff --git a/index.js b/index.js index 4894277..7e17282 100644 --- a/index.js +++ b/index.js @@ -136,7 +136,7 @@ exports.compileToBrowserify = function(options, bundler) { addInstructionsToBrowserify(compile(options), bundler); }; -//-- undocumented low-level API --// +/*-- undocumented low-level API --*/ exports.ConfigLoader = ConfigLoader; exports.compile = compile; diff --git a/lib/compiler.js b/lib/compiler.js index 11f3a3b..2cef7d0 100644 --- a/lib/compiler.js +++ b/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 }; } @@ -74,7 +74,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 +143,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 +155,7 @@ function findScripts(dir) { function tryReadDir() { try { return fs.readdirSync.apply(fs, arguments); - } catch(e) { + } catch (e) { return []; } } @@ -309,7 +309,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); diff --git a/lib/config-loader.js b/lib/config-loader.js index 17834eb..b8a6dfe 100644 --- a/lib/config-loader.js +++ b/lib/config-loader.js @@ -103,7 +103,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); } @@ -163,7 +163,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 +189,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; } } diff --git a/lib/executor.js b/lib/executor.js index 16ca218..768b13d 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -45,10 +45,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 +69,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 +79,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 +98,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 +122,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 +198,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 +240,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; } diff --git a/package.json b/package.json index e5c8750..cd69585 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "main": "index.js", "browser": "browser.js", "scripts": { - "pretest": "jshint .", + "pretest": "jscs . && jshint .", "test": "mocha" }, "license": { @@ -37,6 +37,7 @@ "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", diff --git a/test/browser.test.js b/test/browser.test.js index 01b3132..3acd166 100644 --- a/test/browser.test.js +++ b/test/browser.test.js @@ -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; diff --git a/test/compiler.test.js b/test/compiler.test.js index 2bf2e0a..5df2ed5 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -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() { diff --git a/test/executor.test.js b/test/executor.test.js index d1d6daf..95ed9f4 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -171,7 +171,7 @@ 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); }); @@ -266,7 +266,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,12 +323,11 @@ 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(); }); }); - function assertValidDataSource(dataSource) { // has methods assert.isFunc(dataSource, 'createModel'); @@ -340,7 +339,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'); diff --git a/test/fixtures/simple-app/boot/barSync.js b/test/fixtures/simple-app/boot/barSync.js index 64c0b0b..4a38f80 100644 --- a/test/fixtures/simple-app/boot/barSync.js +++ b/test/fixtures/simple-app/boot/barSync.js @@ -2,4 +2,3 @@ process.bootFlags.push('barSyncLoaded'); module.exports = function(app) { process.bootFlags.push('barSyncExecuted'); }; - diff --git a/test/fixtures/simple-app/boot/foo.js b/test/fixtures/simple-app/boot/foo.js index 6641f03..e2fd7a5 100644 --- a/test/fixtures/simple-app/boot/foo.js +++ b/test/fixtures/simple-app/boot/foo.js @@ -1 +1 @@ -process.bootFlags.push('fooLoaded'); \ No newline at end of file +process.bootFlags.push('fooLoaded'); From 1114bc9227c3d13aeffc7f58352d10c3dfb8c85e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Wed, 12 Nov 2014 16:56:01 +0100 Subject: [PATCH 4/6] Load middleware and phases from `middleware.json` Sample JSON: { "routes:before": { "morgan": { "params": ["dev"] } }, "routes": { "loopback/server/middleware/rest": { } }, "subapps": { "./adminer": { }, } } The JSON file can be customized using the usual conventions: - middleware.local.{js|json} - middleware.{env}.{js|json} It is also possible to mount the same middleware in the same phase multiple times with different configuration. Example config: { "auth": { "oauth2": [ { "params": "first" }, { "params": "second" } ] }, }); --- index.js | 2 + lib/bundler.js | 12 ++ lib/compiler.js | 52 +++++++ lib/config-loader.js | 36 +++++ lib/executor.js | 25 ++++ test/compiler.test.js | 180 +++++++++++++++++++++++ test/executor.test.js | 81 ++++++++-- test/fixtures/simple-app/middleware.json | 7 + test/helpers/push-name-middleware.js | 8 + 9 files changed, 393 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/simple-app/middleware.json create mode 100644 test/helpers/push-name-middleware.js diff --git a/index.js b/index.js index 7e17282..3f4dae2 100644 --- a/index.js +++ b/index.js @@ -107,6 +107,8 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * `production`; however the applications are free to use any names. * @property {Array.} [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.} [bootDirs] List of directories where to look * for boot scripts. * @property {Array.} [bootScripts] List of script files to execute diff --git a/lib/bundler.js b/lib/bundler.js index 0e04525..908f772 100644 --- a/lib/bundler.js +++ b/lib/bundler.js @@ -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 diff --git a/lib/compiler.js b/lib/compiler.js index 2cef7d0..2f6427b 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -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 } @@ -338,3 +347,46 @@ 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 start = middleware.substring(0, 2); + var sourceFile = start !== './' && start !== '..' ? + middleware : + path.resolve(rootDir, middleware); + + var allConfigs = phaseConfig[middleware]; + if (!Array.isArray(allConfigs)) + allConfigs = [allConfigs]; + + allConfigs.forEach(function(config) { + var middlewareConfig = cloneDeep(config); + middlewareConfig.phase = phase; + + middlewareList.push({ + sourceFile: require.resolve(sourceFile), + config: middlewareConfig + }); + }); + }); + }); + + 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 + }; +} diff --git a/lib/config-loader.js b/lib/config-loader.js index b8a6dfe..e7d7095 100644 --- a/lib/config-loader.js +++ b/lib/config-loader.js @@ -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 --*/ /** @@ -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; diff --git a/lib/executor.js b/lib/executor.js index 768b13d..8218639 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -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 @@ -250,6 +251,30 @@ function tryRequire(modulePath) { } } +function setupMiddleware(app, instructions) { + if (!instructions.middleware) { + // the browserified client does not support middleware + return; + } + + 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', data.sourceFile); + var factory = require(data.sourceFile); + app.middlewareFromConfig(factory, data.config); + }); +} + function runBootScripts(app, instructions, callback) { runScripts(app, instructions.files.boot, callback); } diff --git a/test/compiler.test.js b/test/compiler.test.js index 5df2ed5..331c3d4 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -673,6 +673,186 @@ describe('compiler', function() { expect(instructions.config).to.not.have.property('modified'); }); }); + + describe('for middleware', function() { + beforeEach(function() { + appdir.createConfigFilesSync(); + }); + + it('emits middleware instructions', function() { + appdir.writeConfigFileSync('middleware.json', { + initial: { + }, + custom: { + 'loopback/server/middleware/url-not-found': { + params: 'some-config-data' + } + }, + }); + + var instructions = boot.compile(appdir.PATH); + + expect(instructions.middleware).to.eql({ + phases: ['initial', 'custom'], + middleware: [{ + sourceFile: + require.resolve('loopback/server/middleware/url-not-found'), + config: { + phase: 'custom', + params: 'some-config-data' + } + }] + }); + }); + + 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('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' + } + }, + ]); + }); + }); }); function getNameProperty(obj) { diff --git a/test/executor.test.js b/test/executor.test.js index 95ed9f4..e4ffd92 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -6,6 +6,7 @@ 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({ @@ -178,7 +186,6 @@ describe('executor', function() { 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() { @@ -326,6 +324,68 @@ describe('executor', function() { 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 (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) { @@ -350,6 +410,7 @@ function someInstructions(values) { config: values.config || {}, models: values.models || [], dataSources: values.dataSources || { db: { connector: 'memory' } }, + middleware: values.middleware || { phases: [], middleware: [] }, files: { boot: [] } diff --git a/test/fixtures/simple-app/middleware.json b/test/fixtures/simple-app/middleware.json new file mode 100644 index 0000000..3062e34 --- /dev/null +++ b/test/fixtures/simple-app/middleware.json @@ -0,0 +1,7 @@ +{ + "initial": { + "../../helpers/push-name-middleware": { + "params": "custom-middleware" + } + } +} diff --git a/test/helpers/push-name-middleware.js b/test/helpers/push-name-middleware.js new file mode 100644 index 0000000..f7e6d6c --- /dev/null +++ b/test/helpers/push-name-middleware.js @@ -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(); + }; +}; From 2f72006c88c4be5a4f26eff937ee368dbec96d03 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 21 Nov 2014 13:56:28 -0800 Subject: [PATCH 5/6] Implement shorthand notation for middleware paths When the middleware name (path) is in the format {module}#{filename}, loopback-boot resolves the path by trying multiple locations and using the first one that exists: - {module} and check the {filename} property of the exports -> e.g. loopback.rest - {module}/server/middleware/{filename} -> e.g. loopback/server/middleware/rest - {module}/middleware/{filename} -> e.g. loopback/middleware/rest Values in any other format will bypass this resolution algorithm and they will be used in the original form: - a full path in a module: loopback/server/middleware/rest - a relative path: ./middleware/custom, ./custom, ../logger - an absolute path: /usr/local/lib/node_modules/compression --- lib/compiler.js | 74 ++++++++- lib/executor.js | 11 +- test/compiler.test.js | 142 +++++++++++++++--- test/executor.test.js | 28 ++++ test/fixtures/simple-app/client/index.html | 10 ++ test/fixtures/simple-app/middleware/index.js | 8 + .../node_modules/my-module/index.js | 7 + .../node_modules/my-module/package.json | 7 + test/helpers/appdir.js | 6 +- 9 files changed, 262 insertions(+), 31 deletions(-) create mode 100644 test/fixtures/simple-app/client/index.html create mode 100644 test/fixtures/simple-app/middleware/index.js create mode 100644 test/fixtures/simple-app/node_modules/my-module/index.js create mode 100644 test/fixtures/simple-app/node_modules/my-module/package.json diff --git a/lib/compiler.js b/lib/compiler.js index 2f6427b..6cfd3ad 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -354,23 +354,24 @@ function buildMiddlewareInstructions(rootDir, config) { phasesNames.forEach(function(phase) { var phaseConfig = config[phase]; Object.keys(phaseConfig).forEach(function(middleware) { - var start = middleware.substring(0, 2); - var sourceFile = start !== './' && start !== '..' ? - middleware : - path.resolve(rootDir, 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; - middlewareList.push({ - sourceFile: require.resolve(sourceFile), + var item = { + sourceFile: resolved.sourceFile, config: middlewareConfig - }); + }; + if (resolved.fragment) { + item.fragment = resolved.fragment; + } + middlewareList.push(item); }); }); }); @@ -390,3 +391,60 @@ function buildMiddlewareInstructions(rootDir, config) { 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 . 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; +} diff --git a/lib/executor.js b/lib/executor.js index 8218639..c8e3865 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -257,7 +257,8 @@ function setupMiddleware(app, instructions) { return; } - var phases = instructions.middleware.phases; + // Phases can be empty + var phases = instructions.middleware.phases || []; assert(Array.isArray(phases), 'instructions.middleware.phases must be an array'); @@ -269,8 +270,14 @@ function setupMiddleware(app, instructions) { app.defineMiddlewarePhases(phases); middleware.forEach(function(data) { - debug('Configuring middleware %j', data.sourceFile); + 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); }); } diff --git a/test/compiler.test.js b/test/compiler.test.js index 331c3d4..a4f9a1d 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -675,34 +675,52 @@ describe('compiler', function() { }); describe('for middleware', function() { - beforeEach(function() { - appdir.createConfigFilesSync(); - }); - it('emits middleware instructions', function() { - appdir.writeConfigFileSync('middleware.json', { + function testMiddlewareRegistration(middlewareId, sourceFile) { + var json = { initial: { }, custom: { - 'loopback/server/middleware/url-not-found': { - params: 'some-config-data' - } - }, - }); + } + }; + + 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: - require.resolve('loopback/server/middleware/url-not-found'), - config: { - phase: 'custom', - params: 'some-config-data' + 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() { @@ -716,6 +734,20 @@ describe('compiler', function() { .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: { @@ -750,7 +782,7 @@ describe('compiler', function() { routes: { './middleware': { params: { - key: 'custom value', + key: 'custom value' } } } @@ -829,7 +861,7 @@ describe('compiler', function() { params: 'second' } ] - }, + } }); var instructions = boot.compile(appdir.PATH); @@ -849,9 +881,79 @@ describe('compiler', function() { 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)); + }); }); }); diff --git a/test/executor.test.js b/test/executor.test.js index e4ffd92..c6fd891 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -375,6 +375,34 @@ describe('executor', function() { }); }); + 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('\n\n\n' + + ' \n simple-app\n' + + '\n\n

simple-app

\n\n'); + done(); + }); + }); + it('configures middleware (end-to-end)', function(done) { boot.execute(app, simpleAppInstructions()); diff --git a/test/fixtures/simple-app/client/index.html b/test/fixtures/simple-app/client/index.html new file mode 100644 index 0000000..df535e6 --- /dev/null +++ b/test/fixtures/simple-app/client/index.html @@ -0,0 +1,10 @@ + + + + + simple-app + + +

simple-app

+ + \ No newline at end of file diff --git a/test/fixtures/simple-app/middleware/index.js b/test/fixtures/simple-app/middleware/index.js new file mode 100644 index 0000000..d2b20d9 --- /dev/null +++ b/test/fixtures/simple-app/middleware/index.js @@ -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(); + }; +}; diff --git a/test/fixtures/simple-app/node_modules/my-module/index.js b/test/fixtures/simple-app/node_modules/my-module/index.js new file mode 100644 index 0000000..5b64f75 --- /dev/null +++ b/test/fixtures/simple-app/node_modules/my-module/index.js @@ -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(); +}; diff --git a/test/fixtures/simple-app/node_modules/my-module/package.json b/test/fixtures/simple-app/node_modules/my-module/package.json new file mode 100644 index 0000000..07f8627 --- /dev/null +++ b/test/fixtures/simple-app/node_modules/my-module/package.json @@ -0,0 +1,7 @@ +{ + "name": "my-module", + "version": "1.0.0", + "description": "my-module", + "main": "index.js", + "license": "MIT" +} diff --git a/test/helpers/appdir.js b/test/helpers/appdir.js index 1a31be5..a52a606 100644 --- a/test/helpers/appdir.js +++ b/test/helpers/appdir.js @@ -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); +}; From 097fced31042e1f16efbf078118fab3be3c3b658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 27 Nov 2014 19:23:45 +0100 Subject: [PATCH 6/6] v2.4.0 --- CHANGES.md | 200 ++++++++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 191 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c62e362..fc9612b 100644 --- a/CHANGES.md +++ b/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! diff --git a/package.json b/package.json index cd69585..a5b39c0 100644 --- a/package.json +++ b/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",