1import { sync as findUpSync } from 'find-up'; 2import findYarnOrNpmWorkspaceRootUnsafe from 'find-yarn-workspace-root'; 3import fs from 'fs'; 4import yaml from 'js-yaml'; 5import micromatch from 'micromatch'; 6import path from 'path'; 7 8export const NPM_LOCK_FILE = 'package-lock.json'; 9export const YARN_LOCK_FILE = 'yarn.lock'; 10export const PNPM_LOCK_FILE = 'pnpm-lock.yaml'; 11export const PNPM_WORKSPACE_FILE = 'pnpm-workspace.yaml'; 12export const BUN_LOCK_FILE = 'bun.lockb'; 13 14/** Wraps `find-yarn-workspace-root` and guards against having an empty `package.json` file in an upper directory. */ 15export function findYarnOrNpmWorkspaceRoot(projectRoot: string): string | null { 16 try { 17 return findYarnOrNpmWorkspaceRootUnsafe(projectRoot); 18 } catch (error: any) { 19 if (error.message.includes('Unexpected end of JSON input')) { 20 return null; 21 } 22 throw error; 23 } 24} 25 26/** 27 * Find the `pnpm-workspace.yaml` file that represents the root of the monorepo. 28 * This is a synchronous function based on the original async library. 29 * @see https://github.com/pnpm/pnpm/blob/main/packages/find-workspace-dir/src/index.ts 30 */ 31export function findPnpmWorkspaceRoot(projectRoot: string): string | null { 32 const workspaceEnvName = 'NPM_CONFIG_WORKSPACE_DIR'; 33 const workspaceEnvValue = 34 process.env[workspaceEnvName] ?? process.env[workspaceEnvName.toLowerCase()]; 35 36 const workspaceFile = workspaceEnvValue 37 ? path.join(workspaceEnvValue, PNPM_WORKSPACE_FILE) 38 : findUpSync(PNPM_WORKSPACE_FILE, { cwd: projectRoot }); 39 40 if (!workspaceFile || !fs.existsSync(workspaceFile)) { 41 return null; 42 } 43 44 try { 45 // See: https://pnpm.io/pnpm-workspace_yaml 46 const { packages: workspaces } = yaml.load(fs.readFileSync(workspaceFile, 'utf8')); 47 // See: https://github.com/square/find-yarn-workspace-root/blob/11f6e31d3fa15a5bb7b7419f0091390e4c16204c/index.js#L26-L33 48 const workspaceRoot = path.dirname(workspaceFile); 49 const relativePath = path.relative(workspaceRoot, projectRoot); 50 51 if (relativePath === '' || micromatch([relativePath], workspaces).length > 0) { 52 return workspaceRoot; 53 } 54 } catch { 55 // TODO: implement debug logger? 56 return null; 57 } 58 59 return null; 60} 61