[6a3a178] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | var fs = require('fs'),
|
---|
| 4 | path = require('path');
|
---|
| 5 |
|
---|
| 6 | var cache;
|
---|
| 7 |
|
---|
| 8 | /**
|
---|
| 9 | * Perform <code>path.relative()</code> but try to detect and correct sym-linked node modules.
|
---|
| 10 | * @param {string} from The base path
|
---|
| 11 | * @param {string} to The full path
|
---|
| 12 | */
|
---|
| 13 | function enhancedRelative(from, to) {
|
---|
| 14 |
|
---|
| 15 | // relative path
|
---|
| 16 | var relative = path.relative(from, to);
|
---|
| 17 |
|
---|
| 18 | // trailing is the relative path portion without any '../'
|
---|
| 19 | var trailing = relative.replace(/^\.{2}[\\\/]/, ''),
|
---|
| 20 | leading = to.replace(trailing, '');
|
---|
| 21 |
|
---|
| 22 | // within project is what we want
|
---|
| 23 | var isInProject = (relative === trailing);
|
---|
| 24 | if (isInProject) {
|
---|
| 25 | return relative;
|
---|
| 26 | }
|
---|
| 27 | // otherwise look at symbolic linked modules
|
---|
| 28 | else {
|
---|
| 29 | var splitTrailing = trailing.split(/[\\\/]/);
|
---|
| 30 |
|
---|
| 31 | // ensure failures can retry with fresh cache
|
---|
| 32 | for (var i = cache ? 2 : 1, foundPath = false; (i > 0) && !foundPath; i--) {
|
---|
| 33 |
|
---|
| 34 | // ensure cache
|
---|
| 35 | cache = cache || indexLinkedModules(from);
|
---|
| 36 |
|
---|
| 37 | // take elements from the trailing path and append them the the leading path in an attempt to find a package.json
|
---|
| 38 | for (var j = 0; (j < splitTrailing.length) && !foundPath; j++) {
|
---|
| 39 |
|
---|
| 40 | // find the name of packages in the actual file location
|
---|
| 41 | // start at the lowest concrete directory that appears in the relative path
|
---|
| 42 | var packagePath = path.join.apply(path, [leading].concat(splitTrailing.slice(0, j + 1))),
|
---|
| 43 | packageJsonPath = path.join(packagePath, 'package.json'),
|
---|
| 44 | packageName = fs.existsSync(packageJsonPath) && require(packageJsonPath).name;
|
---|
| 45 |
|
---|
| 46 | // lookup any package name in the cache
|
---|
| 47 | var linkedPackagePath = !!packageName && cache[packageName];
|
---|
| 48 | if (linkedPackagePath) {
|
---|
| 49 |
|
---|
| 50 | // the remaining portion of the trailing path, not including the package path
|
---|
| 51 | var remainingPath = path.join.apply(path, splitTrailing.slice(j + 1));
|
---|
| 52 |
|
---|
| 53 | // validate the remaining path in the linked location
|
---|
| 54 | // failure implies we will keep trying nested sym-linked packages
|
---|
| 55 | var linkedFilePath = path.join(linkedPackagePath, remainingPath),
|
---|
| 56 | isValid = !!linkedFilePath && fs.existsSync(linkedFilePath) &&
|
---|
| 57 | fs.statSync(linkedFilePath).isFile();
|
---|
| 58 |
|
---|
| 59 | // path is found where valid
|
---|
| 60 | foundPath = isValid && linkedFilePath;
|
---|
| 61 | }
|
---|
| 62 | }
|
---|
| 63 |
|
---|
| 64 | // cache cannot be trusted if a file can't be found
|
---|
| 65 | // set the cache to false to trigger its rebuild
|
---|
| 66 | cache = !!foundPath && cache;
|
---|
| 67 | }
|
---|
| 68 |
|
---|
| 69 | // the relative path should now be within the project
|
---|
| 70 | return foundPath ? path.relative(from, foundPath) : relative;
|
---|
| 71 | }
|
---|
| 72 | }
|
---|
| 73 |
|
---|
| 74 | module.exports = enhancedRelative;
|
---|
| 75 |
|
---|
| 76 | /**
|
---|
| 77 | * Make a hash of linked modules within the given directory by breadth-first search.
|
---|
| 78 | * @param {string} directory A path to start searching
|
---|
| 79 | * @returns {object} A collection of sym-linked paths within the project keyed by their package name
|
---|
| 80 | */
|
---|
| 81 | function indexLinkedModules(directory) {
|
---|
| 82 | var buffer = listSymLinkedModules(directory),
|
---|
| 83 | hash = {};
|
---|
| 84 |
|
---|
| 85 | // while there are items in the buffer
|
---|
| 86 | while (buffer.length > 0) {
|
---|
| 87 | var modulePath = buffer.shift(),
|
---|
| 88 | packageJsonPath = path.join(modulePath, 'package.json'),
|
---|
| 89 | packageName = fs.existsSync(packageJsonPath) && require(packageJsonPath).name;
|
---|
| 90 | if (packageName) {
|
---|
| 91 |
|
---|
| 92 | // add this path keyed by package name, so long as it doesn't exist at a lower level
|
---|
| 93 | hash[packageName] = hash[packageName] || modulePath;
|
---|
| 94 |
|
---|
| 95 | // detect nested module and push to the buffer (breadth-first)
|
---|
| 96 | buffer.push.apply(buffer, listSymLinkedModules(modulePath));
|
---|
| 97 | }
|
---|
| 98 | }
|
---|
| 99 | return hash;
|
---|
| 100 |
|
---|
| 101 | function listSymLinkedModules(directory) {
|
---|
| 102 | var modulesPath = path.join(directory, 'node_modules'),
|
---|
| 103 | hasNodeModules = fs.existsSync(modulesPath) && fs.statSync(modulesPath).isDirectory(),
|
---|
| 104 | subdirectories = !!hasNodeModules && fs.readdirSync(modulesPath) || [];
|
---|
| 105 |
|
---|
| 106 | return subdirectories
|
---|
| 107 | .map(joinDirectory)
|
---|
| 108 | .filter(testIsSymLink);
|
---|
| 109 |
|
---|
| 110 | function joinDirectory(subdirectory) {
|
---|
| 111 | return path.join(modulesPath, subdirectory);
|
---|
| 112 | }
|
---|
| 113 |
|
---|
| 114 | function testIsSymLink(directory) {
|
---|
| 115 | return fs.lstatSync(directory).isSymbolicLink(); // must use lstatSync not statSync
|
---|
| 116 | }
|
---|
| 117 | }
|
---|
| 118 | } |
---|