UNPKG

pnpm

Version:

Fast, disk space efficient package manager

190 lines (176 loc) • 6.5 kB
// Where the host's native pnpm binary comes from. Shared by the preinstall // (`install.js`), which links it over the placeholder bins, and by the Corepack // entry (`bin/pnpm.mjs`), which spawns it. import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import process from 'node:process' import { fileURLToPath } from 'node:url' const { platform, arch } = process /** Directory of the published wrapper; the native binary lives next to it. */ export const wrapperDir = path.dirname(fileURLToPath(import.meta.url)) const PLATFORMS = { win32: { x64: '@pnpm/exe.win32-x64/pnpm.exe', arm64: '@pnpm/exe.win32-arm64/pnpm.exe', }, darwin: { x64: '@pnpm/exe.darwin-x64/pnpm', arm64: '@pnpm/exe.darwin-arm64/pnpm', }, linux: { x64: { glibc: '@pnpm/exe.linux-x64/pnpm', musl: '@pnpm/exe.linux-x64-musl/pnpm', }, arm64: { glibc: '@pnpm/exe.linux-arm64/pnpm', musl: '@pnpm/exe.linux-arm64-musl/pnpm', }, // Only a glibc build is released for these three. riscv64: { glibc: '@pnpm/exe.linux-riscv64/pnpm', }, ppc64: { glibc: '@pnpm/exe.linux-ppc64/pnpm', }, s390x: { glibc: '@pnpm/exe.linux-s390x/pnpm', }, }, freebsd: { x64: '@pnpm/exe.freebsd-x64/pnpm', }, // Android is bionic, which is neither of the two libcs the linux entries // above are keyed on, so it takes a bare specifier of its own. android: { arm64: '@pnpm/exe.android-arm64/pnpm', x64: '@pnpm/exe.android-x64/pnpm', }, // Android is bionic, which is neither of the two libcs the linux entries // above are keyed on, so it takes a bare specifier of its own. } /** * Native binary specifiers to try, most-preferred first; empty when no released * binary runs on the host. The linux glibc/musl pair is ordered by detected * libc, which only decides the winner when both are installed (e.g. * `npm install --force`). * * @returns {string[]} */ export function getBinCandidates () { const platformEntry = PLATFORMS?.[platform]?.[arch] if (platformEntry == null) { return [] } // Node reports both POWER endiannesses as `ppc64` and npm's `cpu` field // cannot tell them apart, so a big-endian host installs the little-endian // package it cannot run. Only the little-endian build is released. if (arch === 'ppc64' && os.endianness() !== 'LE') { return [] } if (typeof platformEntry === 'string') { return [platformEntry] } // An unprobeable libc (`detectLinuxLibc` returns null) counts as glibc. const detected = detectLinuxLibc() === 'musl' ? 'musl' : 'glibc' const preferred = platformEntry[detected] // An architecture released for one libc only has no entry for the other, and // the binary it does ship cannot run there, so it offers no candidate. if (preferred == null) { return [] } const alternate = platformEntry[detected === 'musl' ? 'glibc' : 'musl'] return alternate == null ? [preferred] : [preferred, alternate] } /** * How the host names itself in messages, spelled like the targets pnpm releases * binaries for: `linux-x64-musl`, `darwin-arm64`. * * @returns {string} */ export function hostTarget () { return `${platform}-${arch}${detectLinuxLibc() === 'musl' ? '-musl' : ''}` } /** * Split a specifier from {@link getBinCandidates} into the package that ships * the binary and the binary's path inside it. * * @param {string} specifier * @returns {{ packageName: string, binFile: string }} */ export function splitBinSpecifier (specifier) { const [scope, name, ...rest] = specifier.split('/') return { packageName: `${scope}/${name}`, binFile: rest.join('/') } } /** * Path to the native binary of whichever platform package the package manager * installed with this wrapper, or `null` when none is present (Corepack, * `--no-optional`). * * Only the wrapper's own `node_modules` and the one it was installed into are * searched, which is where every package manager puts an optional dependency * of the wrapper. A `require` walk would also reach the ancestors' `node_modules`, * which belong to whatever project the wrapper sits under and are not to be * run in its name. * * @returns {string | null} */ export function resolveInstalledBinary () { // Use whichever platform package the package manager installed: it already // filtered by `os`/`cpu`/`libc`, more reliable than re-deriving the host. for (const specifier of getBinCandidates()) { const { packageName, binFile } = splitBinSpecifier(specifier) for (const modulesDir of installedModulesDirs()) { const candidate = path.join(modulesDir, ...packageName.split('/'), binFile) if (fs.statSync(candidate, { throwIfNoEntry: false })?.isFile() === true) { return candidate } } } return null } /** * Where a platform package installed for this wrapper can be: the `node_modules` * nested in the wrapper, then the one the wrapper was installed into (two levels * up for the scoped `@pnpm/exe`). Neither need exist. Throws only when the * wrapper's manifest is unreadable, see {@link readWrapperManifest}. * * @returns {string[]} */ function installedModulesDirs () { const { name } = readWrapperManifest() const segments = typeof name === 'string' ? name.split('/').length : 1 return [ path.join(wrapperDir, 'node_modules'), path.resolve(wrapperDir, ...Array(segments).fill('..')), ] } // A successful read with no optionalDependencies is the dev checkout (there is // no native binary to link there); a read/parse failure is a corrupt published // package and must not be silently swallowed into that same path. export function readWrapperManifest () { const manifestPath = path.join(wrapperDir, 'package.json') let manifest try { manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) } catch (err) { throw new Error(`Failed to read ${manifestPath}: ${err.message}`) } if (typeof manifest !== 'object' || manifest == null || Array.isArray(manifest)) { throw new Error(`Expected ${manifestPath} to contain a JSON object`) } return manifest } function detectLinuxLibc () { if (platform !== 'linux') { return null } // glibc builds set `glibcVersionRuntime`; musl leaves it unset. Guarded — // `process.report` may be unavailable, leaving ordering to the default. try { return process.report?.getReport().header.glibcVersionRuntime ? 'glibc' : 'musl' } catch { return null } }