@skarab/detect-package-manager
Version:
Detects which package manager (bun, pnpm, yarn, npm) is used based on the current working directory.
131 lines (130 loc) • 4.68 kB
JavaScript
import { basename } from 'node:path';
import { readFileSync } from 'node:fs';
import { findFile, walkDirectoryUp } from './fs.js';
import { tryGetCommandVersion } from './version.js';
export const agents = ['bun', 'pnpm', 'yarn', 'npm'];
/**
* Detects all (known) agents installed on the system.
*/
export async function detectInstalledAgents() {
const agentsFound = new Map();
for (const name of agents) {
const version = await tryGetCommandVersion(name);
if (version) {
agentsFound.set(name, { name, version });
}
}
return agentsFound;
}
/**
* Try to find the used agent from a directory with the following rules:
*
* - Try to find the agent in `packageManager` field from `package.json`.
* - If not found, try to find the agent from lock file name.
* - If not found, stats again in parent directory.
*
* @param directory If no directory is provided, the current working directory will be used.
*/
export function detectAgent(directory) {
return walkDirectoryUp(async (directory) => {
let agent = await detectAgentFromPackageJSON(directory);
if (!agent) {
agent = await detectAgentFromLockFile(directory);
}
return agent;
}, directory);
}
/**
* Try to find the used agent defined in `packageManager` field from `package.json`.
*
* @param directory If no directory is provided, the current working directory will be used.
*/
export async function detectAgentFromPackageJSON(directory = process.cwd()) {
const packageJSONPath = await findFile('package.json', directory);
if (!packageJSONPath) {
return;
}
const packageJSONContent = readFileSync(packageJSONPath, 'utf8');
const { packageManager } = JSON.parse(packageJSONContent);
if (!packageManager) {
return;
}
const [name, version] = packageManager.split('@');
return { name, version };
}
export const lockFiles = [
'pnpm-lock.yaml',
'yarn.lock',
'package-lock.json',
'bun.lockb',
'shrinkwrap.yaml',
'npm-shrinkwrap.json',
];
export function detectNPMVersionFromLockFile(path) {
// https://docs.npmjs.com/cli/v8/configuring-npm/package-lock-json#lockfileversion
const { lockfileVersion } = JSON.parse(readFileSync(path, 'utf8'));
switch (lockfileVersion) {
case 1:
return { name: 'npm', version: '5 - 6' };
case 2:
return { name: 'npm', version: '>=5' };
case 3:
return { name: 'npm', version: '>=7' };
default:
return { name: 'npm', version: '<5' };
}
}
export const pnpmLockFileVersionToVersion = {
'5.4': '>=7.0.0',
'5.3': '>=6.0.0',
'5.2': '>=5.10.0',
'5.1': '>=3.5.0',
'5.0': '>=3.0.0',
'4.0': '>=2.17.0',
'3.9': '>=2.13.3',
'3.8': '>=2.8.0',
'3.7': '>=2.0.0',
'3.6': '>=1.43.0',
'3.5': '>=1.40.0',
'3.4': '>=1.23.0',
'3.3': '>=1.22.0',
'3.2': '>=1.18.1',
'3.1': '1.17 - 1.18.0',
'3': '1 - 1.16',
'2': '0.62 - 0',
};
export function detectPNPMVersionFromLockFile(path) {
var _a;
// https://github.com/pnpm/spec/tree/master/lockfile
const content = readFileSync(path, 'utf8');
const [, lockfileVersion] = (_a = content.match(/^lockfileVersion: (\d(\.\d)?)$/m)) !== null && _a !== void 0 ? _a : [];
if (!lockfileVersion) {
return { name: 'pnpm', version: '<0.62' };
}
const version = pnpmLockFileVersionToVersion[lockfileVersion];
return { name: 'pnpm', version: version !== null && version !== void 0 ? version : Object.values(pnpmLockFileVersionToVersion)[0] };
}
export const lockFileVersionDetector = {
'pnpm-lock.yaml': detectPNPMVersionFromLockFile,
'shrinkwrap.yaml': detectPNPMVersionFromLockFile,
'package-lock.json': detectNPMVersionFromLockFile,
'npm-shrinkwrap.json': detectNPMVersionFromLockFile,
'bun.lockb': () => ({ name: 'bun', version: '>=0' }),
'yarn.lock': (path) => {
// https://github.com/yarnpkg/berry/blob/635ed55d7582fe6ee1af4c4b2e033f0fdc33fd2f/packages/yarnpkg-core/sources/scriptUtils.ts#L87
const version = /^__metadata:$/m.test(readFileSync(path, 'utf8')) ? '2' : '1';
return { name: 'yarn', version };
},
};
/**
* Try to find the used agent from lock file name and/or content.
*
* @param directory If no directory is provided, the current working directory will be used.
*/
export async function detectAgentFromLockFile(directory = process.cwd()) {
const lockFilePath = await findFile(lockFiles, directory);
if (!lockFilePath) {
return;
}
return lockFileVersionDetector[basename(lockFilePath)](lockFilePath);
}