diff --git a/lib/compiler.js b/lib/compiler.js index d5d870a..3e1df63 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -5,6 +5,7 @@ var path = require('path'); var toposort = require('toposort'); var ConfigLoader = require('./config-loader'); var debug = require('debug')('loopback:boot:compiler'); +var Module = require('module'); /** * Gather all bootstrap-related configuration data and compile it into @@ -245,7 +246,12 @@ function findModelDefinitions(rootDir, sources) { var registry = {}; sources.forEach(function(src) { - var srcDir = path.resolve(rootDir, src); + var srcDir = resolveSourceDir(rootDir, src); + if (!srcDir) { + debug('Skipping unknown module source dir %j', src); + return; + } + var files = tryReadDir(srcDir); files .filter(function(f) { @@ -267,6 +273,36 @@ function findModelDefinitions(rootDir, sources) { return registry; } +function resolveSourceDir(rootDir, sourceDir) { + var srcDir = path.resolve(rootDir, sourceDir); + if (fs.existsSync(srcDir)) + return srcDir; + + // Handle module-relative path, e.g. `loopback/common/models` + var start = sourceDir.substring(0, 2); + if (start !== './' && start !== '..') { + // Module.globalPaths is a list of globally configured paths like + // [ env.NODE_PATH values, $HOME/.node_modules, etc. ] + // Module._nodeModulePaths(rootDir) returns a list of paths like + // [ rootDir/node_modules, rootDir/../node_modules, etc. ] + var modulePaths = Module.globalPaths + .concat(Module._nodeModulePaths(rootDir)); + + srcDir = modulePaths + .map(function(candidateDir) { + return path.join(candidateDir, sourceDir); + }) + .filter(function(candidate) { + return fs.existsSync(candidate); + }) + [0]; + if (srcDir) + return srcDir; + } + + return undefined; +} + function loadModelDefinition(rootDir, jsonFile) { var definition = require(jsonFile); diff --git a/test/compiler.test.js b/test/compiler.test.js index 45e17ec..f902140 100644 --- a/test/compiler.test.js +++ b/test/compiler.test.js @@ -501,6 +501,30 @@ describe('compiler', function() { }); }); + it('supports sources relative to node_modules', function() { + appdir.createConfigFilesSync({}, {}, { + User: { dataSource: 'db' } + }); + + var instructions = boot.compile({ + appRootDir: appdir.PATH, + modelSources: [ + 'loopback/common/models', + 'loopback/common/dir-does-not-exist' + ] + }); + + expect(instructions.models).to.have.length(1); + expect(instructions.models[0]).to.eql({ + name: 'User', + config: { + dataSource: 'db' + }, + definition: require('loopback/common/models/user.json'), + sourceFile: require.resolve('loopback/common/models/user.js') + }); + }); + it('handles model definitions with no code', function() { appdir.createConfigFilesSync({}, {}, { Car: { dataSource: 'db' } diff --git a/test/fixtures/browser-app/model-config.json b/test/fixtures/browser-app/model-config.json index d895c8a..c0686a2 100644 --- a/test/fixtures/browser-app/model-config.json +++ b/test/fixtures/browser-app/model-config.json @@ -2,7 +2,7 @@ "_meta": { "sources": [ "./models", - "../../../node_modules/loopback/common/models" + "loopback/common/models" ] }, "Customer": {