From 2043c3998f1f5331cdce2642c7188f510404d01c Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Wed, 6 Aug 2014 14:47:56 +0200 Subject: [PATCH 01/17] Support loading of mixing See https://github.com/strongloop/loopback-datasource-juggler/pull/201 --- index.js | 2 ++ lib/compiler.js | 6 ++++++ lib/executor.js | 12 ++++++++++++ test/executor.test.js | 8 ++++++++ test/fixtures/simple-app/mixins/example.js | 5 +++++ 5 files changed, 33 insertions(+) create mode 100644 test/fixtures/simple-app/mixins/example.js diff --git a/index.js b/index.js index 4651138..6fc9e56 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 {Array.} [mixinSources] List of directories where to look + * for files containing model mixin definitions. * @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/compiler.js b/lib/compiler.js index d5d870a..aa24e20 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -58,6 +58,11 @@ module.exports = function compile(options) { var modelSources = options.modelSources || modelsMeta.sources || ['./models']; var modelInstructions = buildAllModelInstructions( modelsRootDir, modelsConfig, modelSources); + + var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins']; + mixinSources = mixinSources.map(function(dir) { + return path.resolve(appRootDir, dir); + }); // When executor passes the instruction to loopback methods, // loopback modifies the data. Since we are loading the data using `require`, @@ -66,6 +71,7 @@ module.exports = function compile(options) { config: appConfig, dataSources: dataSourcesConfig, models: modelInstructions, + mixinSources: mixinSources, files: { boot: bootScripts } diff --git a/lib/executor.js b/lib/executor.js index d04e833..3917db0 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -127,6 +127,7 @@ function setupDataSources(app, instructions) { } function setupModels(app, instructions) { + defineMixins(app, instructions); defineModels(app, instructions); instructions.models.forEach(function(data) { @@ -137,6 +138,17 @@ function setupModels(app, instructions) { }); } +function defineMixins(app, instructions) { + var mixinSources = instructions.mixinSources || []; + if (app.loopback.datasourceJuggler && + app.loopback.datasourceJuggler.mixins && mixinSources.length > 0) { + var mixins = app.loopback.datasourceJuggler.mixins; + mixinSources.forEach(function(dir) { + mixins.load(dir); + }); + } +} + function defineModels(app, instructions) { instructions.models.forEach(function(data) { var name = data.name; diff --git a/test/executor.test.js b/test/executor.test.js index 8c53715..cfc5002 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -168,6 +168,14 @@ describe('executor', function() { assert(process.loadedFooJS); delete process.loadedFooJS; }); + + it('should define `mixins/*` files', function() { + if (app.loopback.datasourceJuggler && app.loopback.datasourceJuggler.mixins) { + var mixins = app.loopback.datasourceJuggler.mixins; + expect(mixins.registry).to.have.property('Example'); + } + }); + }); describe('with PaaS and npm env variables', function() { diff --git a/test/fixtures/simple-app/mixins/example.js b/test/fixtures/simple-app/mixins/example.js new file mode 100644 index 0000000..d583bef --- /dev/null +++ b/test/fixtures/simple-app/mixins/example.js @@ -0,0 +1,5 @@ +module.exports = function(Model, options) { + + Model.exampleMixin = true; + +} \ No newline at end of file From 3387eae31cab2176216576ec979844b176913e94 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Fri, 8 Aug 2014 11:15:20 +0200 Subject: [PATCH 02/17] Implement mixin loading In accordance with the latest mixin cleanup in juggler. --- lib/compiler.js | 40 +++++++++++++++++++++++++++++++--------- lib/executor.js | 15 +++++++++------ test/compiler.test.js | 4 ++-- test/executor.test.js | 7 ++++--- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/lib/compiler.js b/lib/compiler.js index aa24e20..dde26d7 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -44,11 +44,12 @@ module.exports = function compile(options) { assertIsValidConfig('data source', dataSourcesConfig); // require directories - var bootDirs = options.bootDirs || []; // precedence - bootDirs = bootDirs.concat(path.join(appRootDir, 'boot')); + var bootSources = options.bootSources || []; // precedence + bootSources = bootSources.concat(path.join(appRootDir, 'boot')); var bootScripts = options.bootScripts || []; - bootDirs.forEach(function(dir) { + bootSources.forEach(function(dir) { + dir = path.resolve(dir); bootScripts = bootScripts.concat(findScripts(dir)); }); @@ -58,12 +59,10 @@ module.exports = function compile(options) { var modelSources = options.modelSources || modelsMeta.sources || ['./models']; var modelInstructions = buildAllModelInstructions( modelsRootDir, modelsConfig, modelSources); - + var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins']; - mixinSources = mixinSources.map(function(dir) { - return path.resolve(appRootDir, dir); - }); - + var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinSources, options); + // When executor passes the instruction to loopback methods, // loopback modifies the data. Since we are loading the data using `require`, // such change affects also code that calls `require` for the same file. @@ -71,13 +70,30 @@ module.exports = function compile(options) { config: appConfig, dataSources: dataSourcesConfig, models: modelInstructions, - mixinSources: mixinSources, + mixins: mixinInstructions, files: { boot: bootScripts } }); }; +function buildAllMixinInstructions(appRootDir, mixinSources, options) { + var files = options.mixins || []; + mixinSources.forEach(function(dir) { + dir = path.resolve(appRootDir, dir); + files = files.concat(findScripts(dir)); + }); + + var mixins = {}; + files.forEach(function(filepath) { + var ext = path.extname(filepath); + var name = normalizeMixinName(path.basename(filepath, ext)); + mixins[name] = filepath; + }); + + return mixins; +}; + function assertIsValidConfig(name, config) { if(config) { assert(typeof config === 'object', @@ -300,3 +316,9 @@ function loadModelDefinition(rootDir, jsonFile) { sourceFile: sourceFile }; } + +function normalizeMixinName(str) { // classify names: foo-bar => FooBar + str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); + str = str.replace(/(?:^|\s|-)\S/g, function(c){ return c.toUpperCase(); }); + return str.replace(/\s/g, ''); +} diff --git a/lib/executor.js b/lib/executor.js index 3917db0..345774b 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -139,12 +139,15 @@ function setupModels(app, instructions) { } function defineMixins(app, instructions) { - var mixinSources = instructions.mixinSources || []; - if (app.loopback.datasourceJuggler && - app.loopback.datasourceJuggler.mixins && mixinSources.length > 0) { - var mixins = app.loopback.datasourceJuggler.mixins; - mixinSources.forEach(function(dir) { - mixins.load(dir); + var modelBuilder = app.loopback.modelBuilder; + var mixins = instructions.mixins || {}; + var mixinNames = Object.keys(mixins); + if (modelBuilder.mixins && mixinNames.length > 0) { + mixinNames.forEach(function(name) { + var mixin = tryRequire(mixins[name]); + if (typeof mixin === 'function') { + modelBuilder.mixins.define(name, mixin); + } }); } } diff --git a/test/compiler.test.js b/test/compiler.test.js index 45e17ec..b1153b0 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -380,13 +380,13 @@ describe('compiler', function() { expect(instructions.files.boot).to.eql([initJs]); }); - it('supports `bootDirs` option', function() { + it('supports `bootSources` option', function() { appdir.createConfigFilesSync(); var initJs = appdir.writeFileSync('custom-boot/init.js', 'module.exports = function(app) { app.fnCalled = true; };'); var instructions = boot.compile({ appRootDir: appdir.PATH, - bootDirs: [path.dirname(initJs)] + bootSources: [path.dirname(initJs)] }); expect(instructions.files.boot).to.eql([initJs]); }); diff --git a/test/executor.test.js b/test/executor.test.js index cfc5002..13921b7 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -170,9 +170,10 @@ describe('executor', function() { }); it('should define `mixins/*` files', function() { - if (app.loopback.datasourceJuggler && app.loopback.datasourceJuggler.mixins) { - var mixins = app.loopback.datasourceJuggler.mixins; - expect(mixins.registry).to.have.property('Example'); + if (app.loopback.modelBuilder.mixins) { + var modelBuilder = app.loopback.modelBuilder; + var registry = modelBuilder.mixins.mixins; + expect(registry).to.have.property('Example'); } }); From a2b44065d0c02d48662ee8cdc0c0d9151f9419ab Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Mon, 18 Aug 2014 08:36:03 +0200 Subject: [PATCH 03/17] Removed normalization (see: juggler) --- lib/compiler.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/compiler.js b/lib/compiler.js index dde26d7..ab020e7 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -59,7 +59,7 @@ module.exports = function compile(options) { var modelSources = options.modelSources || modelsMeta.sources || ['./models']; var modelInstructions = buildAllModelInstructions( modelsRootDir, modelsConfig, modelSources); - + var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins']; var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinSources, options); @@ -87,7 +87,7 @@ function buildAllMixinInstructions(appRootDir, mixinSources, options) { var mixins = {}; files.forEach(function(filepath) { var ext = path.extname(filepath); - var name = normalizeMixinName(path.basename(filepath, ext)); + var name = path.basename(filepath, ext); mixins[name] = filepath; }); @@ -316,9 +316,3 @@ function loadModelDefinition(rootDir, jsonFile) { sourceFile: sourceFile }; } - -function normalizeMixinName(str) { // classify names: foo-bar => FooBar - str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); - str = str.replace(/(?:^|\s|-)\S/g, function(c){ return c.toUpperCase(); }); - return str.replace(/\s/g, ''); -} From 4cc7cbbfadd549ec44a139778d774b04bd8e7302 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Wed, 20 Aug 2014 17:10:23 +0200 Subject: [PATCH 04/17] Perform normalization --- index.js | 2 +- lib/compiler.js | 8 +++++++- test/executor.test.js | 1 + test/fixtures/simple-app/mixins/time-stamps.js | 5 +++++ 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/simple-app/mixins/time-stamps.js diff --git a/index.js b/index.js index 6fc9e56..d3574f9 100644 --- a/index.js +++ b/index.js @@ -109,7 +109,7 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * for files containing model definitions. * @property {Array.} [mixinSources] List of directories where to look * for files containing model mixin definitions. - * @property {Array.} [bootDirs] List of directories where to look + * @property {Array.} [bootSources] List of directories where to look * for boot scripts. * @property {Array.} [bootScripts] List of script files to execute * on boot. diff --git a/lib/compiler.js b/lib/compiler.js index ab020e7..8ca8384 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -87,7 +87,7 @@ function buildAllMixinInstructions(appRootDir, mixinSources, options) { var mixins = {}; files.forEach(function(filepath) { var ext = path.extname(filepath); - var name = path.basename(filepath, ext); + var name = normalizeMixinName(path.basename(filepath, ext)); mixins[name] = filepath; }); @@ -316,3 +316,9 @@ function loadModelDefinition(rootDir, jsonFile) { sourceFile: sourceFile }; } + +function normalizeMixinName(str) { // classify names: foo-bar => FooBar + str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); + str = str.replace(/(?:^|\s|-)\S/g, function(c){ return c.toUpperCase(); }); + return str.replace(/\s/g, ''); +} diff --git a/test/executor.test.js b/test/executor.test.js index 13921b7..d861022 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -174,6 +174,7 @@ describe('executor', function() { var modelBuilder = app.loopback.modelBuilder; var registry = modelBuilder.mixins.mixins; expect(registry).to.have.property('Example'); + expect(registry).to.have.property('TimeStamps'); } }); diff --git a/test/fixtures/simple-app/mixins/time-stamps.js b/test/fixtures/simple-app/mixins/time-stamps.js new file mode 100644 index 0000000..6200848 --- /dev/null +++ b/test/fixtures/simple-app/mixins/time-stamps.js @@ -0,0 +1,5 @@ +module.exports = function(Model, options) { + + Model.timeStampsMixin = true; + +} \ No newline at end of file From 668bfe209ba5ccc122298bfd884e32ee63186c1c Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Wed, 20 Aug 2014 17:25:53 +0200 Subject: [PATCH 05/17] Minor fixes --- lib/executor.js | 8 +++++++- test/executor.test.js | 10 ++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/executor.js b/lib/executor.js index 345774b..f850a58 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -140,13 +140,19 @@ function setupModels(app, instructions) { function defineMixins(app, instructions) { var modelBuilder = app.loopback.modelBuilder; + var BaseClass = app.loopback.Model; var mixins = instructions.mixins || {}; var mixinNames = Object.keys(mixins); if (modelBuilder.mixins && mixinNames.length > 0) { mixinNames.forEach(function(name) { var mixin = tryRequire(mixins[name]); - if (typeof mixin === 'function') { + if (typeof mixin === 'function' || mixin.prototype instanceof BaseClass) { + debug('Configuring mixin %s', name); modelBuilder.mixins.define(name, mixin); + } else { + debug('Skipping mixin file %s - `module.exports` is not a function' + + ' or Loopback model', + mixins[name]); } }); } diff --git a/test/executor.test.js b/test/executor.test.js index d861022..f3010ae 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -170,12 +170,10 @@ describe('executor', function() { }); it('should define `mixins/*` files', function() { - if (app.loopback.modelBuilder.mixins) { - var modelBuilder = app.loopback.modelBuilder; - var registry = modelBuilder.mixins.mixins; - expect(registry).to.have.property('Example'); - expect(registry).to.have.property('TimeStamps'); - } + var modelBuilder = app.loopback.modelBuilder; + var registry = modelBuilder.mixins.mixins; + expect(registry).to.have.property('Example'); + expect(registry).to.have.property('TimeStamps'); }); }); From 6b357fefd0fff5c6662939c65569978a86d67231 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Thu, 9 Oct 2014 21:25:00 +0200 Subject: [PATCH 06/17] bootSources => bootDirs --- index.js | 2 +- lib/compiler.js | 14 +++++++------- test/compiler.test.js | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index d3574f9..6fc9e56 100644 --- a/index.js +++ b/index.js @@ -109,7 +109,7 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * for files containing model definitions. * @property {Array.} [mixinSources] List of directories where to look * for files containing model mixin definitions. - * @property {Array.} [bootSources] List of directories where to look + * @property {Array.} [bootDirs] List of directories where to look * for boot scripts. * @property {Array.} [bootScripts] List of script files to execute * on boot. diff --git a/lib/compiler.js b/lib/compiler.js index 8ca8384..186fc73 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -44,11 +44,11 @@ module.exports = function compile(options) { assertIsValidConfig('data source', dataSourcesConfig); // require directories - var bootSources = options.bootSources || []; // precedence - bootSources = bootSources.concat(path.join(appRootDir, 'boot')); + var bootDirs = options.bootDirs || []; // precedence + bootDirs = bootDirs.concat(path.join(appRootDir, 'boot')); var bootScripts = options.bootScripts || []; - bootSources.forEach(function(dir) { + bootDirs.forEach(function(dir) { dir = path.resolve(dir); bootScripts = bootScripts.concat(findScripts(dir)); }); @@ -60,8 +60,8 @@ module.exports = function compile(options) { var modelInstructions = buildAllModelInstructions( modelsRootDir, modelsConfig, modelSources); - var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins']; - var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinSources, options); + var mixinDirs = options.mixinDirs || modelsMeta.mixins || ['./mixins']; + var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinDirs, options); // When executor passes the instruction to loopback methods, // loopback modifies the data. Since we are loading the data using `require`, @@ -77,9 +77,9 @@ module.exports = function compile(options) { }); }; -function buildAllMixinInstructions(appRootDir, mixinSources, options) { +function buildAllMixinInstructions(appRootDir, mixinDirs, options) { var files = options.mixins || []; - mixinSources.forEach(function(dir) { + mixinDirs.forEach(function(dir) { dir = path.resolve(appRootDir, dir); files = files.concat(findScripts(dir)); }); diff --git a/test/compiler.test.js b/test/compiler.test.js index b1153b0..45e17ec 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -380,13 +380,13 @@ describe('compiler', function() { expect(instructions.files.boot).to.eql([initJs]); }); - it('supports `bootSources` option', function() { + it('supports `bootDirs` option', function() { appdir.createConfigFilesSync(); var initJs = appdir.writeFileSync('custom-boot/init.js', 'module.exports = function(app) { app.fnCalled = true; };'); var instructions = boot.compile({ appRootDir: appdir.PATH, - bootSources: [path.dirname(initJs)] + bootDirs: [path.dirname(initJs)] }); expect(instructions.files.boot).to.eql([initJs]); }); From fa9703c6b60bbe4230b000cb0206810c1966dc86 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Thu, 9 Oct 2014 22:23:58 +0200 Subject: [PATCH 07/17] Fix remaining PR issues --- index.js | 2 ++ lib/compiler.js | 20 +++++++++++---- lib/executor.js | 5 ++-- package.json | 2 +- test/compiler.test.js | 34 ++++++++++++++++++++++++++ test/executor.test.js | 1 + test/fixtures/simple-app/mixins/Foo.js | 10 ++++++++ 7 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/simple-app/mixins/Foo.js diff --git a/index.js b/index.js index 6fc9e56..5d165cc 100644 --- a/index.js +++ b/index.js @@ -113,6 +113,8 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * for boot scripts. * @property {Array.} [bootScripts] List of script files to execute * on boot. + * @property {String|Function|Boolean} [normalization] Mixin normalization format + * can be false, 'none', 'classify', 'dasherize' - defaults to 'classify'. * @end * * @header boot(app, [options]) diff --git a/lib/compiler.js b/lib/compiler.js index 186fc73..70bb9c4 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -87,7 +87,7 @@ function buildAllMixinInstructions(appRootDir, mixinDirs, options) { var mixins = {}; files.forEach(function(filepath) { var ext = path.extname(filepath); - var name = normalizeMixinName(path.basename(filepath, ext)); + var name = normalizeMixinName(path.basename(filepath, ext), options); mixins[name] = filepath; }); @@ -317,8 +317,18 @@ function loadModelDefinition(rootDir, jsonFile) { }; } -function normalizeMixinName(str) { // classify names: foo-bar => FooBar - str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); - str = str.replace(/(?:^|\s|-)\S/g, function(c){ return c.toUpperCase(); }); - return str.replace(/\s/g, ''); +function normalizeMixinName(str, options) { // classify names: foo-bar => FooBar + var normalization = options.normalization; + if (normalization === false || normalization === 'none') return str; + if (normalization === 'dasherize') { + str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); + str = str.replace(/\s+/g, '-'); + } else if (typeof normalization === 'function') { + str = normalization(str); + } else { // classify + str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); + str = str.replace(/(?:^|\s|-)\S/g, function(c){ return c.toUpperCase(); }); + str = str.replace(/\s+/g, ''); + } + return str; } diff --git a/lib/executor.js b/lib/executor.js index f850a58..56cdea0 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -147,8 +147,9 @@ function defineMixins(app, instructions) { mixinNames.forEach(function(name) { var mixin = tryRequire(mixins[name]); if (typeof mixin === 'function' || mixin.prototype instanceof BaseClass) { - debug('Configuring mixin %s', name); - modelBuilder.mixins.define(name, mixin); + var mixinName = mixin.name || mixin.mixinName || name; + debug('Configuring mixin %s', mixinName); + modelBuilder.mixins.define(mixinName, mixin); } else { debug('Skipping mixin file %s - `module.exports` is not a function' + ' or Loopback model', diff --git a/package.json b/package.json index 9348dc3..e8d14ff 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "browserify": "^4.1.8", "fs-extra": "^0.10.0", "jshint": "^2.5.6", - "loopback": "^1.5.0", + "loopback": "^2.2.0", "mocha": "^1.19.0", "must": "^0.12.0", "supertest": "^0.13.0" diff --git a/test/compiler.test.js b/test/compiler.test.js index 45e17ec..04d329f 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -620,6 +620,40 @@ describe('compiler', function() { instructions = boot.compile(appdir.PATH); expect(instructions.config).to.not.have.property('modified'); }); + + it('support mixin name normalization - classify (default)', function() { + var options = { appRootDir: SIMPLE_APP }; + var instructions = boot.compile(options); + expect(instructions.mixins).to.have.property('Example'); + expect(instructions.mixins).to.have.property('Foo'); + expect(instructions.mixins).to.have.property('TimeStamps'); + }); + + it('support mixin name normalization - dasherize', function() { + var options = { appRootDir: SIMPLE_APP, normalization: 'dasherize' }; + var instructions = boot.compile(options); + expect(instructions.mixins).to.have.property('example'); + expect(instructions.mixins).to.have.property('foo'); + expect(instructions.mixins).to.have.property('time-stamps'); + }); + + it('support mixin name normalization - custom function', function() { + var normalize = function(name) { return name.toUpperCase(); }; + var options = { appRootDir: SIMPLE_APP, normalization: normalize }; + var instructions = boot.compile(options); + expect(instructions.mixins).to.have.property('EXAMPLE'); + expect(instructions.mixins).to.have.property('FOO'); + expect(instructions.mixins).to.have.property('TIME-STAMPS'); + }); + + it('support skip mixin name normalization - none', function() { + var options = { appRootDir: SIMPLE_APP, normalization: false }; + var instructions = boot.compile(options); + expect(instructions.mixins).to.have.property('example'); + expect(instructions.mixins).to.have.property('Foo'); + expect(instructions.mixins).to.have.property('time-stamps'); + }); + }); }); diff --git a/test/executor.test.js b/test/executor.test.js index f3010ae..4ccb393 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -174,6 +174,7 @@ describe('executor', function() { var registry = modelBuilder.mixins.mixins; expect(registry).to.have.property('Example'); expect(registry).to.have.property('TimeStamps'); + expect(registry).to.have.property('bar'); // mixinName }); }); diff --git a/test/fixtures/simple-app/mixins/Foo.js b/test/fixtures/simple-app/mixins/Foo.js new file mode 100644 index 0000000..0123cc5 --- /dev/null +++ b/test/fixtures/simple-app/mixins/Foo.js @@ -0,0 +1,10 @@ +// When you create a named function, its name will be used +// as the mixin name - alternatively, set mixin.mixinName. + +var mixin = function bar(Model, options) { + + Model.barMixin = true; + +}; + +module.exports = mixin; \ No newline at end of file From 6191de270eabebeca74ff282da2b6ea830390251 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Fri, 10 Oct 2014 15:15:23 +0200 Subject: [PATCH 08/17] Fix mixinDirs vs. mixinSources, minor touchups --- index.js | 2 +- lib/compiler.js | 10 +++++----- test/compiler.test.js | 16 ++++++++++++---- test/fixtures/mixins/other.js | 5 +++++ 4 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/mixins/other.js diff --git a/index.js b/index.js index 5d165cc..61f5e09 100644 --- a/index.js +++ b/index.js @@ -108,7 +108,7 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * @property {Array.} [modelSources] List of directories where to look * for files containing model definitions. * @property {Array.} [mixinSources] List of directories where to look - * for files containing model mixin definitions. + * for files containing model mixin definitions. - defaults to ['./mixins'] * @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/compiler.js b/lib/compiler.js index 70bb9c4..e716b97 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -60,8 +60,8 @@ module.exports = function compile(options) { var modelInstructions = buildAllModelInstructions( modelsRootDir, modelsConfig, modelSources); - var mixinDirs = options.mixinDirs || modelsMeta.mixins || ['./mixins']; - var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinDirs, options); + var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins']; + var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinSources, options); // When executor passes the instruction to loopback methods, // loopback modifies the data. Since we are loading the data using `require`, @@ -77,9 +77,9 @@ module.exports = function compile(options) { }); }; -function buildAllMixinInstructions(appRootDir, mixinDirs, options) { +function buildAllMixinInstructions(appRootDir, mixinSources, options) { var files = options.mixins || []; - mixinDirs.forEach(function(dir) { + mixinSources.forEach(function(dir) { dir = path.resolve(appRootDir, dir); files = files.concat(findScripts(dir)); }); @@ -317,7 +317,7 @@ function loadModelDefinition(rootDir, jsonFile) { }; } -function normalizeMixinName(str, options) { // classify names: foo-bar => FooBar +function normalizeMixinName(str, options) { var normalization = options.normalization; if (normalization === false || normalization === 'none') return str; if (normalization === 'dasherize') { diff --git a/test/compiler.test.js b/test/compiler.test.js index 04d329f..1e5ece6 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -6,6 +6,7 @@ var sandbox = require('./helpers/sandbox'); var appdir = require('./helpers/appdir'); var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app'); +var MIXIN_SOURCES = path.join(__dirname, 'fixtures', 'mixins'); describe('compiler', function() { beforeEach(sandbox.reset); @@ -621,7 +622,14 @@ describe('compiler', function() { expect(instructions.config).to.not.have.property('modified'); }); - it('support mixin name normalization - classify (default)', function() { + it('has a mixinSources option', function() { + var options = { appRootDir: SIMPLE_APP, mixinSources: [MIXIN_SOURCES] }; + var instructions = boot.compile(options); + expect(instructions.mixins).to.not.have.property('TimeStamps'); + expect(instructions.mixins).to.have.property('Other'); + }); + + it('supports mixin name normalization - classify (default)', function() { var options = { appRootDir: SIMPLE_APP }; var instructions = boot.compile(options); expect(instructions.mixins).to.have.property('Example'); @@ -629,7 +637,7 @@ describe('compiler', function() { expect(instructions.mixins).to.have.property('TimeStamps'); }); - it('support mixin name normalization - dasherize', function() { + it('supports mixin name normalization - dasherize', function() { var options = { appRootDir: SIMPLE_APP, normalization: 'dasherize' }; var instructions = boot.compile(options); expect(instructions.mixins).to.have.property('example'); @@ -637,7 +645,7 @@ describe('compiler', function() { expect(instructions.mixins).to.have.property('time-stamps'); }); - it('support mixin name normalization - custom function', function() { + it('supports mixin name normalization - custom function', function() { var normalize = function(name) { return name.toUpperCase(); }; var options = { appRootDir: SIMPLE_APP, normalization: normalize }; var instructions = boot.compile(options); @@ -646,7 +654,7 @@ describe('compiler', function() { expect(instructions.mixins).to.have.property('TIME-STAMPS'); }); - it('support skip mixin name normalization - none', function() { + it('supports skip mixin name normalization - none', function() { var options = { appRootDir: SIMPLE_APP, normalization: false }; var instructions = boot.compile(options); expect(instructions.mixins).to.have.property('example'); diff --git a/test/fixtures/mixins/other.js b/test/fixtures/mixins/other.js new file mode 100644 index 0000000..67744b8 --- /dev/null +++ b/test/fixtures/mixins/other.js @@ -0,0 +1,5 @@ +module.exports = function(Model, options) { + + Model.otherMixin = true; + +} \ No newline at end of file From 2d6d653c2dc73ec2c4193328ff28fefcf2a7c2f6 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Fri, 10 Oct 2014 15:22:49 +0200 Subject: [PATCH 09/17] Fix conflict --- index.js | 2 ++ lib/compiler.js | 6 ++++++ lib/executor.js | 12 ++++++++++++ test/executor.test.js | 17 ++++++++++++++++- test/fixtures/simple-app/mixins/example.js | 5 +++++ 5 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/simple-app/mixins/example.js diff --git a/index.js b/index.js index 97e56dc..5a2c011 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 {Array.} [mixinSources] List of directories where to look + * for files containing model mixin definitions. * @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/compiler.js b/lib/compiler.js index d5d870a..aa24e20 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -58,6 +58,11 @@ module.exports = function compile(options) { var modelSources = options.modelSources || modelsMeta.sources || ['./models']; var modelInstructions = buildAllModelInstructions( modelsRootDir, modelsConfig, modelSources); + + var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins']; + mixinSources = mixinSources.map(function(dir) { + return path.resolve(appRootDir, dir); + }); // When executor passes the instruction to loopback methods, // loopback modifies the data. Since we are loading the data using `require`, @@ -66,6 +71,7 @@ module.exports = function compile(options) { config: appConfig, dataSources: dataSourcesConfig, models: modelInstructions, + mixinSources: mixinSources, files: { boot: bootScripts } diff --git a/lib/executor.js b/lib/executor.js index ceef61f..00dcfe4 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -135,6 +135,7 @@ function setupDataSources(app, instructions) { } function setupModels(app, instructions) { + defineMixins(app, instructions); defineModels(app, instructions); instructions.models.forEach(function(data) { @@ -145,6 +146,17 @@ function setupModels(app, instructions) { }); } +function defineMixins(app, instructions) { + var mixinSources = instructions.mixinSources || []; + if (app.loopback.datasourceJuggler && + app.loopback.datasourceJuggler.mixins && mixinSources.length > 0) { + var mixins = app.loopback.datasourceJuggler.mixins; + mixinSources.forEach(function(dir) { + mixins.load(dir); + }); + } +} + function defineModels(app, instructions) { instructions.models.forEach(function(data) { var name = data.name; diff --git a/test/executor.test.js b/test/executor.test.js index 4a63929..d2bbff5 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -193,6 +193,13 @@ describe('executor', function() { done(); }, 10); }); + + it('should define `mixins/*` files', function() { + if (app.loopback.datasourceJuggler && app.loopback.datasourceJuggler.mixins) { + var mixins = app.loopback.datasourceJuggler.mixins; + expect(mixins.registry).to.have.property('Example'); + } + }); }); describe('with boot with callback', function() { @@ -217,7 +224,15 @@ describe('executor', function() { done(); }); }); - + + it('should define `mixins/*` files', function() { + if (app.loopback.datasourceJuggler && app.loopback.datasourceJuggler.mixins) { + var mixins = app.loopback.datasourceJuggler.mixins; + expect(mixins.registry).to.have.property('Example'); + } + }); + +>>>>>>> Support loading of mixing }); describe('with PaaS and npm env variables', function() { diff --git a/test/fixtures/simple-app/mixins/example.js b/test/fixtures/simple-app/mixins/example.js new file mode 100644 index 0000000..d583bef --- /dev/null +++ b/test/fixtures/simple-app/mixins/example.js @@ -0,0 +1,5 @@ +module.exports = function(Model, options) { + + Model.exampleMixin = true; + +} \ No newline at end of file From b5067a0c944356016d46a223e9b3c9dc0e7aa016 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Fri, 8 Aug 2014 11:15:20 +0200 Subject: [PATCH 10/17] Implement mixin loading In accordance with the latest mixin cleanup in juggler. --- lib/compiler.js | 40 +++++++++++++++++++++++++++++++--------- lib/executor.js | 15 +++++++++------ test/compiler.test.js | 4 ++-- test/executor.test.js | 7 ++++--- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/lib/compiler.js b/lib/compiler.js index aa24e20..dde26d7 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -44,11 +44,12 @@ module.exports = function compile(options) { assertIsValidConfig('data source', dataSourcesConfig); // require directories - var bootDirs = options.bootDirs || []; // precedence - bootDirs = bootDirs.concat(path.join(appRootDir, 'boot')); + var bootSources = options.bootSources || []; // precedence + bootSources = bootSources.concat(path.join(appRootDir, 'boot')); var bootScripts = options.bootScripts || []; - bootDirs.forEach(function(dir) { + bootSources.forEach(function(dir) { + dir = path.resolve(dir); bootScripts = bootScripts.concat(findScripts(dir)); }); @@ -58,12 +59,10 @@ module.exports = function compile(options) { var modelSources = options.modelSources || modelsMeta.sources || ['./models']; var modelInstructions = buildAllModelInstructions( modelsRootDir, modelsConfig, modelSources); - + var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins']; - mixinSources = mixinSources.map(function(dir) { - return path.resolve(appRootDir, dir); - }); - + var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinSources, options); + // When executor passes the instruction to loopback methods, // loopback modifies the data. Since we are loading the data using `require`, // such change affects also code that calls `require` for the same file. @@ -71,13 +70,30 @@ module.exports = function compile(options) { config: appConfig, dataSources: dataSourcesConfig, models: modelInstructions, - mixinSources: mixinSources, + mixins: mixinInstructions, files: { boot: bootScripts } }); }; +function buildAllMixinInstructions(appRootDir, mixinSources, options) { + var files = options.mixins || []; + mixinSources.forEach(function(dir) { + dir = path.resolve(appRootDir, dir); + files = files.concat(findScripts(dir)); + }); + + var mixins = {}; + files.forEach(function(filepath) { + var ext = path.extname(filepath); + var name = normalizeMixinName(path.basename(filepath, ext)); + mixins[name] = filepath; + }); + + return mixins; +}; + function assertIsValidConfig(name, config) { if(config) { assert(typeof config === 'object', @@ -300,3 +316,9 @@ function loadModelDefinition(rootDir, jsonFile) { sourceFile: sourceFile }; } + +function normalizeMixinName(str) { // classify names: foo-bar => FooBar + str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); + str = str.replace(/(?:^|\s|-)\S/g, function(c){ return c.toUpperCase(); }); + return str.replace(/\s/g, ''); +} diff --git a/lib/executor.js b/lib/executor.js index 00dcfe4..68f872f 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -147,12 +147,15 @@ function setupModels(app, instructions) { } function defineMixins(app, instructions) { - var mixinSources = instructions.mixinSources || []; - if (app.loopback.datasourceJuggler && - app.loopback.datasourceJuggler.mixins && mixinSources.length > 0) { - var mixins = app.loopback.datasourceJuggler.mixins; - mixinSources.forEach(function(dir) { - mixins.load(dir); + var modelBuilder = app.loopback.modelBuilder; + var mixins = instructions.mixins || {}; + var mixinNames = Object.keys(mixins); + if (modelBuilder.mixins && mixinNames.length > 0) { + mixinNames.forEach(function(name) { + var mixin = tryRequire(mixins[name]); + if (typeof mixin === 'function') { + modelBuilder.mixins.define(name, mixin); + } }); } } diff --git a/test/compiler.test.js b/test/compiler.test.js index 45e17ec..b1153b0 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -380,13 +380,13 @@ describe('compiler', function() { expect(instructions.files.boot).to.eql([initJs]); }); - it('supports `bootDirs` option', function() { + it('supports `bootSources` option', function() { appdir.createConfigFilesSync(); var initJs = appdir.writeFileSync('custom-boot/init.js', 'module.exports = function(app) { app.fnCalled = true; };'); var instructions = boot.compile({ appRootDir: appdir.PATH, - bootDirs: [path.dirname(initJs)] + bootSources: [path.dirname(initJs)] }); expect(instructions.files.boot).to.eql([initJs]); }); diff --git a/test/executor.test.js b/test/executor.test.js index d2bbff5..0562e14 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -226,9 +226,10 @@ describe('executor', function() { }); it('should define `mixins/*` files', function() { - if (app.loopback.datasourceJuggler && app.loopback.datasourceJuggler.mixins) { - var mixins = app.loopback.datasourceJuggler.mixins; - expect(mixins.registry).to.have.property('Example'); + if (app.loopback.modelBuilder.mixins) { + var modelBuilder = app.loopback.modelBuilder; + var registry = modelBuilder.mixins.mixins; + expect(registry).to.have.property('Example'); } }); From 3896756d2675cbc6d4af10d8cfb46cccf7ab1641 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Mon, 18 Aug 2014 08:36:03 +0200 Subject: [PATCH 11/17] Removed normalization (see: juggler) --- lib/compiler.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/compiler.js b/lib/compiler.js index dde26d7..ab020e7 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -59,7 +59,7 @@ module.exports = function compile(options) { var modelSources = options.modelSources || modelsMeta.sources || ['./models']; var modelInstructions = buildAllModelInstructions( modelsRootDir, modelsConfig, modelSources); - + var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins']; var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinSources, options); @@ -87,7 +87,7 @@ function buildAllMixinInstructions(appRootDir, mixinSources, options) { var mixins = {}; files.forEach(function(filepath) { var ext = path.extname(filepath); - var name = normalizeMixinName(path.basename(filepath, ext)); + var name = path.basename(filepath, ext); mixins[name] = filepath; }); @@ -316,9 +316,3 @@ function loadModelDefinition(rootDir, jsonFile) { sourceFile: sourceFile }; } - -function normalizeMixinName(str) { // classify names: foo-bar => FooBar - str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); - str = str.replace(/(?:^|\s|-)\S/g, function(c){ return c.toUpperCase(); }); - return str.replace(/\s/g, ''); -} From 6753aa18fcf21d90295cce69ecf2d72e73babe42 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Wed, 20 Aug 2014 17:10:23 +0200 Subject: [PATCH 12/17] Perform normalization --- index.js | 2 +- lib/compiler.js | 8 +++++++- test/executor.test.js | 1 + test/fixtures/simple-app/mixins/time-stamps.js | 5 +++++ 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/simple-app/mixins/time-stamps.js diff --git a/index.js b/index.js index 5a2c011..522d5f1 100644 --- a/index.js +++ b/index.js @@ -109,7 +109,7 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * for files containing model definitions. * @property {Array.} [mixinSources] List of directories where to look * for files containing model mixin definitions. - * @property {Array.} [bootDirs] List of directories where to look + * @property {Array.} [bootSources] List of directories where to look * for boot scripts. * @property {Array.} [bootScripts] List of script files to execute * on boot. diff --git a/lib/compiler.js b/lib/compiler.js index ab020e7..8ca8384 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -87,7 +87,7 @@ function buildAllMixinInstructions(appRootDir, mixinSources, options) { var mixins = {}; files.forEach(function(filepath) { var ext = path.extname(filepath); - var name = path.basename(filepath, ext); + var name = normalizeMixinName(path.basename(filepath, ext)); mixins[name] = filepath; }); @@ -316,3 +316,9 @@ function loadModelDefinition(rootDir, jsonFile) { sourceFile: sourceFile }; } + +function normalizeMixinName(str) { // classify names: foo-bar => FooBar + str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); + str = str.replace(/(?:^|\s|-)\S/g, function(c){ return c.toUpperCase(); }); + return str.replace(/\s/g, ''); +} diff --git a/test/executor.test.js b/test/executor.test.js index 0562e14..c7e79e8 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -230,6 +230,7 @@ describe('executor', function() { var modelBuilder = app.loopback.modelBuilder; var registry = modelBuilder.mixins.mixins; expect(registry).to.have.property('Example'); + expect(registry).to.have.property('TimeStamps'); } }); diff --git a/test/fixtures/simple-app/mixins/time-stamps.js b/test/fixtures/simple-app/mixins/time-stamps.js new file mode 100644 index 0000000..6200848 --- /dev/null +++ b/test/fixtures/simple-app/mixins/time-stamps.js @@ -0,0 +1,5 @@ +module.exports = function(Model, options) { + + Model.timeStampsMixin = true; + +} \ No newline at end of file From f2cee03654df26b48b02930b60aa9a9a9b76728f Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Wed, 20 Aug 2014 17:25:53 +0200 Subject: [PATCH 13/17] Minor fixes --- lib/executor.js | 8 +++++++- test/executor.test.js | 10 ++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/executor.js b/lib/executor.js index 68f872f..1057e12 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -148,13 +148,19 @@ function setupModels(app, instructions) { function defineMixins(app, instructions) { var modelBuilder = app.loopback.modelBuilder; + var BaseClass = app.loopback.Model; var mixins = instructions.mixins || {}; var mixinNames = Object.keys(mixins); if (modelBuilder.mixins && mixinNames.length > 0) { mixinNames.forEach(function(name) { var mixin = tryRequire(mixins[name]); - if (typeof mixin === 'function') { + if (typeof mixin === 'function' || mixin.prototype instanceof BaseClass) { + debug('Configuring mixin %s', name); modelBuilder.mixins.define(name, mixin); + } else { + debug('Skipping mixin file %s - `module.exports` is not a function' + + ' or Loopback model', + mixins[name]); } }); } diff --git a/test/executor.test.js b/test/executor.test.js index c7e79e8..d28e396 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -226,12 +226,10 @@ describe('executor', function() { }); it('should define `mixins/*` files', function() { - if (app.loopback.modelBuilder.mixins) { - var modelBuilder = app.loopback.modelBuilder; - var registry = modelBuilder.mixins.mixins; - expect(registry).to.have.property('Example'); - expect(registry).to.have.property('TimeStamps'); - } + var modelBuilder = app.loopback.modelBuilder; + var registry = modelBuilder.mixins.mixins; + expect(registry).to.have.property('Example'); + expect(registry).to.have.property('TimeStamps'); }); >>>>>>> Support loading of mixing From 91c2721925ea0643ef8b11c469109e76f5857d4f Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Thu, 9 Oct 2014 21:25:00 +0200 Subject: [PATCH 14/17] bootSources => bootDirs --- index.js | 2 +- lib/compiler.js | 14 +++++++------- test/compiler.test.js | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 522d5f1..5a2c011 100644 --- a/index.js +++ b/index.js @@ -109,7 +109,7 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * for files containing model definitions. * @property {Array.} [mixinSources] List of directories where to look * for files containing model mixin definitions. - * @property {Array.} [bootSources] List of directories where to look + * @property {Array.} [bootDirs] List of directories where to look * for boot scripts. * @property {Array.} [bootScripts] List of script files to execute * on boot. diff --git a/lib/compiler.js b/lib/compiler.js index 8ca8384..186fc73 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -44,11 +44,11 @@ module.exports = function compile(options) { assertIsValidConfig('data source', dataSourcesConfig); // require directories - var bootSources = options.bootSources || []; // precedence - bootSources = bootSources.concat(path.join(appRootDir, 'boot')); + var bootDirs = options.bootDirs || []; // precedence + bootDirs = bootDirs.concat(path.join(appRootDir, 'boot')); var bootScripts = options.bootScripts || []; - bootSources.forEach(function(dir) { + bootDirs.forEach(function(dir) { dir = path.resolve(dir); bootScripts = bootScripts.concat(findScripts(dir)); }); @@ -60,8 +60,8 @@ module.exports = function compile(options) { var modelInstructions = buildAllModelInstructions( modelsRootDir, modelsConfig, modelSources); - var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins']; - var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinSources, options); + var mixinDirs = options.mixinDirs || modelsMeta.mixins || ['./mixins']; + var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinDirs, options); // When executor passes the instruction to loopback methods, // loopback modifies the data. Since we are loading the data using `require`, @@ -77,9 +77,9 @@ module.exports = function compile(options) { }); }; -function buildAllMixinInstructions(appRootDir, mixinSources, options) { +function buildAllMixinInstructions(appRootDir, mixinDirs, options) { var files = options.mixins || []; - mixinSources.forEach(function(dir) { + mixinDirs.forEach(function(dir) { dir = path.resolve(appRootDir, dir); files = files.concat(findScripts(dir)); }); diff --git a/test/compiler.test.js b/test/compiler.test.js index b1153b0..45e17ec 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -380,13 +380,13 @@ describe('compiler', function() { expect(instructions.files.boot).to.eql([initJs]); }); - it('supports `bootSources` option', function() { + it('supports `bootDirs` option', function() { appdir.createConfigFilesSync(); var initJs = appdir.writeFileSync('custom-boot/init.js', 'module.exports = function(app) { app.fnCalled = true; };'); var instructions = boot.compile({ appRootDir: appdir.PATH, - bootSources: [path.dirname(initJs)] + bootDirs: [path.dirname(initJs)] }); expect(instructions.files.boot).to.eql([initJs]); }); From ee4b609582b79897fdf3ac3cf2895c446f9307a5 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Thu, 9 Oct 2014 22:23:58 +0200 Subject: [PATCH 15/17] Fix remaining PR issues --- index.js | 2 ++ lib/compiler.js | 20 +++++++++++---- lib/executor.js | 5 ++-- package.json | 2 +- test/compiler.test.js | 34 ++++++++++++++++++++++++++ test/executor.test.js | 1 + test/fixtures/simple-app/mixins/Foo.js | 10 ++++++++ 7 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/simple-app/mixins/Foo.js diff --git a/index.js b/index.js index 5a2c011..71ff6f5 100644 --- a/index.js +++ b/index.js @@ -113,6 +113,8 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * for boot scripts. * @property {Array.} [bootScripts] List of script files to execute * on boot. + * @property {String|Function|Boolean} [normalization] Mixin normalization format + * can be false, 'none', 'classify', 'dasherize' - defaults to 'classify'. * @end * * @header boot(app, [options]) diff --git a/lib/compiler.js b/lib/compiler.js index 186fc73..70bb9c4 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -87,7 +87,7 @@ function buildAllMixinInstructions(appRootDir, mixinDirs, options) { var mixins = {}; files.forEach(function(filepath) { var ext = path.extname(filepath); - var name = normalizeMixinName(path.basename(filepath, ext)); + var name = normalizeMixinName(path.basename(filepath, ext), options); mixins[name] = filepath; }); @@ -317,8 +317,18 @@ function loadModelDefinition(rootDir, jsonFile) { }; } -function normalizeMixinName(str) { // classify names: foo-bar => FooBar - str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); - str = str.replace(/(?:^|\s|-)\S/g, function(c){ return c.toUpperCase(); }); - return str.replace(/\s/g, ''); +function normalizeMixinName(str, options) { // classify names: foo-bar => FooBar + var normalization = options.normalization; + if (normalization === false || normalization === 'none') return str; + if (normalization === 'dasherize') { + str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); + str = str.replace(/\s+/g, '-'); + } else if (typeof normalization === 'function') { + str = normalization(str); + } else { // classify + str = String(str).replace(/[\W_]/g, ' ').toLowerCase(); + str = str.replace(/(?:^|\s|-)\S/g, function(c){ return c.toUpperCase(); }); + str = str.replace(/\s+/g, ''); + } + return str; } diff --git a/lib/executor.js b/lib/executor.js index 1057e12..38ca1f2 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -155,8 +155,9 @@ function defineMixins(app, instructions) { mixinNames.forEach(function(name) { var mixin = tryRequire(mixins[name]); if (typeof mixin === 'function' || mixin.prototype instanceof BaseClass) { - debug('Configuring mixin %s', name); - modelBuilder.mixins.define(name, mixin); + var mixinName = mixin.name || mixin.mixinName || name; + debug('Configuring mixin %s', mixinName); + modelBuilder.mixins.define(mixinName, mixin); } else { debug('Skipping mixin file %s - `module.exports` is not a function' + ' or Loopback model', diff --git a/package.json b/package.json index 779f7d2..f9d2a52 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "browserify": "^4.1.8", "fs-extra": "^0.10.0", "jshint": "^2.5.6", - "loopback": "^1.5.0", + "loopback": "^2.2.0", "mocha": "^1.19.0", "must": "^0.12.0", "supertest": "^0.13.0" diff --git a/test/compiler.test.js b/test/compiler.test.js index 45e17ec..04d329f 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -620,6 +620,40 @@ describe('compiler', function() { instructions = boot.compile(appdir.PATH); expect(instructions.config).to.not.have.property('modified'); }); + + it('support mixin name normalization - classify (default)', function() { + var options = { appRootDir: SIMPLE_APP }; + var instructions = boot.compile(options); + expect(instructions.mixins).to.have.property('Example'); + expect(instructions.mixins).to.have.property('Foo'); + expect(instructions.mixins).to.have.property('TimeStamps'); + }); + + it('support mixin name normalization - dasherize', function() { + var options = { appRootDir: SIMPLE_APP, normalization: 'dasherize' }; + var instructions = boot.compile(options); + expect(instructions.mixins).to.have.property('example'); + expect(instructions.mixins).to.have.property('foo'); + expect(instructions.mixins).to.have.property('time-stamps'); + }); + + it('support mixin name normalization - custom function', function() { + var normalize = function(name) { return name.toUpperCase(); }; + var options = { appRootDir: SIMPLE_APP, normalization: normalize }; + var instructions = boot.compile(options); + expect(instructions.mixins).to.have.property('EXAMPLE'); + expect(instructions.mixins).to.have.property('FOO'); + expect(instructions.mixins).to.have.property('TIME-STAMPS'); + }); + + it('support skip mixin name normalization - none', function() { + var options = { appRootDir: SIMPLE_APP, normalization: false }; + var instructions = boot.compile(options); + expect(instructions.mixins).to.have.property('example'); + expect(instructions.mixins).to.have.property('Foo'); + expect(instructions.mixins).to.have.property('time-stamps'); + }); + }); }); diff --git a/test/executor.test.js b/test/executor.test.js index d28e396..c412608 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -230,6 +230,7 @@ describe('executor', function() { var registry = modelBuilder.mixins.mixins; expect(registry).to.have.property('Example'); expect(registry).to.have.property('TimeStamps'); + expect(registry).to.have.property('bar'); // mixinName }); >>>>>>> Support loading of mixing diff --git a/test/fixtures/simple-app/mixins/Foo.js b/test/fixtures/simple-app/mixins/Foo.js new file mode 100644 index 0000000..0123cc5 --- /dev/null +++ b/test/fixtures/simple-app/mixins/Foo.js @@ -0,0 +1,10 @@ +// When you create a named function, its name will be used +// as the mixin name - alternatively, set mixin.mixinName. + +var mixin = function bar(Model, options) { + + Model.barMixin = true; + +}; + +module.exports = mixin; \ No newline at end of file From ea64b76ad43b9028f82a80adb7f55ce1bad5aa2e Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Fri, 10 Oct 2014 15:15:23 +0200 Subject: [PATCH 16/17] Fix mixinDirs vs. mixinSources, minor touchups --- index.js | 2 +- lib/compiler.js | 10 +++++----- test/compiler.test.js | 16 ++++++++++++---- test/fixtures/mixins/other.js | 5 +++++ 4 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/mixins/other.js diff --git a/index.js b/index.js index 71ff6f5..48b3e42 100644 --- a/index.js +++ b/index.js @@ -108,7 +108,7 @@ var addInstructionsToBrowserify = require('./lib/bundler'); * @property {Array.} [modelSources] List of directories where to look * for files containing model definitions. * @property {Array.} [mixinSources] List of directories where to look - * for files containing model mixin definitions. + * for files containing model mixin definitions. - defaults to ['./mixins'] * @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/compiler.js b/lib/compiler.js index 70bb9c4..e716b97 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -60,8 +60,8 @@ module.exports = function compile(options) { var modelInstructions = buildAllModelInstructions( modelsRootDir, modelsConfig, modelSources); - var mixinDirs = options.mixinDirs || modelsMeta.mixins || ['./mixins']; - var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinDirs, options); + var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins']; + var mixinInstructions = buildAllMixinInstructions(appRootDir, mixinSources, options); // When executor passes the instruction to loopback methods, // loopback modifies the data. Since we are loading the data using `require`, @@ -77,9 +77,9 @@ module.exports = function compile(options) { }); }; -function buildAllMixinInstructions(appRootDir, mixinDirs, options) { +function buildAllMixinInstructions(appRootDir, mixinSources, options) { var files = options.mixins || []; - mixinDirs.forEach(function(dir) { + mixinSources.forEach(function(dir) { dir = path.resolve(appRootDir, dir); files = files.concat(findScripts(dir)); }); @@ -317,7 +317,7 @@ function loadModelDefinition(rootDir, jsonFile) { }; } -function normalizeMixinName(str, options) { // classify names: foo-bar => FooBar +function normalizeMixinName(str, options) { var normalization = options.normalization; if (normalization === false || normalization === 'none') return str; if (normalization === 'dasherize') { diff --git a/test/compiler.test.js b/test/compiler.test.js index 04d329f..1e5ece6 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -6,6 +6,7 @@ var sandbox = require('./helpers/sandbox'); var appdir = require('./helpers/appdir'); var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app'); +var MIXIN_SOURCES = path.join(__dirname, 'fixtures', 'mixins'); describe('compiler', function() { beforeEach(sandbox.reset); @@ -621,7 +622,14 @@ describe('compiler', function() { expect(instructions.config).to.not.have.property('modified'); }); - it('support mixin name normalization - classify (default)', function() { + it('has a mixinSources option', function() { + var options = { appRootDir: SIMPLE_APP, mixinSources: [MIXIN_SOURCES] }; + var instructions = boot.compile(options); + expect(instructions.mixins).to.not.have.property('TimeStamps'); + expect(instructions.mixins).to.have.property('Other'); + }); + + it('supports mixin name normalization - classify (default)', function() { var options = { appRootDir: SIMPLE_APP }; var instructions = boot.compile(options); expect(instructions.mixins).to.have.property('Example'); @@ -629,7 +637,7 @@ describe('compiler', function() { expect(instructions.mixins).to.have.property('TimeStamps'); }); - it('support mixin name normalization - dasherize', function() { + it('supports mixin name normalization - dasherize', function() { var options = { appRootDir: SIMPLE_APP, normalization: 'dasherize' }; var instructions = boot.compile(options); expect(instructions.mixins).to.have.property('example'); @@ -637,7 +645,7 @@ describe('compiler', function() { expect(instructions.mixins).to.have.property('time-stamps'); }); - it('support mixin name normalization - custom function', function() { + it('supports mixin name normalization - custom function', function() { var normalize = function(name) { return name.toUpperCase(); }; var options = { appRootDir: SIMPLE_APP, normalization: normalize }; var instructions = boot.compile(options); @@ -646,7 +654,7 @@ describe('compiler', function() { expect(instructions.mixins).to.have.property('TIME-STAMPS'); }); - it('support skip mixin name normalization - none', function() { + it('supports skip mixin name normalization - none', function() { var options = { appRootDir: SIMPLE_APP, normalization: false }; var instructions = boot.compile(options); expect(instructions.mixins).to.have.property('example'); diff --git a/test/fixtures/mixins/other.js b/test/fixtures/mixins/other.js new file mode 100644 index 0000000..67744b8 --- /dev/null +++ b/test/fixtures/mixins/other.js @@ -0,0 +1,5 @@ +module.exports = function(Model, options) { + + Model.otherMixin = true; + +} \ No newline at end of file From 320f7469aa6cf891a34e33e7fb088b83079a2868 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Fri, 10 Oct 2014 15:26:23 +0200 Subject: [PATCH 17/17] Minor touchups after rebase --- test/executor.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/executor.test.js b/test/executor.test.js index c412608..513b1ab 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -198,6 +198,8 @@ describe('executor', function() { if (app.loopback.datasourceJuggler && app.loopback.datasourceJuggler.mixins) { var mixins = app.loopback.datasourceJuggler.mixins; expect(mixins.registry).to.have.property('Example'); + expect(mixins.registry).to.have.property('TimeStamps'); + expect(mixins.registry).to.have.property('bar'); // mixinName } }); }); @@ -233,7 +235,6 @@ describe('executor', function() { expect(registry).to.have.property('bar'); // mixinName }); ->>>>>>> Support loading of mixing }); describe('with PaaS and npm env variables', function() {