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