1import { sync as findUpSync } from 'find-up'; 2import findYarnOrNpmWorkspaceRoot from 'find-yarn-workspace-root'; 3import { existsSync } from 'fs'; 4import path from 'path'; 5 6import type { NodePackageManager } from '../NodePackageManagers'; 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 managerResolutionOrder: NodePackageManager[] = ['yarn', 'npm', 'pnpm']; 13 14/** 15 * Find the `pnpm-workspace.yaml` file that represents the root of the monorepo. 16 * This is a synchronous function based on the original async library. 17 * @see https://github.com/pnpm/pnpm/blob/main/packages/find-workspace-dir/src/index.ts 18 */ 19function findPnpmWorkspaceRoot(projectRoot: string) { 20 const workspaceEnvName = 'NPM_CONFIG_WORKSPACE_DIR'; 21 22 const workspaceEnvValue = 23 process.env[workspaceEnvName] ?? process.env[workspaceEnvName.toLowerCase()]; 24 const manifestLocation = workspaceEnvValue 25 ? path.join(workspaceEnvValue, PNPM_WORKSPACE_FILE) 26 : findUpSync(PNPM_WORKSPACE_FILE, { cwd: projectRoot }); 27 28 return manifestLocation ? path.dirname(manifestLocation) : null; 29} 30 31/** Wraps `findYarnOrNpmWorkspaceRoot` and guards against having an empty `package.json` file in an upper directory. */ 32export function findYarnOrNpmWorkspaceRootSafe(projectRoot: string): string | null { 33 try { 34 return findYarnOrNpmWorkspaceRoot(projectRoot); 35 } catch (error: any) { 36 if (error.message.includes('Unexpected end of JSON input')) { 37 return null; 38 } 39 throw error; 40 } 41} 42 43/** 44 * Resolve the workspace root for a project, if its part of a monorepo. 45 * Optionally, provide a specific packager to only resolve that one specifically. 46 * 47 * By default, this tries to resolve the workspaces in order of: 48 * - npm 49 * - yarn 50 * - pnpm 51 */ 52export function findWorkspaceRoot( 53 projectRoot: string, 54 packageManager?: NodePackageManager 55): string | null { 56 const strategies: Record<NodePackageManager, (projectRoot: string) => string | null> = { 57 npm: findYarnOrNpmWorkspaceRootSafe, 58 yarn: findYarnOrNpmWorkspaceRootSafe, 59 pnpm: findPnpmWorkspaceRoot, 60 }; 61 62 if (packageManager) { 63 return strategies[packageManager](projectRoot); 64 } 65 66 for (const strategy of managerResolutionOrder.map((name) => strategies[name])) { 67 const root = strategy(projectRoot); 68 if (root) { 69 return root; 70 } 71 } 72 73 return null; 74} 75 76/** 77 * Resolve the used node package manager for a project by checking the lockfile. 78 * This also tries to resolve the workspace root, if its part of a monorepo. 79 * Optionally, provide a specific packager to only resolve that one specifically. 80 * 81 * By default, this tries to resolve the workspaces in order of: 82 * - npm 83 * - yarn 84 * - pnpm 85 */ 86export function resolvePackageManager( 87 projectRoot: string, 88 packageManager?: NodePackageManager 89): NodePackageManager | null { 90 const workspaceRoot = findWorkspaceRoot(projectRoot, packageManager) || projectRoot; 91 const lockfileNames: Record<NodePackageManager, string> = { 92 npm: NPM_LOCK_FILE, 93 yarn: YARN_LOCK_FILE, 94 pnpm: PNPM_LOCK_FILE, 95 }; 96 97 if (packageManager) { 98 const lockfilePath = path.join(workspaceRoot, lockfileNames[packageManager]); 99 if (existsSync(lockfilePath)) { 100 return packageManager; 101 } 102 return null; 103 } 104 105 for (const manager of managerResolutionOrder) { 106 const lockfilePath = path.join(workspaceRoot, lockfileNames[manager]); 107 if (existsSync(lockfilePath)) { 108 return manager; 109 } 110 } 111 112 return null; 113} 114 115/** 116 * Returns true if the project is using yarn, false if the project is using another package manager. 117 */ 118export function isUsingYarn(projectRoot: string): boolean { 119 return !!resolvePackageManager(projectRoot, 'yarn'); 120} 121 122/** 123 * Returns true if the project is using npm, false if the project is using another package manager. 124 */ 125export function isUsingNpm(projectRoot: string): boolean { 126 return !!resolvePackageManager(projectRoot, 'npm'); 127} 128