/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ import fs from 'fs-extra'; import path from 'path'; // This function searches all the folders which can be used to // resolve dependencies for package located in "packageDir", including // all its transitive dependencies. It scans all the parent directories // up the file tree and grabs all "node_modules" inside these directories. // Aditionally, it resolves all the symlinks found in any of these "node_modules" // directories and repeat the same process for each resolved target directory. export default async (packageDir: string): Promise => { if (!(await fs.pathExists(packageDir))) { return []; } const packagesToProcess = [packageDir]; const processedPackages = new Set(); processedPackages.add(packageDir); const watchDirs = new Set(); while (packagesToProcess.length > 0) { let currentDir = packagesToProcess.shift() as string; watchDirs.add(currentDir); const {dependencies, peerDependencies, devDependencies} = await fs.readJson( path.join(currentDir, 'package.json'), ); const dependenciesSet = new Set([ ...Object.keys(dependencies ?? {}), ...Object.keys(devDependencies ?? {}), ...Object.keys(peerDependencies ?? {}), ]); while (dependenciesSet.size > 0) { const nodeModulesDir = path.join(currentDir, 'node_modules'); if (await fs.pathExists(nodeModulesDir)) { watchDirs.add(nodeModulesDir); for (const moduleName of dependenciesSet) { const isModuleNameScoped = moduleName.includes('/'); const fullModulePath = path.join(nodeModulesDir, moduleName); if (await fs.pathExists(fullModulePath)) { dependenciesSet.delete(moduleName); const stat = await fs.lstat(fullModulePath); if (stat.isSymbolicLink()) { const targetDir = await fs.readlink(fullModulePath); let absoluteTargetDir; if (path.isAbsolute(targetDir)) { absoluteTargetDir = targetDir; } else if (isModuleNameScoped) { const scope = moduleName.split('/')[0]; const scopeDir = path.join(nodeModulesDir, scope); absoluteTargetDir = path.resolve(scopeDir, targetDir); } else { absoluteTargetDir = path.resolve(nodeModulesDir, targetDir); } if (!processedPackages.has(absoluteTargetDir)) { packagesToProcess.push(absoluteTargetDir); processedPackages.add(absoluteTargetDir); } } } } } const parentDir = path.dirname(currentDir); if (!parentDir || parentDir === '/' || parentDir === currentDir) { break; } currentDir = parentDir; } } return [...watchDirs]; };