diff --git a/packages/cli-tools/src/__tests__/resolveNodeModuleDir.test.ts b/packages/cli-tools/src/__tests__/resolveNodeModuleDir.test.ts new file mode 100644 index 000000000..2a28281d8 --- /dev/null +++ b/packages/cli-tools/src/__tests__/resolveNodeModuleDir.test.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import resolveNodeModuleDir, { + alternateResolveNodeModuleDir, +} from '../resolveNodeModuleDir'; + +import packageJson from '../../package.json'; + +describe('alternateResolveNodeModulesDir', () => { + it('should be identical to default resolution for this projects dependencies', () => { + const packages = [ + ...Object.keys(packageJson.dependencies), + ...Object.keys(packageJson.devDependencies), + ].filter((a) => !a.match('@react-native-community') && !a.match('@types')); + + packages.forEach((pkg) => { + const dir = resolveNodeModuleDir(__dirname, pkg); + const alternateDir = alternateResolveNodeModuleDir(__dirname, pkg); + expect(dir).toEqual(alternateDir); + }); + }); + + it('should return identical output for package deep file requires', () => { + const pkgs = [ + 'lodash/intersection.js', + 'lodash/package.json', + 'yargs/locales/en.json', + ]; + pkgs.forEach((pkg) => { + const dir = resolveNodeModuleDir(__dirname, pkg); + const alternateDir = alternateResolveNodeModuleDir(__dirname, pkg); + expect(dir).toEqual(alternateDir); + }); + }); + + it('should blow up for non-existent packages', () => { + const pkgs = ['blerbqwerzxcqwerv', 'qwerasdfasdfertrr/asdfasdf']; + pkgs.forEach((pkg) => { + expect(() => { + alternateResolveNodeModuleDir(__dirname, pkg); + }).toThrow(); + }); + }); +}); diff --git a/packages/cli-tools/src/resolveNodeModuleDir.ts b/packages/cli-tools/src/resolveNodeModuleDir.ts index 7a03950cd..848f0f605 100644 --- a/packages/cli-tools/src/resolveNodeModuleDir.ts +++ b/packages/cli-tools/src/resolveNodeModuleDir.ts @@ -7,9 +7,43 @@ export default function resolveNodeModuleDir( root: string, packageName: string, ): string { - return path.dirname( - require.resolve(path.join(packageName, 'package.json'), { - paths: [root], - }), - ); + try { + return path.dirname( + require.resolve(path.join(packageName, 'package.json'), { + paths: [root], + }), + ); + } catch (e) { + //TODO: Remove this alternate method once Node figures out a better way of resolving the package root directory. See https://github.com/nodejs/loaders/issues/26 + return alternateResolveNodeModuleDir(root, packageName); + } +} + +export function alternateResolveNodeModuleDir( + root: string, + packageName: string, +) { + const moduleMainFilePath = require.resolve(packageName, {paths: [root]}); + + const moduleNameParts = packageName.split('/'); + + let searchForPathSection; + + if (packageName.startsWith('@') && moduleNameParts.length > 1) { + const [org, mod] = moduleNameParts; + searchForPathSection = `node_modules${path.sep}${org}${path.sep}${mod}`; + } else { + const [mod] = moduleNameParts; + searchForPathSection = `node_modules${path.sep}${mod}`; + } + + const lastIndex = moduleMainFilePath.lastIndexOf(searchForPathSection); + + if (lastIndex === -1) { + throw new Error( + `Unable to resolve base directory of package ${packageName}. Searched inside the resolved main file path "${moduleMainFilePath}" using "${searchForPathSection}"`, + ); + } + + return moduleMainFilePath.slice(0, lastIndex + searchForPathSection.length); }