diff --git a/index.js b/index.js index b6511d0..7b3ca6e 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ 'use strict'; const fs = require('node:fs'); +const path = require('node:path'); const { debuglog } = require('node:util'); const cabinet = require('filing-cabinet'); const precinct = require('precinct'); @@ -162,6 +163,7 @@ function traverse(config = {}) { for (const dependency of dependencies) { const localConfig = config.clone(); localConfig.filename = dependency; + localConfig.directory = getDirectory(localConfig); if (localConfig.isListForm) { for (const item of traverse(localConfig)) { @@ -193,3 +195,25 @@ function dedupeNonExistent(nonExistent) { i++; } } + +// If the file is in a node module, use the root directory of the module +function getDirectory(localConfig) { + if (!localConfig.filename.includes('node_modules')) { + return localConfig.directory; + } + + return getProjectPath(path.dirname(localConfig.filename)) || localConfig.directory; +} + +function getProjectPath(filename) { + try { + const nodeModuleParts = filename.split('node_modules'); + const packageSubPathPath = nodeModuleParts.pop().split(path.sep).filter(Boolean); + const packageName = packageSubPathPath[0].startsWith('@') ? `${packageSubPathPath[0]}${path.sep}${packageSubPathPath[1]}` : packageSubPathPath[0]; + + return path.normalize([...nodeModuleParts, `${path.sep}${packageName}`].join('node_modules')); + } catch { + debug(`Could not determine the root directory of package file ${filename}. Using default`); + return null; + } +} diff --git a/test/test.mjs b/test/test.mjs index aa705d8..8a97d8d 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -472,6 +472,77 @@ describe('dependencyTree', () => { }); }); + describe('it uses package specific node_module directory when resolving package dependencies', () => { + testTreesForFormat('commonjs'); + + it('it can find sub package in node module package', () => { + mockfs({ + [path.join(__dirname, '/es6')]: { + 'module.entry.js': 'import * as module from "parent_module_a"', + node_modules: { + parent_module_a: { + 'index.main.js': 'import * as child_module from "child_node_module"; module.exports = child_module;', + 'package.json': '{ "main": "index.main.js"}', + node_modules: { + child_node_module: { + 'index.main.js': 'module.exports = "child_node_module_of_parent_a"', + 'package.json': '{ "main": "index.main.js"}' + } + } + } + } + } + }); + + const directory = path.join(__dirname, '/es6'); + const filename = path.normalize(`${directory}/module.entry.js`); + + const treeList = dependencyTree({ + filename, + directory, + isListForm: true + }); + + assert.ok(treeList.includes(path.normalize(`${directory}/node_modules/parent_module_a/node_modules/child_node_module/index.main.js`))); + }); + + it('it uses correct version of sub package in node module package', () => { + mockfs({ + [path.join(__dirname, '/es6')]: { + 'module.entry.js': 'import * as module from "parent_module_a"', + node_modules: { + child_node_module: { + 'index.main.js': 'module.exports = "child_node_module"', + 'package.json': '{ "main": "index.main.js", "version": "2.0.0"}' + }, + parent_module_a: { + 'index.main.js': 'import * as child_module from "child_node_module"; module.exports = child_module;', + 'package.json': '{ "main": "index.main.js"}', + node_modules: { + child_node_module: { + 'index.main.js': 'module.exports = "child_node_module_of_parent_a"', + 'package.json': '{ "main": "index.main.js", "version": "1.0.0"}' + } + } + } + } + } + }); + + const directory = path.join(__dirname, '/es6'); + const filename = path.normalize(`${directory}/module.entry.js`); + + const treeList = dependencyTree({ + filename, + directory, + isListForm: true + }); + + assert.ok(!treeList.includes(path.normalize(`${directory}/node_modules/child_node_module/index.main.js`))); + assert.ok(treeList.includes(path.normalize(`${directory}/node_modules/parent_module_a/node_modules/child_node_module/index.main.js`))); + }); + }); + describe('module formats', () => { describe('amd', () => { testTreesForFormat('amd');