global/commands/README.md
Command handlers for pnpm's global package management (pnpm add -g, pnpm remove -g, pnpm update -g, pnpm list -g).
Unlike npm, where all global packages share a single node_modules/ directory, pnpm installs each global package (or group of packages installed together) into its own isolated installation directory. This prevents global packages from interfering with each other through peer dependency conflicts, hoisting changes, or version resolution shifts.
All global package data lives under {pnpmHomeDir}/global/v11/ (the "global package directory", referred to as globalPkgDir in the code). The layout is:
{pnpmHomeDir}/global/v11/
{hash-1} -> symlink to {pid}-{timestamp-1}/ (hash symlink)
{pid}-{timestamp-1}/ (install dir)
package.json
node_modules/
.pnpm/
{pkg-a}/
{pkg-b}/
pnpm-lock.yaml
{hash-2} -> symlink to {pid}-{timestamp-2}/
{pid}-{timestamp-2}/
package.json
node_modules/
.pnpm/
{pkg-c}/
pnpm-lock.yaml
Each install group has two entries:
Install directory ({pid}-{timestamp}): A regular directory containing a complete pnpm project with its own package.json, node_modules/, and lockfile. Named with the process ID and timestamp at creation time to avoid collisions.
Hash symlink ({hash}): A symbolic link named with a deterministic hash of the package aliases and registries. Points to the install directory. This serves as the lookup key for finding where a set of packages is installed.
The hash is computed from the sorted list of package aliases and sorted registry URLs, making it deterministic for a given set of packages.
pnpm add -g <pkg> [pkg2 ...]Handled by handleGlobalAdd():
{pid}-{timestamp}.installGlobalPackages() to install the requested packages into this directory using @pnpm/core's mutateModulesInSingleProject().package.json (this is more reliable than parsing aliases from CLI params, which may be tarballs, git URLs, etc.).checkGlobalBinConflicts().pnpm remove -g <pkg> [pkg2 ...]Handled by handleGlobalRemove():
findGlobalPackage()).pnpm update -g [pkg ...]Handled by handleGlobalUpdate():
--latest versions if the --latest flag is set, or within existing ranges otherwise).pnpm list -g [pattern ...]Handled by listGlobalPackages():
node_modules/.@pnpm/matcher).installGlobalPackages() — the core install functionThis is a focused ~30-line function that replaces the 450-line installDeps() from plugin-commands-installation. Global installs don't need workspace logic, recursive installs, update matching, rebuild orchestration, or any of the other complexity in installDeps(). The function does exactly:
mutateModulesInSingleProject() with mutation: 'installSome'.checkGlobalBinConflicts() prevents a common footgun: installing a global package whose binaries would shadow binaries from a different globally-installed package. Before any bin linking happens, it:
cleanOrphanedInstallDirs() (from @pnpm/global.packages) runs at the start of add and update to remove install directories that are no longer referenced by any hash symlink. This handles cases where a previous install was interrupted or crashed. A 5-minute safety window prevents cleaning up directories from concurrent installs that haven't created their hash symlink yet.
global/
commands/ @pnpm/global.commands (this package)
src/
globalAdd.ts handleGlobalAdd()
globalRemove.ts handleGlobalRemove()
globalUpdate.ts handleGlobalUpdate()
listGlobalPackages.ts listGlobalPackages()
installGlobalPackages.ts core install function
checkGlobalBinConflicts.ts bin conflict detection
readInstalledPackages.ts shared helper
index.ts
packages/ @pnpm/global.packages
src/
scanGlobalPackages.ts directory scanning, package lookup
globalPackageDir.ts install dir / hash link management
cacheKey.ts deterministic hash computation
index.ts
@pnpm/global.packages provides the low-level utilities for reading and managing the directory structure. @pnpm/global.commands provides the high-level command handlers that orchestrate installs, removals, updates, and listing.
The CLI command handlers in plugin-commands-installation and plugin-commands-listing delegate to this package with a simple early return:
// In add.ts handler:
if (opts.global) {
return handleGlobalAdd(opts, params)
}
// In remove.ts handler:
if (opts.global) {
return handleGlobalRemove(opts, params)
}
// In update/index.ts handler:
if (opts.global) {
return handleGlobalUpdate(opts, params)
}
// In list.ts handler:
if (opts.global && opts.globalPkgDir) {
return listGlobalPackages(opts.globalPkgDir, params)
}