which-package-manager
Version:
Detect which package manager is used in the current project
168 lines • 6.81 kB
JavaScript
import { exec } from 'node:child_process';
import { readFile, stat } from 'node:fs/promises';
import { dirname, join, relative } from 'node:path';
import process from 'node:process';
import { promisify } from 'node:util';
import micromatch from 'micromatch';
import { findUp } from 'find-up';
const packageManagers = ['npm', 'pnpm', 'yarn'];
const lockFilesForPackageManager = {
npm: 'package-lock.json',
pnpm: 'pnpm-lock.yaml',
yarn: 'yarn.lock',
};
const getPackageManagerField = (packageJson) => {
const [packageManager, version] = packageJson.packageManager?.split('@') ?? [];
if (packageManager) {
return { packageManager: packageManager, version };
}
return undefined;
};
const isFile = async (file) => {
try {
const fileStat = await stat(file);
return fileStat.isFile();
}
catch {
return false;
}
};
export const hasLockFile = async (pm, cwd) => isFile(join(cwd, lockFilesForPackageManager[pm]));
/**
* Yarn's package.json private field must be true and workspaces field can be a structure.
*/
const checkYarnWorkspaceRoot = (packageJson) => {
if (packageJson.private === true) {
const workspaces = Array.isArray(packageJson.workspaces) ? packageJson.workspaces : packageJson.workspaces.packages;
return { compatible: true, workspaces };
}
return { compatible: false };
};
/**
* Npm's package.json workspaces field must be an array.
*/
const checkNpmWorkspaceRoot = (packageJson) => {
const workspaces = Array.isArray(packageJson.workspaces) ? packageJson.workspaces : undefined;
return workspaces === undefined ? { compatible: false } : { compatible: true, workspaces };
};
const getPackageJson = async (file) => {
const packageJsonRawContent = await readFile(file);
return JSON.parse(packageJsonRawContent.toString());
};
const findLockFile = async (cwd) => {
const unfilteredLockFiles = await Promise.all(packageManagers.map(async (pm) => ((await hasLockFile(pm, cwd)) ? pm : undefined)));
const detectedLockFiles = unfilteredLockFiles.filter(pm => pm !== undefined);
if (detectedLockFiles.length > 1) {
throw new Error(`Lock files for multiples package managers found: ${detectedLockFiles.join(', ')}`);
}
return detectedLockFiles[0];
};
const checkWorkspaceRoot = async (cwd, packageJson) => {
if (await isFile(join(cwd, 'pnpm-workspace.yaml'))) {
return { isRoot: true, compatiblePackageManager: ['pnpm'], workspaces: ['**'] };
}
// Workspaces field is supported by yarn and npm
if (packageJson.workspaces) {
const detectWorkspace = { isRoot: true, compatiblePackageManager: [], workspaces: [] };
const npmWorkspace = checkNpmWorkspaceRoot(packageJson);
if (npmWorkspace.compatible) {
detectWorkspace.workspaces = npmWorkspace.workspaces;
detectWorkspace.compatiblePackageManager.push('npm');
}
const yarnWorkspace = checkYarnWorkspaceRoot(packageJson);
if (yarnWorkspace.compatible) {
detectWorkspace.workspaces = yarnWorkspace.workspaces;
detectWorkspace.compatiblePackageManager.push('yarn');
}
return detectWorkspace;
}
return { isRoot: false };
};
const getWorkspaceRoot = async (cwd) => {
const packageJsonRoot = await findUp('package.json', { cwd: join(cwd, '..') });
if (!packageJsonRoot) {
return undefined;
}
const workspaceRoot = dirname(packageJsonRoot);
const packageJson = await getPackageJson(packageJsonRoot);
const packageManagerField = getPackageManagerField(packageJson);
const detectWorkspace = await checkWorkspaceRoot(workspaceRoot, packageJson);
const relativeToPackageJson = relative(workspaceRoot, cwd);
if (detectWorkspace.isRoot && micromatch.isMatch(relativeToPackageJson, detectWorkspace.workspaces)) {
const lockFile = await findLockFile(workspaceRoot);
return {
compatiblePackageManager: lockFile ? [lockFile] : detectWorkspace.compatiblePackageManager,
lockFile,
workspaceRoot,
packageManagerField,
};
}
return undefined;
};
export const detectPackageStructure = async ({ cwd = process.cwd() }) => {
const packageJsonFile = join(cwd, 'package.json');
const hasPackageJson = await isFile(join(packageJsonFile));
let packageManagerField;
if (hasPackageJson) {
const packageJson = await getPackageJson(packageJsonFile);
packageManagerField = getPackageManagerField(packageJson);
const lockFile = await findLockFile(cwd);
if (lockFile) {
return {
lockFile,
compatiblePackageManager: [lockFile],
packageManagerField,
};
}
const detectWorkspace = await checkWorkspaceRoot(cwd, packageJson);
if (detectWorkspace.isRoot) {
const { compatiblePackageManager } = detectWorkspace;
return { compatiblePackageManager, packageManagerField };
}
}
const findWorkspaceRoot = await getWorkspaceRoot(cwd);
if (findWorkspaceRoot) {
const { workspaceRoot, compatiblePackageManager, packageManagerField: workspacePackageManagerField } = findWorkspaceRoot;
return {
workspaceRoot,
compatiblePackageManager,
packageManagerField: workspacePackageManagerField,
};
}
return {
compatiblePackageManager: packageManagers,
packageManagerField,
};
};
export const whichPackageManager = async ({ cwd, preferred = [], checkExecutable, ignorePackageManagerField, } = {}) => {
const structure = await detectPackageStructure({ cwd });
if (structure.compatiblePackageManager?.length === 1) {
return structure.compatiblePackageManager[0];
}
const packageManagerFromField = structure.packageManagerField?.packageManager;
// Package Manager from package.json field should be compatible.
if (packageManagerFromField &&
!ignorePackageManagerField &&
structure.compatiblePackageManager?.includes(packageManagerFromField)) {
return packageManagerFromField;
}
for (const pm of preferred) {
if (structure.compatiblePackageManager?.includes(pm)) {
if (!checkExecutable) {
return pm;
}
try {
const promise = promisify(exec)(`${pm} -v`);
// eslint-disable-next-line no-await-in-loop
await promise;
if (promise.child.exitCode === 0) {
return pm;
}
/* c8 ignore next */
}
catch { }
}
}
return undefined;
};
//# sourceMappingURL=index.js.map