import { resolve, dirname } from "path"
import { exec, exists } from "builder-util"
export type PM = "npm" | "yarn" | "pnpm" | "bun"
const cache = new Map()
const globalInstallationCache = new Map<string, boolean>()
const lockfileCache = new Map<string, PM>()
* Check if a global pm is available
*/
function hasGlobalInstallation(pm: PM): Promise<boolean> {
const key = `has_global_${pm}`
if (globalInstallationCache.has(key)) {
return Promise.resolve(globalInstallationCache.get(key)!)
}
return exec(pm, ["--version"], { shell: true })
.then(res => {
return /^\d+.\d+.\d+$/.test(res)
})
.then(value => {
globalInstallationCache.set(key, value)
return value
})
.catch(() => false)
}
function getTypeofLockFile(cwd = process.cwd()): Promise<PM> {
const key = `lockfile_${cwd}`
if (lockfileCache.has(key)) {
return Promise.resolve(lockfileCache.get(key)!)
}
return Promise.all([
exists(resolve(cwd, "yarn.lock")),
exists(resolve(cwd, "package-lock.json")),
exists(resolve(cwd, "pnpm-lock.yaml")),
exists(resolve(cwd, "bun.lockb")),
]).then(([isYarn, _, isPnpm, isBun]) => {
let value: PM
if (isYarn) {
value = "yarn"
} else if (isPnpm) {
value = "pnpm"
} else if (isBun) {
value = "bun"
} else {
value = "npm"
}
cache.set(key, value)
return value
})
}
export const detect = async ({ cwd, includeGlobalBun }: { cwd?: string; includeGlobalBun?: boolean } = {}) => {
let type = await getTypeofLockFile(cwd)
if (type) {
return type
}
let tmpCwd = cwd || "."
for (let i = 1; i <= 5; i++) {
tmpCwd = dirname(tmpCwd)
type = await getTypeofLockFile(tmpCwd)
if (type) {
return type
}
}
if (await hasGlobalInstallation("yarn")) {
return "yarn"
}
if (await hasGlobalInstallation("pnpm")) {
return "yarn"
}
if (includeGlobalBun && (await hasGlobalInstallation("bun"))) {
return "bun"
}
return "npm"
}
export function getPackageManagerVersion(pm: PM) {
return exec(pm, ["--version"], { shell: true }).then(res => res.trim())
}
export function clearCache() {
return cache.clear()
}