import { sync as findUpSync } from 'find-up'; import findYarnOrNpmWorkspaceRoot from 'find-yarn-workspace-root'; import { existsSync } from 'fs'; import path from 'path'; import type { NodePackageManager } from '../NodePackageManagers'; export const NPM_LOCK_FILE = 'package-lock.json'; export const YARN_LOCK_FILE = 'yarn.lock'; export const PNPM_LOCK_FILE = 'pnpm-lock.yaml'; export const PNPM_WORKSPACE_FILE = 'pnpm-workspace.yaml'; export const managerResolutionOrder: NodePackageManager[] = ['yarn', 'npm', 'pnpm']; /** * Find the `pnpm-workspace.yaml` file that represents the root of the monorepo. * This is a synchronous function based on the original async library. * @see https://github.com/pnpm/pnpm/blob/main/packages/find-workspace-dir/src/index.ts */ function findPnpmWorkspaceRoot(projectRoot: string) { const workspaceEnvName = 'NPM_CONFIG_WORKSPACE_DIR'; const workspaceEnvValue = process.env[workspaceEnvName] ?? process.env[workspaceEnvName.toLowerCase()]; const manifestLocation = workspaceEnvValue ? path.join(workspaceEnvValue, PNPM_WORKSPACE_FILE) : findUpSync(PNPM_WORKSPACE_FILE, { cwd: projectRoot }); return manifestLocation ? path.dirname(manifestLocation) : null; } /** Wraps `findYarnOrNpmWorkspaceRoot` and guards against having an empty `package.json` file in an upper directory. */ export function findYarnOrNpmWorkspaceRootSafe(projectRoot: string): string | null { try { return findYarnOrNpmWorkspaceRoot(projectRoot); } catch (error: any) { if (error.message.includes('Unexpected end of JSON input')) { return null; } throw error; } } /** * Resolve the workspace root for a project, if its part of a monorepo. * Optionally, provide a specific packager to only resolve that one specifically. * * By default, this tries to resolve the workspaces in order of: * - npm * - yarn * - pnpm */ export function findWorkspaceRoot( projectRoot: string, packageManager?: NodePackageManager ): string | null { const strategies: Record string | null> = { npm: findYarnOrNpmWorkspaceRootSafe, yarn: findYarnOrNpmWorkspaceRootSafe, pnpm: findPnpmWorkspaceRoot, }; if (packageManager) { return strategies[packageManager](projectRoot); } for (const strategy of managerResolutionOrder.map((name) => strategies[name])) { const root = strategy(projectRoot); if (root) { return root; } } return null; } /** * Resolve the used node package manager for a project by checking the lockfile. * This also tries to resolve the workspace root, if its part of a monorepo. * Optionally, provide a specific packager to only resolve that one specifically. * * By default, this tries to resolve the workspaces in order of: * - npm * - yarn * - pnpm */ export function resolvePackageManager( projectRoot: string, packageManager?: NodePackageManager ): NodePackageManager | null { const workspaceRoot = findWorkspaceRoot(projectRoot, packageManager) || projectRoot; const lockfileNames: Record = { npm: NPM_LOCK_FILE, yarn: YARN_LOCK_FILE, pnpm: PNPM_LOCK_FILE, }; if (packageManager) { const lockfilePath = path.join(workspaceRoot, lockfileNames[packageManager]); if (existsSync(lockfilePath)) { return packageManager; } return null; } for (const manager of managerResolutionOrder) { const lockfilePath = path.join(workspaceRoot, lockfileNames[manager]); if (existsSync(lockfilePath)) { return manager; } } return null; } /** * Returns true if the project is using yarn, false if the project is using another package manager. */ export function isUsingYarn(projectRoot: string): boolean { return !!resolvePackageManager(projectRoot, 'yarn'); } /** * Returns true if the project is using npm, false if the project is using another package manager. */ export function isUsingNpm(projectRoot: string): boolean { return !!resolvePackageManager(projectRoot, 'npm'); }