'use strict';
var path = require('path');
var fs = require('fs');
// Due to the fact that the node community doesn't feel that
// the node_modules resolution algorithm should be public method
// we have copy pasta'd it here.
function nodeModulePaths(from) {
// guarantee that 'from' is absolute.
from = path.resolve(from);
// note: this approach *only* works when the path is guaranteed
// to be absolute. Doing a fully-edge-case-correct path.split
// that works on both Windows and Posix is non-trivial.
var splitRe = process.platform === 'win32' ? /[\/\\]/ : /\//;
var paths = [];
var parts = from.split(splitRe);
for (var tip = parts.length - 1; tip >= 0; tip--) {
// don't search in .../node_modules/node_modules
if (parts[tip] === 'node_modules') {
continue;
}
var dir = parts.slice(0, tip + 1).concat('node_modules').join(path.sep);
paths.push(dir);
}
return paths;
}
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 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) {
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 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');
} else {
return path.resolve(nodePath);
}
} else {
var paths = nodeModulePaths(contextPath);
for (var i = 0, l = paths.length; i < l; i++) {
var nodeModulePathUnderTest = paths[i];
if (fs.existsSync(nodeModulePathUnderTest)) {
return nodeModulePathUnderTest;
}
}
return null;
}
};