import fs from 'node:fs'; import path from 'node:path'; import { resolvePath } from '@libs/resolve-path'; const PROJECT_ROOT_MEMO = new Map(); /** * Finds the root directory of a project by looking for specific indicator files * (e.g., package-lock.json, pnpm-lock.yaml, etc.) in the directory hierarchy. * * The function starts from a given directory and traverses up the directory * tree until it finds a directory containing any of the specified indicator * files. * * If it reaches the root of the filesystem without finding any indicator files, * it throws an error. * * The results are memoized to optimize subsequent calls with the same starting * directory. * * @param from - The starting directory from which to search for the project * root. * @param indicatorFiles - An array of filenames that indicate the project root. * Defaults to common lock files used in JavaScript projects. * @param memo - An internal parameter used for memoization during recursion. * @returns The path to the project root directory. * @throws If the project root cannot be found after reaching the filesystem * root. */ export function getProjectRoot( from: string, indicatorFiles: string[] = [ 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lockb', 'deno.lock', ], memo: string[] = [], ): string { if (!PROJECT_ROOT_MEMO.has(from)) { memo.push(from); const dirent = fs.readdirSync(resolvePath(from), { withFileTypes: true }); const hasIndicatorFile = dirent.some((entity) => { return indicatorFiles.includes(entity.name); }); if (!hasIndicatorFile) { if (from === '/') { throw new Error('Failed to find the project root'); } return getProjectRoot(path.dirname(from), indicatorFiles, memo); } memo.forEach((pathname) => { PROJECT_ROOT_MEMO.set(pathname, from); }); } return PROJECT_ROOT_MEMO.get(from)!; }