From 8cb23537deb0d717dd24a45bc78910eead3897d9 Mon Sep 17 00:00:00 2001 From: Cory Forsyth Date: Tue, 7 Apr 2015 16:15:08 -0400 Subject: [PATCH 1/4] fix incorrect test setup for project unit test --- tests/unit/models/project-test.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/unit/models/project-test.js b/tests/unit/models/project-test.js index 164521e9fe2..c972f825b50 100644 --- a/tests/unit/models/project-test.js +++ b/tests/unit/models/project-test.js @@ -15,8 +15,12 @@ var emberCLIVersion = versionUtils.emberCLIVersion; describe('models/project.js', function() { var project, projectPath; + afterEach(function() { + if (project) { project = null; } + }); + describe('Project.prototype.config', function() { - var called = false; + var called; beforeEach(function() { projectPath = process.cwd() + '/tmp/test-app'; @@ -36,6 +40,7 @@ describe('models/project.js', function() { }); afterEach(function() { + called = null; return tmp.teardown(projectPath); }); @@ -349,6 +354,11 @@ describe('models/project.js', function() { }); describe('emberCLIVersion', function() { + beforeEach(function() { + projectPath = process.cwd() + '/tmp/test-app'; + project = new Project(projectPath, {}, new MockUI()); + }); + it('should return the same value as the utlity function', function() { expect(project.emberCLIVersion()).to.equal(emberCLIVersion()); }); @@ -429,6 +439,11 @@ describe('models/project.js', function() { }); describe('bowerDirectory', function() { + beforeEach(function() { + projectPath = path.resolve(__dirname, '../../fixtures/addon/simple'); + project = new Project(projectPath, {}, new MockUI()); + }); + it('should be initialized in constructor', function() { expect(project.bowerDirectory).to.equal('bower_components'); }); From 94c7775ed3eaa9604e64154158298114584b49f6 Mon Sep 17 00:00:00 2001 From: Jake Howerton Date: Fri, 3 Apr 2015 00:35:05 -0400 Subject: [PATCH 2/4] [ENHANCEMENT] use EMBER_NODE_PATH to specify node_modules path ember-cli will use the value in env.EMBER_NODE_PATH if it is provided to look up addons when `require`-ing them. This allows a user to have their `node_modules` path outside of the project root. Previously, the ember-cli custom require code assumed that `node_modules` is always in the project's root. --- lib/cli/node-modules-path.js | 51 ++++++++++++++++++++++++ lib/models/addon-discovery.js | 17 ++++---- lib/models/project.js | 37 ++++++++--------- lib/utilities/require-local.js | 5 ++- tests/unit/cli/node-modules-path-test.js | 43 ++++++++++++++++++++ 5 files changed, 125 insertions(+), 28 deletions(-) create mode 100644 lib/cli/node-modules-path.js create mode 100644 tests/unit/cli/node-modules-path-test.js diff --git a/lib/cli/node-modules-path.js b/lib/cli/node-modules-path.js new file mode 100644 index 00000000000..7f4516ddae8 --- /dev/null +++ b/lib/cli/node-modules-path.js @@ -0,0 +1,51 @@ +'use strict'; + +var path = require('path'); + +function isSubdirectoryOf(parentPath, possibleChildPath) { + return possibleChildPath.length > parentPath.length && + possibleChildPath.indexOf(parentPath) === 0; +} + +// A utility function for determining what path an addon may be found at. Addons +// will only be resolved in the project's local `node_modules/` directory. This +// is in contrast to a plain `require('some-module')` lookup, which would resolve +// a library according to the paths in NODE_PATH. +// +// A description of node's lookup logic can be found here: +// +// * https://nodejs.org/api/modules.html#modules_all_together +// +// By requiring with an absolute path this logic is bypassed. +// +module.exports = function nodeModulesPath(context) { + + // Optionally configure a different home for `node_modules/` in a parent + // directory of the project. Possible use cases for this include caching + // the `node_modules/` directory outside of a source code checkout, and + // ensuring the same source code (shared over a network) can be used with + // different environments (Linux, OSX) where binary compatibility may not + // exist. + // + // For example, you can specify a different home directory for modules: + // + // EMBER_NODE_PATH=/opt/deps/node_modules NODE_PATH=/opt/deps/node_modules ember build + // + var nodePath = process.env.EMBER_NODE_PATH; + var contextPath = path.resolve(context); + + if (nodePath) { + var configuredPath = path.resolve(nodePath); + + // The contextPath is likely the project root, or possibly a subdirectory in + // node_modules/ nested dependencies. If it is more specific (a subdirectory + // of) than the configuredPath prefer the more specific contextPath. + if (isSubdirectoryOf(configuredPath, contextPath)) { + return path.resolve(contextPath,'node_modules'); + } else { + return path.resolve(nodePath); + } + } else { + return path.resolve(contextPath,'node_modules'); + } +}; diff --git a/lib/models/addon-discovery.js b/lib/models/addon-discovery.js index 4931e33590a..aaed9d06f5f 100644 --- a/lib/models/addon-discovery.js +++ b/lib/models/addon-discovery.js @@ -4,13 +4,14 @@ @module ember-cli */ -var assign = require('lodash/object/assign'); -var debug = require('debug')('ember-cli:addon-discovery'); -var fs = require('fs'); -var path = require('path'); -var CoreObject = require('core-object'); -var resolve = require('resolve'); -var findup = require('findup'); +var assign = require('lodash/object/assign'); +var debug = require('debug')('ember-cli:addon-discovery'); +var fs = require('fs'); +var path = require('path'); +var CoreObject = require('core-object'); +var resolve = require('resolve'); +var findup = require('findup'); +var nodeModulesPath = require ('../cli/node-modules-path'); /** AddonDiscovery is responsible for collecting information about all of the @@ -103,7 +104,7 @@ AddonDiscovery.prototype.discoverFromDependencies = function(root, pkg, excludeD // this supports packages that do not have a valid entry point // script (aka `main` entry in `package.json` or `index.js`) - addonPath = path.join(root, 'node_modules', name); + addonPath = path.join(nodeModulesPath(root), name); var addon = discovery.discoverAtPath(addonPath); if (addon) { var chalk = require('chalk'); diff --git a/lib/models/project.js b/lib/models/project.js index 4a0887d52b7..ca97317bc5b 100644 --- a/lib/models/project.js +++ b/lib/models/project.js @@ -3,23 +3,24 @@ /** @module ember-cli */ -var Promise = require('../ext/promise'); -var path = require('path'); -var findup = Promise.denodeify(require('findup')); -var resolve = Promise.denodeify(require('resolve')); -var fs = require('fs'); -var find = require('lodash/collection/find'); -var assign = require('lodash/object/assign'); -var forOwn = require('lodash/object/forOwn'); -var merge = require('lodash/object/merge'); -var debug = require('debug')('ember-cli:project'); -var AddonDiscovery = require('../models/addon-discovery'); -var AddonsFactory = require('../models/addons-factory'); -var Command = require('../models/command'); -var UI = require('../ui'); - -var versionUtils = require('../utilities/version-utils'); -var emberCLIVersion = versionUtils.emberCLIVersion; +var Promise = require('../ext/promise'); +var path = require('path'); +var findup = Promise.denodeify(require('findup')); +var resolve = Promise.denodeify(require('resolve')); +var fs = require('fs'); +var find = require('lodash/collection/find'); +var assign = require('lodash/object/assign'); +var forOwn = require('lodash/object/forOwn'); +var merge = require('lodash/object/merge'); +var debug = require('debug')('ember-cli:project'); +var AddonDiscovery = require('../models/addon-discovery'); +var AddonsFactory = require('../models/addons-factory'); +var Command = require('../models/command'); +var UI = require('../ui'); +var nodeModulesPath = require('../cli/node-modules-path'); + +var versionUtils = require('../utilities/version-utils'); +var emberCLIVersion = versionUtils.emberCLIVersion; /** The Project model is tied to your package.json. It is instiantiated @@ -216,7 +217,7 @@ Project.prototype.require = function(file) { if (/^\.\//.test(file)) { // Starts with ./ return require(path.join(this.root, file)); } else { - return require(path.join(this.root, 'node_modules', file)); + return require(path.join(nodeModulesPath(this.root), file)); } }; diff --git a/lib/utilities/require-local.js b/lib/utilities/require-local.js index bea51a6c275..6775e6111e9 100644 --- a/lib/utilities/require-local.js +++ b/lib/utilities/require-local.js @@ -1,7 +1,8 @@ 'use strict'; -var path = require('path'); +var path = require('path'); +var nodeModulesPath = require('../cli/node-modules-path'); module.exports = function requireLocal(lib) { - return require(path.join(process.cwd(), 'node_modules', lib)); + return require(path.join(nodeModulesPath(process.cwd()), lib)); }; diff --git a/tests/unit/cli/node-modules-path-test.js b/tests/unit/cli/node-modules-path-test.js new file mode 100644 index 00000000000..5d9e1252106 --- /dev/null +++ b/tests/unit/cli/node-modules-path-test.js @@ -0,0 +1,43 @@ +'use strict'; +/*jshint expr: true*/ + +var expect = require('chai').expect; +var path = require('path'); +var nodeModulesPath = require('../../../lib/cli/node-modules-path'); + +describe('cli/node-module-path.js', function() { + + afterEach(function() { + delete process.env.EMBER_NODE_PATH; + }); + + + it('nodeModulesPath() should return the local node_modules path by default.', function() { + // Valid commands + var expectedPath = path.join(process.cwd(),'node_modules'); + + expect( + nodeModulesPath(process.cwd()) + ).to.equal(expectedPath); + }); + + it('nodeModulesPath() should return subdirectories of EMBER_NODE_PATH when set to an absolute path.', function() { + if (process.platform === 'win32') { + process.env.EMBER_NODE_PATH = 'C:\\tmp\node_modules'; + } else { + process.env.EMBER_NODE_PATH = '/tmp/node_modules'; + } + + expect(nodeModulesPath(process.cwd())).to.equal(path.resolve(process.env.EMBER_NODE_PATH)); + + var addOnPath = path.resolve(process.env.EMBER_NODE_PATH, 'node_modules', 'my-add-on'); + var addOnModulesPath = path.resolve(process.env.EMBER_NODE_PATH, 'node_modules', 'my-add-on', 'node_modules'); + expect(nodeModulesPath(addOnPath)).to.equal(addOnModulesPath); + }); + + it('nodeModulesPath() should return subdirectories of EMBER_NODE_PATH when set to a relative path.', function() { + process.env.EMBER_NODE_PATH = '../../tmp/node_modules'; + expect(nodeModulesPath(process.cwd())).to.equal(path.resolve('../../tmp','node_modules')); + expect(nodeModulesPath('../../tmp/node_modules/my-add-on')).to.equal(path.resolve('../../tmp','node_modules','my-add-on','node_modules')); + }); +}); From c21ed5646b1e9985e571b37c1fc8364661ed25a3 Mon Sep 17 00:00:00 2001 From: Cory Forsyth Date: Tue, 7 Apr 2015 17:07:02 -0400 Subject: [PATCH 3/4] [ENHANCEMENT] Add `nodeModulesPath` property to Project and Addon This uses the cli utility helper in node-modules-path to determine the nodeModulesPath. Also modifies private method `AddonDiscovery.discoverFromDependencies` to get the nodeModules path from the project (or addon if inside AddonDiscovery.discoverChildAddons) as its second argument. This property is intended to provide a way for 3rd-party addons (like ember-cli-dependency-checker) to know the project's node modules path without having to discover it in an ad hoc way. --- lib/models/addon-discovery.js | 9 ++++---- lib/models/addon.js | 6 ++++++ lib/models/project.js | 7 +++++- tests/helpers/mock-project.js | 1 + tests/unit/models/addon-discovery-test.js | 12 +++++++---- tests/unit/models/addon-test.js | 17 +++++++++++++-- tests/unit/models/project-test.js | 26 +++++++++++++++++++++++ 7 files changed, 66 insertions(+), 12 deletions(-) diff --git a/lib/models/addon-discovery.js b/lib/models/addon-discovery.js index aaed9d06f5f..916590b1de4 100644 --- a/lib/models/addon-discovery.js +++ b/lib/models/addon-discovery.js @@ -11,7 +11,6 @@ var path = require('path'); var CoreObject = require('core-object'); var resolve = require('resolve'); var findup = require('findup'); -var nodeModulesPath = require ('../cli/node-modules-path'); /** AddonDiscovery is responsible for collecting information about all of the @@ -42,7 +41,7 @@ AddonDiscovery.prototype.constructor = AddonDiscovery; AddonDiscovery.prototype.discoverProjectAddons = function(project) { var projectAsAddon = this.discoverFromProjectItself(project); var internalAddons = this.discoverFromInternalProjectAddons(project); - var dependencyAddons = this.discoverFromDependencies(project.root, project.pkg, false); + var dependencyAddons = this.discoverFromDependencies(project.root, project.nodeModulesPath, project.pkg, false); var inRepoAddons = this.discoverInRepoAddons(project.root, project.pkg); var addons = projectAsAddon.concat(internalAddons, dependencyAddons, inRepoAddons); @@ -61,7 +60,7 @@ AddonDiscovery.prototype.discoverProjectAddons = function(project) { */ AddonDiscovery.prototype.discoverChildAddons = function(addon) { debug('discoverChildAddons: %s(%s)', addon.name, addon.root); - var dependencyAddons = this.discoverFromDependencies(addon.root, addon.pkg, true); + var dependencyAddons = this.discoverFromDependencies(addon.root, addon.nodeModulesPath, addon.pkg, true); var inRepoAddons = this.discoverInRepoAddons(addon.root, addon.pkg); var addons = dependencyAddons.concat(inRepoAddons); return addons; @@ -92,7 +91,7 @@ AddonDiscovery.prototype.discoverFromProjectItself = function(project) { @private @method discoverFromDependencies */ -AddonDiscovery.prototype.discoverFromDependencies = function(root, pkg, excludeDevDeps) { +AddonDiscovery.prototype.discoverFromDependencies = function(root, nodeModulesPath, pkg, excludeDevDeps) { var discovery = this; var addons = Object.keys(this.dependencies(pkg, excludeDevDeps)).map(function(name) { if (name !== 'ember-cli') { @@ -104,7 +103,7 @@ AddonDiscovery.prototype.discoverFromDependencies = function(root, pkg, excludeD // this supports packages that do not have a valid entry point // script (aka `main` entry in `package.json` or `index.js`) - addonPath = path.join(nodeModulesPath(root), name); + addonPath = path.join(nodeModulesPath, name); var addon = discovery.discoverAtPath(addonPath); if (addon) { var chalk = require('chalk'); diff --git a/lib/models/addon.js b/lib/models/addon.js index aa8e4d92842..346e12a40bc 100644 --- a/lib/models/addon.js +++ b/lib/models/addon.js @@ -12,6 +12,7 @@ var glob = require('glob'); var SilentError = require('../errors/silent'); var reexport = require('../utilities/reexport'); var debug = require('debug')('ember-cli:addon'); +var nodeModulesPath = require('../cli/node-modules-path'); var p = require('../preprocessors'); var preprocessJs = p.preprocessJs; @@ -62,6 +63,11 @@ function Addon(parent, project) { this.registry = p.defaultRegistry(this); this._didRequiredBuildPackages = false; + if (!this.root) { + throw new Error('Addon classes must be instantiated with the `root` property'); + } + this.nodeModulesPath = nodeModulesPath(this.root); + this.treePaths = { app: 'app', styles: 'app/styles', diff --git a/lib/models/project.js b/lib/models/project.js index ca97317bc5b..f7506c470d2 100644 --- a/lib/models/project.js +++ b/lib/models/project.js @@ -40,6 +40,7 @@ function Project(root, pkg, ui) { this.addons = []; this.liveReloadFilterPatterns = []; this.setupBowerDirectory(); + this.setupNodeModulesPath(); this.addonDiscovery = new AddonDiscovery(this.ui); this.addonsFactory = new AddonsFactory(this, this); } @@ -69,6 +70,10 @@ Project.prototype.setupBowerDirectory = function() { debug('bowerDirectory: %s', this.bowerDirectory); }; +Project.prototype.setupNodeModulesPath = function(){ + this.nodeModulesPath = nodeModulesPath(this.root); +}; + var NULL_PROJECT = new Project(process.cwd(), {}); NULL_PROJECT.isEmberCLIProject = function() { @@ -217,7 +222,7 @@ Project.prototype.require = function(file) { if (/^\.\//.test(file)) { // Starts with ./ return require(path.join(this.root, file)); } else { - return require(path.join(nodeModulesPath(this.root), file)); + return require(path.join(this.nodeModulesPath, file)); } }; diff --git a/tests/helpers/mock-project.js b/tests/helpers/mock-project.js index 83833ac4397..bed9bf94261 100644 --- a/tests/helpers/mock-project.js +++ b/tests/helpers/mock-project.js @@ -38,6 +38,7 @@ MockProject.prototype.discoverAddons = Project.prototype.discoverAddons; MockProject.prototype.addIfAddon = Project.prototype.addIfAddon; MockProject.prototype.supportedInternalAddonPaths = Project.prototype.supportedInternalAddonPaths; MockProject.prototype.setupBowerDirectory = Project.prototype.setupBowerDirectory; +MockProject.prototype.setupNodeModulesPath = Project.prototype.setupNodeModulesPath; MockProject.prototype.dependencies = function() { return []; }; diff --git a/tests/unit/models/addon-discovery-test.js b/tests/unit/models/addon-discovery-test.js index 35576abf8ed..a8882d22026 100644 --- a/tests/unit/models/addon-discovery-test.js +++ b/tests/unit/models/addon-discovery-test.js @@ -184,6 +184,7 @@ describe('models/addon-discovery.js', function() { it('can find a package without a main entry point [DEPRECATED]', function() { var root = path.join(fixturePath, 'shared-package', 'base'); + var addonNodeModulesPath = path.join(root, 'node_modules'); var actualPaths = []; var discovery = new AddonDiscovery(ui); @@ -194,7 +195,7 @@ describe('models/addon-discovery.js', function() { return providedPath; }; - discovery.discoverFromDependencies(root, mockPkg, true); + discovery.discoverFromDependencies(root, addonNodeModulesPath, mockPkg, true); var expectedPaths = [ path.join(root, 'node_modules', 'foo-bar'), @@ -211,6 +212,7 @@ describe('models/addon-discovery.js', function() { it('does not error when dependencies are not found', function() { var root = path.join(fixturePath, 'shared-package', 'base'); + var addonNodeModulesPath = path.join(root, 'node_modules'); var actualPaths = []; var discovery = new AddonDiscovery(ui); @@ -221,7 +223,7 @@ describe('models/addon-discovery.js', function() { return providedPath; }; - discovery.discoverFromDependencies(root, mockPkg, true); + discovery.discoverFromDependencies(root, addonNodeModulesPath, mockPkg, true); var expectedPaths = [ path.join(root, 'node_modules', 'foo-bar'), @@ -234,6 +236,7 @@ describe('models/addon-discovery.js', function() { it('calls discoverAtPath for each entry in dependencies', function() { var root = path.join(fixturePath, 'shared-package', 'base'); + var addonNodeModulesPath = path.join(root, 'node_modules'); var actualPaths = []; var discovery = new AddonDiscovery(ui); @@ -243,7 +246,7 @@ describe('models/addon-discovery.js', function() { return providedPath; }; - discovery.discoverFromDependencies(root, mockPkg); + discovery.discoverFromDependencies(root, addonNodeModulesPath, mockPkg); var expectedPaths = [ path.join(root, '..', 'node_modules', 'dev-foo-bar'), @@ -256,6 +259,7 @@ describe('models/addon-discovery.js', function() { it('excludes devDeps if `excludeDevDeps` is true', function() { var root = path.join(fixturePath, 'shared-package', 'base'); + var addonNodeModulesPath = path.join(root, 'node_modules'); var actualPaths = []; var discovery = new AddonDiscovery(ui); @@ -265,7 +269,7 @@ describe('models/addon-discovery.js', function() { return providedPath; }; - discovery.discoverFromDependencies(root, mockPkg, true); + discovery.discoverFromDependencies(root, addonNodeModulesPath, mockPkg, true); var expectedPaths = [ path.join(root, 'node_modules', 'foo-bar'), diff --git a/tests/unit/models/addon-test.js b/tests/unit/models/addon-test.js index fb6b446b532..5e821df58b6 100644 --- a/tests/unit/models/addon-test.js +++ b/tests/unit/models/addon-test.js @@ -20,6 +20,15 @@ var fixturePath = path.resolve(__dirname, '../../fixtures/addon'); describe('models/addon.js', function() { var addon, project, projectPath; + describe('root property', function() { + it('is required', function() { + expect(function() { + var TheAddon = Addon.extend({root:undefined}); + new TheAddon(); + }).to.throw(/root/); + }); + }); + describe('treePaths and treeForMethods', function() { var FirstAddon, SecondAddon; @@ -31,6 +40,7 @@ describe('models/addon.js', function() { FirstAddon = Addon.extend({ name: 'first', + root: projectPath, init: function() { this.treePaths.vendor = 'blazorz'; @@ -40,6 +50,7 @@ describe('models/addon.js', function() { SecondAddon = Addon.extend({ name: 'first', + root: projectPath, init: function() { this.treePaths.vendor = 'blammo'; @@ -345,7 +356,8 @@ describe('models/addon.js', function() { beforeEach(function() { var MyAddon = Addon.extend({ - name: 'test-project' + name: 'test-project', + root: 'foo' }); var projectPath = path.resolve(fixturePath, 'simple'); @@ -507,7 +519,8 @@ describe('models/addon.js', function() { project = new Project(projectPath, packageContents, ui); var AddonTemp = Addon.extend({ - name: 'temp' + name: 'temp', + root: 'foo' }); addon = new AddonTemp(project, project); diff --git a/tests/unit/models/project-test.js b/tests/unit/models/project-test.js index c972f825b50..e1128331210 100644 --- a/tests/unit/models/project-test.js +++ b/tests/unit/models/project-test.js @@ -472,4 +472,30 @@ describe('models/project.js', function() { expect(project.bowerDirectory).to.equal('bower_components'); }); }); + + describe('nodeModulesPath', function() { + function makeProject() { + projectPath = path.resolve(__dirname, '../../fixtures/addon/simple'); + project = new Project(projectPath, {}, new MockUI()); + } + + afterEach(function() { + delete process.env.EMBER_NODE_PATH; + }); + + it('should equal env.EMBER_NODE_PATH when it is set', function() { + var nodePath = '/my/path/node_modules'; + process.env.EMBER_NODE_PATH = nodePath; + + makeProject(); + + expect(project.nodeModulesPath).to.equal(nodePath); + }); + + it('should equal project.root joined with "node_modules" when EMBER_NODE_PATH is not set', function() { + makeProject(); + + expect(project.nodeModulesPath).to.equal(path.join(projectPath, 'node_modules')); + }); + }); }); From b8442845ba8a8dd7f948bdd5823c2728a0d9f5c6 Mon Sep 17 00:00:00 2001 From: Cory Forsyth Date: Wed, 8 Apr 2015 10:55:51 -0400 Subject: [PATCH 4/4] Change comments to yuidoc style --- lib/cli/node-modules-path.js | 75 ++++++++++++++++++++----------- lib/models/addon-discovery.js | 14 +++--- lib/models/project.js | 8 ++++ tests/unit/models/project-test.js | 2 +- 4 files changed, 65 insertions(+), 34 deletions(-) diff --git a/lib/cli/node-modules-path.js b/lib/cli/node-modules-path.js index 7f4516ddae8..718f18fa736 100644 --- a/lib/cli/node-modules-path.js +++ b/lib/cli/node-modules-path.js @@ -7,30 +7,52 @@ function isSubdirectoryOf(parentPath, possibleChildPath) { possibleChildPath.indexOf(parentPath) === 0; } -// A utility function for determining what path an addon may be found at. Addons -// will only be resolved in the project's local `node_modules/` directory. This -// is in contrast to a plain `require('some-module')` lookup, which would resolve -// a library according to the paths in NODE_PATH. -// -// A description of node's lookup logic can be found here: -// -// * https://nodejs.org/api/modules.html#modules_all_together -// -// By requiring with an absolute path this logic is bypassed. -// +/** + A utility function for determining what path an addon may be found at. Addons + will only be resolved in the project's own `node_modules/` directory, they + do not follow the standard node `require` logic that a standard + `require('mode-module')` lookup would use, which finds the module according + to the `NODE_PATH`. + + A description of node's lookup logic can be found here: + + https://nodejs.org/api/modules.html#modules_all_together + + Using this method to discover the correct location of project's `node_modules` + directory allows addons to be looked up properly even when that `node_modules` + directory is outside of the project's root. + + This method checks the env variable `EMBER_NODE_PATH`. If present, its value + is used to determine the `node_modules` path. + + Possible use cases for this include caching + the `node_modules/` directory outside of a source code checkout, and + ensuring the same source code (shared over a network) can be used with + different environments (Linux, OSX) where binary compatibility may not + exist. + + For example, if you have a project in /projects/my-app and its `node_modules` + directory is at /resource/node_modules, you would: + + ``` + # Node uses this as its search path for standard `require('module')` calls + export NODE_PATH=/resource/node_modules + + # So that ember addon discovery looks here + export EMBER_NODE_PATH=/resource/node_modules + + cd /projects/my-app && ember build + ``` + + @private + @method nodeModulesPath + @param {String} context The starting directory to use to find the + node_modules path. This will usually be the + project's root + @return {String} absolute path to the node_modules directory + */ module.exports = function nodeModulesPath(context) { - // Optionally configure a different home for `node_modules/` in a parent - // directory of the project. Possible use cases for this include caching - // the `node_modules/` directory outside of a source code checkout, and - // ensuring the same source code (shared over a network) can be used with - // different environments (Linux, OSX) where binary compatibility may not - // exist. - // - // For example, you can specify a different home directory for modules: - // - // EMBER_NODE_PATH=/opt/deps/node_modules NODE_PATH=/opt/deps/node_modules ember build - // var nodePath = process.env.EMBER_NODE_PATH; var contextPath = path.resolve(context); @@ -38,14 +60,15 @@ module.exports = function nodeModulesPath(context) { var configuredPath = path.resolve(nodePath); // The contextPath is likely the project root, or possibly a subdirectory in - // node_modules/ nested dependencies. If it is more specific (a subdirectory - // of) than the configuredPath prefer the more specific contextPath. + // node_modules/ nested dependencies. If it is more specific than the + // the configuredPath (i.e. it is a subdirectory of the configuredPath) + // prefer the more specific contextPath. if (isSubdirectoryOf(configuredPath, contextPath)) { - return path.resolve(contextPath,'node_modules'); + return path.resolve(contextPath, 'node_modules'); } else { return path.resolve(nodePath); } } else { - return path.resolve(contextPath,'node_modules'); + return path.resolve(contextPath, 'node_modules'); } }; diff --git a/lib/models/addon-discovery.js b/lib/models/addon-discovery.js index 916590b1de4..48b0bdb8cd0 100644 --- a/lib/models/addon-discovery.js +++ b/lib/models/addon-discovery.js @@ -4,13 +4,13 @@ @module ember-cli */ -var assign = require('lodash/object/assign'); -var debug = require('debug')('ember-cli:addon-discovery'); -var fs = require('fs'); -var path = require('path'); -var CoreObject = require('core-object'); -var resolve = require('resolve'); -var findup = require('findup'); +var assign = require('lodash/object/assign'); +var debug = require('debug')('ember-cli:addon-discovery'); +var fs = require('fs'); +var path = require('path'); +var CoreObject = require('core-object'); +var resolve = require('resolve'); +var findup = require('findup'); /** AddonDiscovery is responsible for collecting information about all of the diff --git a/lib/models/project.js b/lib/models/project.js index f7506c470d2..dcee77eb596 100644 --- a/lib/models/project.js +++ b/lib/models/project.js @@ -70,8 +70,16 @@ Project.prototype.setupBowerDirectory = function() { debug('bowerDirectory: %s', this.bowerDirectory); }; +/** + Sets the path to the node_modules directory for this + project. + + @private + @method setupNodeModulesPath + */ Project.prototype.setupNodeModulesPath = function(){ this.nodeModulesPath = nodeModulesPath(this.root); + debug('nodeModulesPath: %s', this.nodeModulesPath); }; var NULL_PROJECT = new Project(process.cwd(), {}); diff --git a/tests/unit/models/project-test.js b/tests/unit/models/project-test.js index e1128331210..994a6db589f 100644 --- a/tests/unit/models/project-test.js +++ b/tests/unit/models/project-test.js @@ -489,7 +489,7 @@ describe('models/project.js', function() { makeProject(); - expect(project.nodeModulesPath).to.equal(nodePath); + expect(project.nodeModulesPath).to.equal(path.resolve(nodePath)); }); it('should equal project.root joined with "node_modules" when EMBER_NODE_PATH is not set', function() {