1import fs from 'fs'; 2import path from 'path'; 3 4import { 5 findPnpmWorkspaceRoot, 6 findYarnOrNpmWorkspaceRoot, 7 NPM_LOCK_FILE, 8 PNPM_LOCK_FILE, 9 YARN_LOCK_FILE, 10 BUN_LOCK_FILE, 11} from './nodeWorkspaces'; 12import { PackageManagerOptions } from '../PackageManager'; 13import { BunPackageManager } from '../node/BunPackageManager'; 14import { NpmPackageManager } from '../node/NpmPackageManager'; 15import { PnpmPackageManager } from '../node/PnpmPackageManager'; 16import { YarnPackageManager } from '../node/YarnPackageManager'; 17 18export type NodePackageManager = 19 | NpmPackageManager 20 | PnpmPackageManager 21 | YarnPackageManager 22 | BunPackageManager; 23 24export type NodePackageManagerForProject = PackageManagerOptions & 25 Partial<Record<NodePackageManager['name'], boolean>>; 26 27/** The order of the package managers to use when resolving automatically */ 28export const RESOLUTION_ORDER: NodePackageManager['name'][] = ['yarn', 'npm', 'pnpm', 'bun']; 29 30/** 31 * Resolve the workspace root for a project, if its part of a monorepo. 32 * Optionally, provide a specific packager to only resolve that one specifically. 33 */ 34export function findWorkspaceRoot( 35 projectRoot: string, 36 preferredManager?: NodePackageManager['name'] 37): string | null { 38 const strategies: Record<NodePackageManager['name'], (projectRoot: string) => string | null> = { 39 npm: findYarnOrNpmWorkspaceRoot, 40 yarn: findYarnOrNpmWorkspaceRoot, 41 pnpm: findPnpmWorkspaceRoot, 42 bun: findYarnOrNpmWorkspaceRoot, 43 }; 44 45 if (preferredManager) { 46 return strategies[preferredManager](projectRoot); 47 } 48 49 for (const strategy of RESOLUTION_ORDER) { 50 const root = strategies[strategy](projectRoot); 51 if (root) { 52 return root; 53 } 54 } 55 56 return null; 57} 58 59/** 60 * Resolve the used node package manager for a project by checking the lockfile. 61 * This also tries to resolve the workspace root, if its part of a monorepo. 62 * Optionally, provide a preferred packager to only resolve that one specifically. 63 */ 64export function resolvePackageManager( 65 projectRoot: string, 66 preferredManager?: NodePackageManager['name'] 67): NodePackageManager['name'] | null { 68 const root = findWorkspaceRoot(projectRoot, preferredManager) ?? projectRoot; 69 const lockFiles: Record<NodePackageManager['name'], string> = { 70 npm: NPM_LOCK_FILE, 71 pnpm: PNPM_LOCK_FILE, 72 yarn: YARN_LOCK_FILE, 73 bun: BUN_LOCK_FILE, 74 }; 75 76 if (preferredManager) { 77 if (fs.existsSync(path.join(root, lockFiles[preferredManager]))) { 78 return preferredManager; 79 } 80 81 return null; 82 } 83 84 for (const managerName of RESOLUTION_ORDER) { 85 if (fs.existsSync(path.join(root, lockFiles[managerName]))) { 86 return managerName; 87 } 88 } 89 90 return null; 91} 92 93/** 94 * This creates a Node package manager from the provided options. 95 * If these options are not provided, it will infer the package manager from lockfiles. 96 * When no package manager is found, it falls back to npm. 97 */ 98export function createForProject( 99 projectRoot: string, 100 options: NodePackageManagerForProject = {} 101): NodePackageManager { 102 if (options.npm) { 103 return new NpmPackageManager({ cwd: projectRoot, ...options }); 104 } else if (options.yarn) { 105 return new YarnPackageManager({ cwd: projectRoot, ...options }); 106 } else if (options.pnpm) { 107 return new PnpmPackageManager({ cwd: projectRoot, ...options }); 108 } else if (options.bun) { 109 return new BunPackageManager({ cwd: projectRoot, ...options }); 110 } 111 112 switch (resolvePackageManager(projectRoot)) { 113 case 'npm': 114 return new NpmPackageManager({ cwd: projectRoot, ...options }); 115 case 'pnpm': 116 return new PnpmPackageManager({ cwd: projectRoot, ...options }); 117 case 'yarn': 118 return new YarnPackageManager({ cwd: projectRoot, ...options }); 119 case 'bun': 120 return new BunPackageManager({ cwd: projectRoot, ...options }); 121 default: 122 return new NpmPackageManager({ cwd: projectRoot, ...options }); 123 } 124} 125