GENERATION.md
# Generation Info - **Source:** `sources/pnpm` - **Git SHA:** `5cd19942ee75cda8ed299233c486a67d95bb38ec` - **Generated:** 2026-06-22
antfu/skills · GitHub
엄격한 종속성 해결 기능을 갖춘 Node.js 패키지 관리자입니다. pnpm 전용 명령어를 실행하거나, pnpm-workspace.yaml을 통해 작업 공간을 구성하거나, 카탈로그, 패치, 오버라이드, 구성 종속성 또는 전역 가상 저장소를 사용하여 종속성을 관리할 때 사용합니다.
프로젝트 폴더에서 아래 명령어를 실행하고, 설치할 에이전트를 선택하세요.
npx skills add antfu/skills --skill pnpm설치 명령을 직접 실행해야 적용됩니다. 지원 에이전트와 필요한 권한·라이선스는 제작자의 안내를 확인하세요.
GENERATION.md# Generation Info - **Source:** `sources/pnpm` - **Git SHA:** `5cd19942ee75cda8ed299233c486a67d95bb38ec` - **Generated:** 2026-06-22
references/best-practices-ci.md---
name: pnpm-ci-cd-setup
description: Optimizing pnpm for continuous integration and deployment workflows
---
# pnpm CI/CD Setup
Best practices for using pnpm in CI/CD environments for fast, reliable builds.
> **CI auto-behaviors:** When pnpm detects a CI environment it switches to **frozen-lockfile** mode automatically and (since v11) **fails on an incompatible lockfile** written by a newer pnpm major instead of rewriting it — keep the CI pnpm version in sync with the one that generated the lockfile. The global virtual store is auto-disabled in CI (no warm cache).
## GitHub Actions
### Basic Setup
```yaml
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile # or: pnpm ci
- run: pnpm test
- run: pnpm build
```
> `pnpm ci` (aliases `clean-install`, `install-clean`) = `pnpm clean` + `pnpm install --frozen-lockfile`, ideal for fully reproducible CI builds.
### With Store Caching
For larger projects, cache the pnpm store:
```yaml
- uses: pnpm/action-setup@v4
with:
version: 10
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- run: pnpm install --frozen-lockfile
```
> **Trust:** only cache/restore the pnpm store and cache dir between *trusted* jobs. A store an untrusted job can write to must not be reused by trusted jobs — it is part of pnpm's trust domain.
### Matrix Testing
```yaml
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test
```
## GitLab CI
```yaml
image: node:20
stages:
- install
- test
- build
variables:
PNPM_HOME: /root/.local/share/pnpm
PATH: $PNPM_HOME:$PATH
before_script:
- corepack enable
- corepack prepare pnpm@latest --activate
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .pnpm-store
install:
stage: install
script:
- pnpm config set store-dir .pnpm-store
- pnpm install --frozen-lockfile
test:
stage: test
script:
- pnpm test
build:
stage: build
script:
- pnpm build
```
## Docker
> **PATH change (v11):** global pnpm binaries now live in `$PNPM_HOME/bin`. In Docker set `ENV PATH="$PNPM_HOME/bin:$PATH"` (not `$PNPM_HOME`). There is also an official image `ghcr.io/pnpm/pnpm:<version>` (Debian slim, pnpm only — choose Node yourself via `pnpm runtime set node <ver> -g` or `devEngines.runtime`).
### Multi-Stage Build
```dockerfile
# Build stage
FROM node:24-slim AS builder
# Enable corepack for pnpm
RUN corepack enable
WORKDIR /app
# Copy package files first for layer caching
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/*/package.json ./packages/
# Install dependencies
RUN pnpm install --frozen-lockfile
# Copy source and build
COPY . .
RUN pnpm build
# Production stage
FROM node:20-slim AS runner
RUN corepack enable
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
COPY --from=builder /app/pnpm-lock.yaml ./
# Production install
RUN pnpm install --frozen-lockfile --prod
CMD ["node", "dist/index.js"]
```
### Optimized for Monorepos
```dockerfile
FROM node:20-slim AS builder
RUN corepack enable
WORKDIR /app
# Copy workspace config
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
# Copy all package.json files maintaining structure
COPY packages/core/package.json ./packages/core/
COPY packages/api/package.json ./packages/api/
# Install all dependencies
RUN pnpm install --frozen-lockfile
# Copy source
COPY . .
# Build specific package
RUN pnpm --filter @myorg/api build
```
## Key CI Flags
### --frozen-lockfile
**Always use in CI.** Fails if `pnpm-lock.yaml` needs updates:
```bash
pnpm install --frozen-lockfile
```
### --prefer-offline
Use cached packages when available:
```bash
pnpm install --frozen-lockfile --prefer-offline
```
### --ignore-scripts
Skip lifecycle scripts for faster installs (use cautiously):
```bash
pnpm install --frozen-lockfile --ignore-scripts
```
## Corepack Integration
Use Corepack to pin the pnpm version:
```json
// package.json
{
"packageManager": "pnpm@10.0.0"
}
```
```yaml
# GitHub Actions
- run: corepack enable
- run: pnpm install --frozen-lockfile
```
For range-based pinning use `devEngines.packageManager` (resolved version stored in the lockfile). To skip the pin check when version management is external (asdf/mise/Volta), set `pmOnFail: ignore` in `pnpm-workspace.yaml`, or run a one-off with `pnpm with current <cmd>`.
## Monorepo CI Strategies
### Build Changed Packages Only
```yaml
- name: Build changed packages
run: |
pnpm --filter "...[origin/main]" build
```
### Parallel Jobs per Package
```yaml
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.changes.outputs.packages }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: changes
run: |
echo "packages=$(pnpm --filter '...[origin/main]' list --json | jq -c '[.[].name]')" >> $GITHUB_OUTPUT
test:
needs: detect-changes
if: needs.detect-changes.outputs.packages != '[]'
runs-on: ubuntu-latest
strategy:
matrix:
package: ${{ fromJson(needs.detect-changes.outputs.packages) }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- run: pnpm install --frozen-lockfile
- run: pnpm --filter ${{ matrix.package }} test
```
## Best Practices Summary
1. **Use `pnpm ci` or `--frozen-lockfile`** in CI
2. **Cache the pnpm store** (only across trusted jobs)
3. **Match the CI pnpm major** to the one that wrote the lockfile (CI fails on incompatible lockfiles)
4. **Pin `packageManager`** (or `devEngines.packageManager`) in package.json
5. **Use `--filter`** in monorepos to build only what changed
6. **Multi-stage Docker builds**; set `PATH=$PNPM_HOME/bin:$PATH`
<!--
Source references:
- https://pnpm.io/continuous-integration
- https://pnpm.io/docker
- https://pnpm.io/cli/ci
- https://github.com/pnpm/action-setup
-->
references/best-practices-migration.md---
name: migration-to-pnpm
description: Migrating from npm or Yarn to pnpm with minimal friction
---
# Migration to pnpm
Guide for migrating existing projects from npm or Yarn to pnpm, plus upgrading pnpm v10 → v11.
## Upgrading pnpm v10 → v11
v11 changes how configuration is read. Most of it is mechanical — run the codemod:
```bash
cd /path/to/project
pnpx codemod run pnpm-v10-to-v11
```
The codemod automatically:
- **Moves `package.json#pnpm` settings into `pnpm-workspace.yaml`** (the `pnpm` field is no longer read).
- **Splits `.npmrc`**: only auth/registry settings stay in `.npmrc`; every other key moves to `pnpm-workspace.yaml` as **camelCase** (e.g. `node-linker` → `nodeLinker`). Per-subproject `.npmrc` files become `packageConfigs["<name>"]`.
- **Consolidates build settings** (`onlyBuiltDependencies`, `neverBuiltDependencies`, `ignoredBuiltDependencies`, `onlyBuiltDependenciesFile`) into one `allowBuilds: { name: true|false }` map.
- **Replaces** `managePackageManagerVersions`/`packageManagerStrict`/`packageManagerStrictVersion` with `pmOnFail: download|ignore|warn|error`.
- **Renames** `allowNonAppliedPatches` → `allowUnusedPatches`, `auditConfig.ignoreCves` → `auditConfig.ignoreGhsas`.
- **Converts** `useNodeVersion` → `devEngines.runtime`, and bumps `packageManager`.
Manual follow-ups (not automatable):
- Convert `CVE-…` IDs to `GHSA-…` in `auditConfig.ignoreGhsas`.
- `ignorePatchFailures` removed — failed patches now always throw.
- `npm_config_*` env vars → `pnpm_config_*` (CI, shell profiles, Docker).
- `pnpm link <name>` → use a path (`pnpm link ./foo`); `pnpm link --global` → `pnpm add -g .`.
- `pnpm install -g` (no args) and `pnpm server` removed.
- A `package.json` script named `clean`/`setup`/`deploy`/`rebuild` now shadows the built-in — use `pnpm pm <name>` for the built-in.
## Migrating from npm / Yarn
## Quick Migration
### From npm
```bash
# Remove npm lockfile and node_modules
rm -rf node_modules package-lock.json
# Install with pnpm
pnpm install
```
### From Yarn
```bash
# Remove yarn lockfile and node_modules
rm -rf node_modules yarn.lock
# Install with pnpm
pnpm install
```
### Import Existing Lockfile
pnpm can import existing lockfiles:
```bash
# Import from npm or yarn lockfile
pnpm import
# This creates pnpm-lock.yaml from:
# - package-lock.json (npm)
# - yarn.lock (yarn)
# - npm-shrinkwrap.json (npm)
```
## Handling Common Issues
### Phantom Dependencies
pnpm is strict about dependencies. If code imports a package not in `package.json`, it will fail.
**Problem:**
```js
// Works with npm (hoisted), fails with pnpm
import lodash from 'lodash' // Not in dependencies, installed by another package
```
**Solution:** Add missing dependencies explicitly:
```bash
pnpm add lodash
```
### Missing Peer Dependencies
pnpm reports peer dependency issues by default.
**Option 1:** Let pnpm auto-install (default in v8+):
```yaml title="pnpm-workspace.yaml"
autoInstallPeers: true
```
**Option 2:** Install manually:
```bash
pnpm add react react-dom
```
**Option 3:** Suppress warnings if acceptable:
```yaml title="pnpm-workspace.yaml"
peerDependencyRules:
ignoreMissing:
- react
```
### Symlink Issues
Some tools don't work with symlinks. Use hoisted mode:
```yaml title="pnpm-workspace.yaml"
nodeLinker: hoisted
```
Or hoist specific packages:
```yaml title="pnpm-workspace.yaml"
publicHoistPattern:
- '*eslint*'
- '*babel*'
```
### Native Module Rebuilds
If native modules fail, try:
```bash
# Rebuild all native modules
pnpm rebuild
# Or reinstall
rm -rf node_modules
pnpm install
```
## Monorepo Migration
### From npm Workspaces
1. Create `pnpm-workspace.yaml`:
```yaml
packages:
- 'packages/*'
```
2. Update internal dependencies to use workspace protocol:
```json
{
"dependencies": {
"@myorg/utils": "workspace:^"
}
}
```
3. Install:
```bash
rm -rf node_modules packages/*/node_modules package-lock.json
pnpm install
```
### From Yarn Workspaces
1. Remove Yarn-specific files:
```bash
rm yarn.lock .yarnrc.yml
rm -rf .yarn
```
2. Create `pnpm-workspace.yaml` matching `workspaces` in package.json:
```yaml
packages:
- 'packages/*'
```
3. Update `package.json` - remove Yarn workspace config if not needed:
```json
{
// Remove "workspaces" field (optional, pnpm uses pnpm-workspace.yaml)
}
```
4. Convert workspace references:
```json
// From Yarn
"@myorg/utils": "*"
// To pnpm
"@myorg/utils": "workspace:*"
```
### From Lerna
pnpm can replace Lerna for most use cases:
```bash
# Lerna: run script in all packages
lerna run build
# pnpm equivalent
pnpm -r run build
# Lerna: run in specific package
lerna run build --scope=@myorg/app
# pnpm equivalent
pnpm --filter @myorg/app run build
# Lerna: publish
lerna publish
# pnpm: use changesets instead
pnpm add -Dw @changesets/cli
pnpm changeset
pnpm changeset version
pnpm publish -r
```
## Configuration Migration
Keep only **auth/registry** in `.npmrc`; put everything else in `pnpm-workspace.yaml` (camelCase).
```ini title=".npmrc (auth only, gitignored)"
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
//npm.myorg.com/:_authToken=${MYORG_TOKEN}
```
```yaml title="pnpm-workspace.yaml"
registries:
default: https://registry.npmjs.org/
'@myorg': https://npm.myorg.com/
autoInstallPeers: true
strictPeerDependencies: false
```
### Scripts Migration
Most scripts work unchanged. Update pnpm-specific patterns:
```json
{
"scripts": {
// npm: recursive scripts
"build:all": "npm run build --workspaces",
// pnpm: use -r flag
"build:all": "pnpm -r run build",
// npm: run in specific workspace
"dev:app": "npm run dev -w packages/app",
// pnpm: use --filter
"dev:app": "pnpm --filter @myorg/app run dev"
}
}
```
## CI/CD Migration
Update CI configuration:
```yaml
# Before (npm)
- run: npm ci
# After (pnpm)
- uses: pnpm/action-setup@v4
- run: pnpm install --frozen-lockfile # or: pnpm ci
```
Add to `package.json` for Corepack:
```json
{
"packageManager": "pnpm@10.0.0"
}
```
## Gradual Migration
For large projects, migrate gradually:
1. **Start with CI**: Use pnpm in CI, keep npm/yarn locally
2. **Add pnpm-lock.yaml**: Run `pnpm import` to create lockfile
3. **Test thoroughly**: Ensure builds work with pnpm
4. **Update documentation**: Update README, CONTRIBUTING
5. **Remove old files**: Delete old lockfiles after team adoption
## Rollback Plan
If migration causes issues:
```bash
# Remove pnpm files
rm -rf node_modules pnpm-lock.yaml pnpm-workspace.yaml
# Restore npm
npm install
# Or restore Yarn
yarn install
```
Keep old lockfile in git history for easy rollback.
<!--
Source references:
- https://pnpm.io/migration
- https://pnpm.io/cli/import
- https://pnpm.io/configuring
-->
references/best-practices-performance.md--- name: pnpm-performance-optimization description: Tips and tricks for faster installs and better performance --- # pnpm Performance Optimization pnpm is fast by default, but these optimizations can make it even faster. ## Install Optimizations ### Use Frozen Lockfile Skip resolution when lockfile exists: ```bash pnpm install --frozen-lockfile ``` This is faster because pnpm skips the resolution phase entirely. ### Prefer Offline Mode Use cached packages when available: ```bash pnpm install --prefer-offline ``` ### Skip Optional Dependencies If you don't need optional deps: ```bash pnpm install --no-optional ``` ### Skip Scripts For CI or when scripts aren't needed: ```bash pnpm install --ignore-scripts ``` **Caution:** Some packages require postinstall scripts to work correctly. ### Only Build Specific Dependencies Build-script approval is a single `allowBuilds` map (replaces `onlyBuiltDependencies`/`neverBuiltDependencies`). Only allowed packages run install scripts: ```yaml title="pnpm-workspace.yaml" allowBuilds: esbuild: true '@swc/core': true core-js: false # explicitly skip ``` Packages not listed are treated as unreviewed (blocked by default). See `features-supply-chain-security` for the full build-approval workflow. ## Store Optimizations ### Side Effects Cache Cache native module build results (enabled by default): ```yaml title="pnpm-workspace.yaml" sideEffectsCache: true ``` This caches the results of postinstall scripts, speeding up subsequent installs. ### Global Virtual Store For many checkouts of the same repo (e.g. git worktrees / multiple agents), enable the global virtual store so each project's `node_modules` is just symlinks into one shared store — near-zero per-checkout cost. Auto-disabled in CI. ```yaml title="pnpm-workspace.yaml" enableGlobalVirtualStore: true ``` ### Shared Store A single content-addressable store is used for all projects by default: ```yaml title="pnpm-workspace.yaml" storeDir: ~/.local/share/pnpm/store ``` Benefits: packages downloaded once, hard links save disk space, faster cached installs. ### Store Maintenance Periodically clean unused packages: ```bash # Remove unreferenced packages pnpm store prune # Check store integrity pnpm store status ``` ## Workspace Optimizations ### Parallel Execution Run workspace scripts in parallel: ```bash pnpm -r --parallel run build ``` Control concurrency: ```yaml title="pnpm-workspace.yaml" workspaceConcurrency: 8 ``` ### Stream Output See output in real-time: ```bash pnpm -r --stream run build ``` ### Filter to Changed Packages Only build what changed: ```bash # Build packages changed since main branch pnpm --filter "...[origin/main]" run build ``` ### Topological Order Build dependencies before dependents: ```bash pnpm -r run build # Automatically runs in topological order ``` For explicit sequential builds: ```bash pnpm -r --workspace-concurrency=1 run build ``` ## Network Optimizations Network/registry settings are camelCase in `pnpm-workspace.yaml` (registry URLs may also go in `registries`): ```yaml title="pnpm-workspace.yaml" registries: default: https://registry.npmmirror.com/ fetchRetries: 3 fetchRetryMintimeout: 10000 fetchRetryMaxtimeout: 60000 networkConcurrency: 16 # auto: clamp(workers x 3, 16, 64) httpProxy: http://proxy.company.com:8080 httpsProxy: http://proxy.company.com:8080 ``` ## Lockfile Optimization ### Single Lockfile (Monorepos) Use shared lockfile for all packages (default): ```yaml title="pnpm-workspace.yaml" sharedWorkspaceLockfile: true ``` Benefits: - Single source of truth - Faster resolution - Consistent versions across workspace ### Lockfile-only Mode Only update lockfile without installing: ```bash pnpm install --lockfile-only ``` ## Benchmarking ### Compare Install Times ```bash # Clean install rm -rf node_modules pnpm-lock.yaml time pnpm install # Cached install (with lockfile) rm -rf node_modules time pnpm install --frozen-lockfile # With store cache time pnpm install --frozen-lockfile --prefer-offline ``` ### Profile Resolution Debug slow installs: ```bash # Verbose logging pnpm install --reporter=append-only # Debug mode DEBUG=pnpm:* pnpm install ``` ## Configuration Summary Optimized `pnpm-workspace.yaml` for performance: ```yaml title="pnpm-workspace.yaml" # Install behavior autoInstallPeers: true sideEffectsCache: true optimisticRepeatInstall: true # Build approval (only what's necessary) allowBuilds: esbuild: true '@swc/core': true # Network fetchRetries: 3 networkConcurrency: 16 # Workspace workspaceConcurrency: 4 # Many checkouts of the same repo enableGlobalVirtualStore: true ``` ## Quick Reference | Scenario | Command/Setting | |----------|-----------------| | CI installs | `pnpm ci` / `pnpm install --frozen-lockfile` | | Offline development | `--prefer-offline` | | Control native builds | `allowBuilds` map | | Parallel workspace | `pnpm -r --parallel run build` | | Build changed only | `pnpm --filter "...[origin/main]" build` | | Clean store | `pnpm store prune` | | Many worktrees/agents | `enableGlobalVirtualStore: true` | <!-- Source references: - https://pnpm.io/settings - https://pnpm.io/cli/install - https://pnpm.io/filtering - https://pnpm.io/global-virtual-store -->
references/core-cli.md--- name: pnpm-cli-commands description: Essential pnpm commands for package management, running scripts, workspaces, publishing, and runtimes --- # pnpm CLI Commands pnpm provides a comprehensive CLI. Commands resemble npm/yarn but with unique features. ## Installation Commands ```bash pnpm install # install all deps (alias: pnpm i) pnpm add <pkg> # production dependency pnpm add -D <pkg> # devDependency (also -d) pnpm add -O <pkg> # optionalDependency (also -o) pnpm add -E <pkg> # exact version (also -e) pnpm add <pkg>@<version> pnpm remove <pkg> # aliases: rm, uninstall, un pnpm update # alias: up pnpm update --latest # ignore semver ranges (-L) pnpm update -i # interactive ``` ### Clean / reproducible installs ```bash pnpm install --frozen-lockfile # fail if lockfile would change (auto in CI) pnpm ci # clean install = pnpm clean + install --frozen-lockfile pnpm clean # remove node_modules in all workspace projects (alias: purge) pnpm clean --lockfile # also delete pnpm-lock.yaml ``` > Since v11, an integrity mismatch against the lockfile is a hard error (`ERR_PNPM_TARBALL_INTEGRITY`). Use `pnpm install --update-checksums` only after verifying the new bytes. In CI, pnpm also fails on lockfiles written by a newer pnpm major. ## Script Commands ```bash pnpm run <script> # or just: pnpm <script> pnpm run build -- --watch pnpm run --if-present build pnpm set-script test "vitest run" # add/update a scripts entry (alias: ss) pnpm exec <cmd> # run a local binary, e.g. pnpm exec eslint . ``` - **Hidden scripts:** names starting with `.` (e.g. `.helper`) can't be run directly, only called from other scripts. - **Built-in vs script conflict:** `clean`, `setup`, `deploy`, `rebuild` prefer a same-named `package.json` script. Force the built-in with `pnpm pm <name>` (e.g. `pnpm pm clean`). ### dlx / pnx — run without installing ```bash pnx create-vite my-app # pnx == pnpm dlx == pnpx pnpm dlx degit user/repo dest pnx shx@catalog: # catalog: protocol supported pnx --package=@scope/tool tool --help ``` > `dlx`/`pnx` honor supply-chain settings (`minimumReleaseAge`, `trustPolicy`) and use the global virtual store by default in v11. ## Workspace Commands ```bash pnpm -r run <script> # run in all packages (alias: --recursive) pnpm --filter <pattern> run <script> pnpm --filter "./packages/**" run build pnpm --filter "@myorg/*" run lint pnpm -r --parallel run dev ``` ### Filter patterns ```bash pnpm --filter <pkg-name> <cmd> # by name (-F shorthand) pnpm --filter "./packages/core" test pnpm --filter "...@scope/app" build # package + its dependencies pnpm --filter "@scope/core..." test # package + its dependents pnpm --filter "...[origin/main]" build # changed since git ref ``` ## Patches ```bash pnpm patch <pkg>@<version> # opens an editable copy, prints a path pnpm patch-commit <path> # writes patches/*.patch and records it pnpm patch-remove <pkg>@<version> ``` ## Linking local packages ```bash pnpm link <dir> # link a path into this project's node_modules (path only!) pnpm add -g . # register the current package's bins globally ``` > Breaking in v11: `pnpm link` accepts **only relative/absolute paths** (no global store resolution, no `--global`, no bare `pnpm link`). Use `pnpm add -g .` to expose bins system-wide. ## Global packages (v11 isolated installs) ```bash pnpm add -g typescript prettier # each gets its own isolated install dir pnpm add -g eslint,prettier # comma = ONE shared install group pnpm add -g --allow-build=esbuild esbuild pnpm remove -g <pkg> pnpm list -g pnpm bin -g # show global bin dir ($PNPM_HOME/bin) ``` > `pnpm install -g` (no args) is not supported. After upgrading to v11 run `pnpm setup` so `$PNPM_HOME/bin` is on PATH. ## Runtimes (Node/Deno/Bun) ```bash pnpm runtime set node 22 -g # install & expose node (alias: rt) pnpm runtime set node lts -g pnpm runtime set deno 2 -g pnpm install --no-runtime # skip installing devEngines.runtime entries ``` ## Store management ```bash pnpm store path # store location (prints removed size after prune) pnpm store prune # GC unreferenced packages (+ global virtual store links) pnpm store status ``` ## Inspection / registry ```bash pnpm list # alias: ls pnpm why <pkg> # reverse-dependency tree (dedupes subtrees) pnpm why --find-by=<finder> # custom finder from .pnpmfile.mjs pnpm outdated pnpm audit pnpm peers check # report unmet/missing peers from the lockfile pnpm view <pkg> [field] # registry metadata (aliases: info, show) pnpm whoami pnpm rebuild pnpm import # create pnpm-lock.yaml from npm/yarn lockfile pnpm dedupe ``` ## Publishing ```bash pnpm pack pnpm publish -r --no-git-checks pnpm version patch|minor|major|2.0.0 # bump version, commit + tag (v11) pnpm version prerelease --preid beta pnpm deprecate <pkg>@<range> "message" pnpm dist-tag add <pkg>@<version> <tag> pnpm unpublish <pkg>@<version> # discouraged; prefer deprecate pnpm sbom --sbom-format cyclonedx # SBOM: cyclonedx (1.7) | spdx (2.3) pnpm stage publish ... # staged publishing (defer 2FA) ``` ## Maintenance & version management ```bash pnpm self-update [<version>] # updates the packageManager pin, or installs globally pnpm with current install # run a specific pnpm version for one command pnpm with 11.0.0 install pnpm approve-builds [--all] # review dependency build scripts (writes allowBuilds) ``` ## Useful Flags ```bash pnpm install --ignore-scripts pnpm install --prefer-offline pnpm install --prod # -P, omit devDependencies pnpm install --no-optional pnpm install --strict-peer-dependencies ``` ## Key Points - `pnpm ci` = clean + frozen install; CI auto-enables frozen-lockfile. - `dlx`/`pnpx` are aliases of `pnx`; global installs are now isolated per package (comma-list to share). - `pnpm link` only takes paths; use `pnpm add -g .` for global bins. - Manage Node/Deno/Bun with `pnpm runtime set`; skip them at install with `--no-runtime`. - New publishing/registry commands: `version`, `view`, `whoami`, `deprecate`, `dist-tag`, `unpublish`, `sbom`, `stage`. <!-- Source references: - https://pnpm.io/cli/install - https://pnpm.io/cli/add - https://pnpm.io/cli/run - https://pnpm.io/filtering - https://pnpm.io/cli/link - https://pnpm.io/global-packages - https://pnpm.io/cli/runtime - https://pnpm.io/cli/version - https://pnpm.io/cli/with - https://pnpm.io/cli/sbom -->
references/core-config.md---
name: pnpm-configuration
description: Configuring pnpm via pnpm-workspace.yaml (settings), the global config.yaml, and .npmrc (auth only)
---
# pnpm Configuration
pnpm settings are split into **two** categories. Knowing where each goes is the single most important config concept in current pnpm:
| Category | Stored in | Format |
|----------|-----------|--------|
| **All pnpm/install settings** (`nodeLinker`, `hoistPattern`, `autoInstallPeers`, `overrides`, `catalog`, …) | `pnpm-workspace.yaml` (project) and `config.yaml` (global) | YAML, **camelCase** keys |
| **Auth & registry credentials** (`_authToken`, `cert`, `key`, …) | `.npmrc` (project, gitignored) and global `rc` | INI |
> **Important changes:** pnpm no longer reads settings from the `pnpm` field of `package.json`, and `.npmrc` is now used **only** for authentication/registry credentials. Everything else belongs in `pnpm-workspace.yaml`. Keys in YAML are **camelCase** (e.g. `nodeLinker`), not the kebab-case used by old `.npmrc` files.
## pnpm-workspace.yaml (primary config)
Place at the workspace/project root. Even a single-package project uses this file for pnpm settings.
```yaml title="pnpm-workspace.yaml"
# Workspace packages (omit for a single-package repo)
packages:
- 'packages/*'
- 'apps/*'
- '!**/test/**'
# Common install settings (camelCase)
nodeLinker: isolated # isolated (default) | hoisted | pnp
autoInstallPeers: true
strictPeerDependencies: false
savePrefix: '^'
saveExact: false
hoistPattern:
- '*eslint*'
- '*babel*'
publicHoistPattern: []
shamefullyHoist: false
dedupeDirectDeps: false
resolutionMode: highest # highest | time-based | lowest-direct
# Centralized version management
catalog:
react: ^18.2.0
# Force dependency versions (root only)
overrides:
lodash: ^4.17.21
'foo@^1.0.0>bar': ^2.0.0
# Extend/patch broken package manifests
packageExtensions:
react-redux:
peerDependencies:
react-dom: '*'
# Peer dependency rules
peerDependencyRules:
ignoreMissing:
- '@babel/*'
allowedVersions:
react: '17 || 18'
```
## Global configuration (config.yaml)
User-level non-auth settings live in a global YAML `config.yaml`:
- `$XDG_CONFIG_HOME/pnpm/config.yaml` (if set)
- Linux: `~/.config/pnpm/config.yaml`
- macOS: `~/Library/Preferences/pnpm/config.yaml`
- Windows: `~/AppData/Local/pnpm/config/config.yaml`
The companion global `rc` file (same directory, named `rc`) holds only registry/auth settings.
## Per-project settings in a workspace (packageConfigs)
There are no per-subproject `.npmrc` files anymore. Set per-package config via `packageConfigs` in the root `pnpm-workspace.yaml`:
```yaml title="pnpm-workspace.yaml"
packageConfigs:
# Map form: keyed by package name
project-1:
saveExact: true
project-2:
savePrefix: '~'
# Array form: pattern-matched rules
# - match: ['project-1', 'project-2']
# modulesDir: node_modules
# saveExact: true
```
## .npmrc — authentication only
Keep auth tokens out of the repo (gitignore the project `.npmrc`). Auth files, highest priority first:
1. `<workspace root>/.npmrc` (project, gitignored)
2. `<pnpm config>/auth.ini` (written by `pnpm login`)
3. `~/.npmrc` (fallback for npm compatibility)
```ini title=".npmrc"
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
@myorg:registry=https://npm.myorg.com/
//npm.myorg.com/:_authToken=${MYORG_TOKEN}
```
Configure registries themselves (non-secret) in `pnpm-workspace.yaml`:
```yaml title="pnpm-workspace.yaml"
registries:
default: https://registry.npmjs.org/
'@my-org': https://private.example.com/
# Named registry aliases usable as a prefix, e.g. `pnpm add work:@corp/lib`
namedRegistries:
work: https://npm.work.example.com/
```
> Security: since v11, env-variable expansion is disabled for registry/proxy URLs and credential keys in the **project** `.npmrc` (to stop a malicious repo from leaking secrets). Put dynamic-token lines in the user-level auth file instead.
## The `pnpm config` command
```bash
# Writes to global config.yaml / rc by default
pnpm config set nodeVersion 22.0.0
pnpm config set --location=project nodeVersion 22.0.0 # writes pnpm-workspace.yaml
# JSON values create arrays/objects
pnpm config set --location=project --json allowBuilds '{"react": true}'
# get/list print JSON (no longer INI) since v11
pnpm config get nodeLinker
pnpm config get 'allowBuilds.react'
pnpm config list
```
## Environment variables
Use `pnpm_config_*` (or `PNPM_CONFIG_*`). pnpm **no longer reads `npm_config_*`**.
```bash
pnpm_config_save_exact=true pnpm add foo
```
## Notable settings that changed names
| Old (removed) | Replacement | Notes |
|---------------|-------------|-------|
| `onlyBuiltDependencies`, `neverBuiltDependencies`, `ignoredBuiltDependencies`, `onlyBuiltDependenciesFile` | `allowBuilds: { name: true\|false }` | Single map controlling build-script approval. See supply-chain-security. |
| `managePackageManagerVersions`, `packageManagerStrict`, `packageManagerStrictVersion`, `COREPACK_ENABLE_STRICT` | `pmOnFail: download\|ignore\|warn\|error` | Behavior when running pnpm version ≠ declared one. |
| `useNodeVersion` | `devEngines.runtime` (in `package.json`) | Runtime pinning. |
| `auditConfig.ignoreCves` | `auditConfig.ignoreGhsas` | Use GHSA IDs. |
| `allowNonAppliedPatches` | `allowUnusedPatches` | `ignorePatchFailures` removed (patches now always throw). |
| `package.json#pnpm` field | `pnpm-workspace.yaml` | No longer read at all. |
## Package Manager / Runtime pinning (package.json)
```json
{
"packageManager": "pnpm@10.0.0",
"devEngines": {
"packageManager": { "name": "pnpm", "version": ">=11.0.0 <12.0.0", "onFail": "download" },
"runtime": { "name": "node", "version": "22.x", "onFail": "download" }
}
}
```
`devEngines.packageManager` supports ranges (resolved version stored in lockfile); `packageManager` requires an exact version. Override `onFail` without editing the manifest via `pmOnFail` / `runtimeOnFail` settings.
## Key Points
- All pnpm settings go in `pnpm-workspace.yaml` (camelCase) or global `config.yaml`; `.npmrc` is auth/registry only.
- `package.json#pnpm` and `npm_config_*` env vars are no longer read.
- Use `packageConfigs` for per-package settings inside a workspace.
- Build-script approval is now one `allowBuilds` map; package-manager strictness is one `pmOnFail` setting.
- `pnpm config get`/`list` output JSON, and `--location=project` writes to `pnpm-workspace.yaml`.
<!--
Source references:
- https://pnpm.io/settings
- https://pnpm.io/configuring
- https://pnpm.io/npmrc
- https://pnpm.io/pnpm-workspace_yaml
- https://pnpm.io/package_json
- https://pnpm.io/cli/config
-->
references/core-store.md---
name: pnpm-store
description: Content-addressable storage system that makes pnpm fast and disk-efficient
---
# pnpm Store
pnpm uses a content-addressable store to save disk space and speed up installations. All packages are stored once globally and hard-linked to project `node_modules`.
## How It Works
1. **Global Store**: Packages are downloaded once to a central store
2. **Hard Links**: Projects link to store instead of copying files
3. **Content-Addressable**: Files are stored by content hash, deduplicating identical files
### Storage Layout
```
<store-dir>/ # Global content-addressable store (pnpm store path)
└── files/
└── <hash>/ # Files stored by content hash
project/
└── node_modules/
├── .pnpm/ # Virtual store (hard links to global store)
│ ├── lodash@4.17.21/
│ │ └── node_modules/
│ │ └── lodash/
│ └── express@4.18.2/
│ └── node_modules/
│ ├── express/
│ └── <deps>/ # Flat structure for dependencies
├── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
└── express -> .pnpm/express@4.18.2/node_modules/express
```
## Store Commands
```bash
# Show store location
pnpm store path
# Remove unreferenced packages
pnpm store prune
# Check store integrity
pnpm store status
# Add package to store without installing
pnpm store add <pkg>
```
## Configuration
Store/linker settings live in `pnpm-workspace.yaml` (camelCase), not `.npmrc`.
### Store Location
```yaml title="pnpm-workspace.yaml"
storeDir: ~/.local/share/pnpm/store
```
The default store path is OS-specific (e.g. `~/.local/share/pnpm/store` on Linux, `~/Library/pnpm/store` on macOS). Find it with `pnpm store path`.
### Virtual Store
The virtual store (`.pnpm` in `node_modules`) contains hard links to the global store:
```yaml title="pnpm-workspace.yaml"
virtualStoreDir: node_modules/.pnpm
virtualStoreDirMaxLength: 60 # lower this for long-path issues on Windows
nodeLinker: hoisted # alternative flat layout
```
## Disk Space Benefits
pnpm saves significant disk space:
- **Deduplication**: Same package version stored once across all projects
- **Content deduplication**: Identical files across different packages stored once
- **Hard links**: No copying, just linking
### Check disk usage
```bash
# Compare actual vs apparent size
du -sh node_modules # Apparent size
du -sh --apparent-size node_modules # With hard links counted
```
## Global Virtual Store
With `enableGlobalVirtualStore: true`, projects skip the per-project `node_modules/.pnpm` directory entirely; their `node_modules` contains only symlinks into one shared virtual store at `<store-path>/links/`, keyed by dependency-graph hash. In pnpm v11 it is the default for `pnpm dlx`/`pnx` and global installs; for project installs it is still opt-in. See `features-global-virtual-store` for details and the git-worktrees multi-agent workflow.
```yaml title="pnpm-workspace.yaml"
enableGlobalVirtualStore: true
```
## Node Linker Modes
Configure how `node_modules` is structured (`nodeLinker` in `pnpm-workspace.yaml`):
```yaml title="pnpm-workspace.yaml"
nodeLinker: isolated # default: symlinked virtual store (strict, no phantom deps)
# nodeLinker: hoisted # flat node_modules (npm-like) for tools that dislike symlinks
# nodeLinker: pnp # Plug'n'Play, no node_modules (set `symlink: false` too)
```
### Isolated Mode (Default)
- Strict dependency resolution
- No phantom dependencies
- Packages can only access declared dependencies
### Hoisted Mode
- Flat `node_modules` like npm
- For compatibility with tools that don't support symlinks
- Loses strictness benefits
## Side Effects Cache
Cache build outputs for native modules (enabled by default):
```yaml title="pnpm-workspace.yaml"
sideEffectsCache: true
sideEffectsCacheReadonly: false # only read the cache, don't create it
```
## Read-only / Frozen Store
`frozenStore: true` (v11.7+) lets `pnpm install` run against a read-only store (Nix store, read-only bind mount, OCI layer). Pair with `--offline --frozen-lockfile`; the store must already contain everything, including approved build outputs.
```bash
pnpm install --frozen-store --offline --frozen-lockfile
```
## Shared Store Across Machines
For CI/CD, you can share the store:
```yaml
# GitHub Actions example
- uses: pnpm/action-setup@v4
with:
run_install: false
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
```
## Troubleshooting
### Store corruption
```bash
# Verify and fix store
pnpm store status
pnpm store prune
```
### Hard link issues (network drives, Docker)
```yaml title="pnpm-workspace.yaml"
# auto (default) tries clone -> hardlink -> copy
packageImportMethod: copy
```
### Permission issues
```bash
# Fix store permissions (find the path with `pnpm store path`)
chmod -R u+w "$(pnpm store path)"
```
<!--
Source references:
- https://pnpm.io/symlinked-node-modules-structure
- https://pnpm.io/cli/store
- https://pnpm.io/settings#storedir
- https://pnpm.io/global-virtual-store
-->
references/core-workspaces.md---
name: pnpm-workspaces
description: Monorepo support with workspaces for managing multiple packages
---
# pnpm Workspaces
pnpm has built-in support for monorepos (multi-package repositories) through workspaces.
## Setting Up Workspaces
Create `pnpm-workspace.yaml` at the repository root:
```yaml
packages:
# Include all packages in packages/ directory
- 'packages/*'
# Include all apps
- 'apps/*'
# Include nested packages
- 'tools/*/packages/*'
# Exclude test directories
- '!**/test/**'
```
## Workspace Protocol
Use `workspace:` protocol to reference local packages:
```json
{
"dependencies": {
"@myorg/utils": "workspace:*",
"@myorg/core": "workspace:^",
"@myorg/types": "workspace:~"
}
}
```
### Protocol Variants
| Protocol | Behavior | Published As |
|----------|----------|--------------|
| `workspace:*` | Any version | Actual version (e.g., `1.2.3`) |
| `workspace:^` | Compatible version | `^1.2.3` |
| `workspace:~` | Patch version | `~1.2.3` |
| `workspace:^1.0.0` | Semver range | `^1.0.0` |
## Filtering Packages
Run commands on specific packages using `--filter`:
```bash
# By package name
pnpm --filter @myorg/app build
pnpm -F @myorg/app build
# By directory path
pnpm --filter "./packages/core" test
# Glob patterns
pnpm --filter "@myorg/*" lint
pnpm --filter "!@myorg/internal-*" publish
# All packages
pnpm -r build
pnpm --recursive build
```
### Dependency-based Filtering
```bash
# Package and all its dependencies
pnpm --filter "...@myorg/app" build
# Package and all its dependents
pnpm --filter "@myorg/core..." test
# Both directions
pnpm --filter "...@myorg/shared..." build
# Changed since git ref
pnpm --filter "...[origin/main]" test
pnpm --filter "[HEAD~5]" lint
```
## Workspace Commands
### Install dependencies
```bash
# Install all workspace packages
pnpm install
# Add dependency to specific package
pnpm --filter @myorg/app add lodash
# Add workspace dependency
pnpm --filter @myorg/app add @myorg/utils
```
### Run scripts
```bash
# Run in all packages with that script
pnpm -r run build
# Run in topological order (dependencies first)
pnpm -r --workspace-concurrency=1 run build
# Run in parallel
pnpm -r --parallel run test
# Stream output
pnpm -r --stream run dev
```
### Execute commands
```bash
# Run command in all packages
pnpm -r exec pwd
# Run in specific packages
pnpm --filter "./packages/**" exec rm -rf dist
```
## Workspace Settings
Configure in `pnpm-workspace.yaml` using **camelCase** keys (these settings no longer belong in `.npmrc`):
```yaml title="pnpm-workspace.yaml"
packages:
- 'packages/*'
# Link workspace packages automatically
linkWorkspacePackages: true
# Prefer workspace packages over registry
preferWorkspacePackages: true
# Single lockfile for the whole workspace (recommended)
sharedWorkspaceLockfile: true
# Workspace protocol handling on publish
saveWorkspaceProtocol: rolling
# Concurrent workspace scripts
workspaceConcurrency: 4
# Use root deps to resolve peers of all projects
resolvePeersFromWorkspaceRoot: true
# Scripts required in every project (else `pnpm -r run <name>` fails)
requiredScripts:
- build
```
### Per-package configuration (packageConfigs)
There are no per-subproject `.npmrc` files. Set package-specific settings from the root file:
```yaml title="pnpm-workspace.yaml"
packageConfigs:
project-1:
saveExact: true
project-2:
savePrefix: '~'
```
## Publishing Workspaces
When publishing, `workspace:` protocols are converted:
```json
// Before publish
{
"dependencies": {
"@myorg/utils": "workspace:^"
}
}
// After publish
{
"dependencies": {
"@myorg/utils": "^1.2.3"
}
}
```
Use `--no-git-checks` for publishing from CI:
```bash
pnpm publish -r --no-git-checks
```
## Best Practices
1. **Use workspace protocol** for internal dependencies
2. **Enable `linkWorkspacePackages`** for automatic linking
3. **Use shared lockfile** for consistency
4. **Filter by dependencies** when building to ensure correct order
5. **Use catalogs** for shared external dependency versions (defined in this same file)
6. **Keep all pnpm settings in `pnpm-workspace.yaml`** (camelCase), not `.npmrc`
## Example Project Structure
```
my-monorepo/
├── pnpm-workspace.yaml
├── package.json
├── pnpm-lock.yaml
├── packages/
│ ├── core/
│ │ └── package.json
│ ├── utils/
│ │ └── package.json
│ └── types/
│ └── package.json
└── apps/
├── web/
│ └── package.json
└── api/
└── package.json
```
<!--
Source references:
- https://pnpm.io/workspaces
- https://pnpm.io/filtering
- https://pnpm.io/npmrc#workspace-settings
-->
references/features-aliases.md---
name: pnpm-aliases
description: Install packages under custom names for versioning, forks, or alternatives
---
# pnpm Aliases
pnpm supports package aliases using the `npm:` protocol. This lets you install packages under different names, use multiple versions of the same package, or substitute packages.
## Basic Syntax
```bash
pnpm add <alias>@npm:<package>@<version>
```
In `package.json`:
```json
{
"dependencies": {
"<alias>": "npm:<package>@<version>"
}
}
```
## Use Cases
### Multiple Versions of Same Package
Install different versions side by side:
```json
{
"dependencies": {
"lodash3": "npm:lodash@3",
"lodash4": "npm:lodash@4"
}
}
```
Usage:
```js
import lodash3 from 'lodash3'
import lodash4 from 'lodash4'
```
### Replace Package with Fork
Substitute a package with a fork or alternative:
```json
{
"dependencies": {
"original-pkg": "npm:my-fork@^1.0.0"
}
}
```
All imports of `original-pkg` will resolve to `my-fork`.
### Replace Deprecated Package
```json
{
"dependencies": {
"request": "npm:@cypress/request@^3.0.0"
}
}
```
### Scoped to Unscoped (or vice versa)
```json
{
"dependencies": {
"vue": "npm:@anthropic/vue@^3.0.0",
"@myorg/utils": "npm:lodash@^4.17.21"
}
}
```
## CLI Usage
### Add with alias
```bash
# Add lodash under alias
pnpm add lodash4@npm:lodash@4
# Add fork as original name
pnpm add request@npm:@cypress/request
```
### Add multiple versions
```bash
pnpm add react17@npm:react@17 react18@npm:react@18
```
## With TypeScript
For type resolution with aliases, you may need to configure TypeScript:
```json
// tsconfig.json
{
"compilerOptions": {
"paths": {
"lodash3": ["node_modules/lodash3"],
"lodash4": ["node_modules/lodash4"]
}
}
}
```
Or use `@types` packages with aliases:
```json
{
"devDependencies": {
"@types/lodash3": "npm:@types/lodash@3",
"@types/lodash4": "npm:@types/lodash@4"
}
}
```
## Combined with Overrides
Force all transitive dependencies to use an alias:
```yaml
# pnpm-workspace.yaml
overrides:
"underscore": "npm:lodash@^4.17.21"
```
This replaces all `underscore` imports (including in dependencies) with lodash.
## Git and Local Aliases
Aliases work with any valid pnpm specifier:
```json
{
"dependencies": {
"my-fork": "npm:user/repo#commit",
"local-pkg": "file:../local-package"
}
}
```
## Registry Aliases (namedRegistries)
Distinct from package aliases: a `namedRegistries` prefix selects *which registry* a package is fetched from.
```yaml title="pnpm-workspace.yaml"
namedRegistries:
work: https://npm.work.example.com/
```
```bash
pnpm add work:@corp/lib@^2.0.0 # resolves @corp/lib against the work registry
```
The built-in `gh:` alias points at GitHub Packages. Auth is reused from per-URL `.npmrc` entries.
## Best Practices
1. **Clear naming**: Use descriptive alias names that indicate purpose
```json
"lodash-legacy": "npm:lodash@3"
"lodash-modern": "npm:lodash@4"
```
2. **Document aliases**: explain why aliases exist
3. **Prefer overrides for global replacement**: to replace a package everywhere, use `overrides` (in `pnpm-workspace.yaml`) instead of aliases
4. **Test thoroughly**: Aliased packages may have subtle differences in behavior
<!--
Source references:
- https://pnpm.io/aliases
- https://pnpm.io/settings#namedregistries
-->
references/features-catalogs.md---
name: pnpm-catalogs
description: Centralized dependency version management for workspaces
---
# pnpm Catalogs
Catalogs provide a centralized way to manage dependency versions across a workspace. Define versions once, use everywhere.
## Basic Usage
Define a catalog in `pnpm-workspace.yaml`:
```yaml
packages:
- 'packages/*'
catalog:
react: ^18.2.0
react-dom: ^18.2.0
typescript: ~5.3.0
vite: ^5.0.0
```
Reference in `package.json` with `catalog:`:
```json
{
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:"
},
"devDependencies": {
"typescript": "catalog:",
"vite": "catalog:"
}
}
```
`catalog:` is shorthand for `catalog:default`. The `catalog:` protocol is valid in `package.json` `dependencies`, `devDependencies`, `peerDependencies`, and `optionalDependencies`, plus in `overrides` inside `pnpm-workspace.yaml`. It also works on the CLI: `pnpm add react@catalog:` and `pnx shx@catalog:`.
## Named Catalogs
Create multiple catalogs for different scenarios:
```yaml
packages:
- 'packages/*'
# Default catalog
catalog:
lodash: ^4.17.21
# Named catalogs
catalogs:
react17:
react: ^17.0.2
react-dom: ^17.0.2
react18:
react: ^18.2.0
react-dom: ^18.2.0
testing:
vitest: ^1.0.0
"@testing-library/react": ^14.0.0
```
Reference named catalogs:
```json
{
"dependencies": {
"react": "catalog:react18",
"react-dom": "catalog:react18"
},
"devDependencies": {
"vitest": "catalog:testing"
}
}
```
## Keeping overrides in sync with a catalog
Reference a catalog from `overrides` so the version lives in exactly one place:
```yaml title="pnpm-workspace.yaml"
catalog:
foo: ^1.0.0
overrides:
foo: 'catalog:' # or catalog:<name>
```
## Settings
```yaml title="pnpm-workspace.yaml"
# How `pnpm add` interacts with the default catalog (v10.12+)
catalogMode: manual # manual (default) | prefer | strict
# strict: only catalog versions allowed; prefer: fall back if no match
cleanupUnusedCatalogs: true # remove unused catalog entries on install (v10.15+)
```
## Benefits
1. **Single source of truth**: Update version in one place
2. **Consistency**: All packages use the same version
3. **Easy upgrades**: Change version once, affects entire workspace
4. **Fewer merge conflicts**: package.json files stay untouched on upgrades
## Catalog vs Overrides
| Feature | Catalogs | Overrides |
|---------|----------|-----------|
| Purpose | Define versions for direct dependencies | Force versions for any dependency |
| Scope | Direct dependencies only | All dependencies (including transitive) |
| Usage | `"pkg": "catalog:"` | Applied automatically |
| Opt-in | Explicit per package.json | Global to workspace |
## Publishing with Catalogs
When publishing, `catalog:` references are replaced with actual versions:
```json
// Before publish (source)
{
"dependencies": {
"react": "catalog:"
}
}
// After publish (published package)
{
"dependencies": {
"react": "^18.2.0"
}
}
```
## Migration from Overrides
If you're using overrides for version consistency:
```yaml
# Before (using overrides)
overrides:
react: ^18.2.0
react-dom: ^18.2.0
```
Migrate to catalogs for cleaner dependency management:
```yaml
# After (using catalogs)
catalog:
react: ^18.2.0
react-dom: ^18.2.0
```
Then update package.json files to use `catalog:`. To migrate an existing workspace automatically:
```bash
pnpx codemod pnpm/catalog
```
## Best Practices
1. **Use default catalog** for commonly shared dependencies
2. **Use named catalogs** for version variants (e.g., different React versions)
3. **Keep catalog minimal** - only include shared dependencies
4. **Combine with workspace protocol** for internal packages
```yaml
catalog:
# External shared dependencies
lodash: ^4.17.21
zod: ^3.22.0
# Internal packages use workspace: protocol instead
# "dependencies": { "@myorg/utils": "workspace:^" }
```
<!--
Source references:
- https://pnpm.io/catalogs
-->
references/features-config-dependencies.md---
name: pnpm-config-dependencies
description: Share and centralize pnpm hooks, settings, patches, catalogs, and overrides across repos via config dependencies
---
# pnpm Config Dependencies
Config dependencies are npm packages that pnpm installs **before** all regular dependencies, so they can supply hooks, settings, patches, catalogs, and overrides that are reused across many repositories. They let you keep one shared "pnpm config" package and consume it everywhere.
## Declaring config dependencies
They live in `pnpm-workspace.yaml`; their integrity is recorded in a dedicated env-lockfile document inside `pnpm-lock.yaml`.
```yaml title="pnpm-workspace.yaml"
configDependencies:
my-configs: "1.0.0"
```
Add one with the `--config` flag:
```bash
pnpm add --config my-configs
pnpm add --config @myorg/pnpm-plugin-my-catalogs
```
## Constraints
- **No regular `dependencies`.** They may declare `optionalDependencies`, but only one level deep.
- **No lifecycle scripts** (`preinstall`, `postinstall`, …).
- `optionalDependencies` (used for platform-specific binaries, esbuild-style) must use **exact** versions — ranges/tags are rejected, keeping installs reproducible.
## Auto-loaded plugins
A config dependency named `pnpm-plugin-*`, `@*/pnpm-plugin-*`, or `@pnpm/plugin-*` has its `pnpmfile.mjs` (or `.cjs`) loaded automatically from the package root.
## Use cases
### Import hook logic from a shared package
Because config deps install before the pnpmfile loads, you can import from them:
```js title=".pnpmfile.mjs"
import { readPackage } from '.pnpm-config/my-hooks'
export const hooks = { readPackage }
```
### Share settings & catalogs via updateConfig
A plugin can inject settings/catalog entries through the `updateConfig` hook:
```js title="@myorg/pnpm-plugin-my-catalogs/pnpmfile.mjs"
export const hooks = {
updateConfig(config) {
config.catalogs.default ??= {}
config.catalogs.default['is-odd'] = '1.0.0'
return config
}
}
```
After installing it as a config dependency, consumers can use the catalog:
```bash
pnpm add is-odd@catalog: # installs is-odd@1.0.0, writes "is-odd": "catalog:"
```
### Share patch files
Reference patches stored inside a config dependency:
```yaml title="pnpm-workspace.yaml"
configDependencies:
my-patches: "1.0.0"
patchedDependencies:
react: "node_modules/.pnpm-config/my-patches/react.patch"
```
## Key Points
- Centralize hooks, settings, catalogs, overrides, and patches in one package, consumed across repos.
- Declared via `configDependencies` in `pnpm-workspace.yaml`; installed before regular deps.
- No regular dependencies and no lifecycle scripts; `optionalDependencies` need exact versions.
- `pnpm-plugin-*` / `@pnpm/plugin-*` packages auto-load their pnpmfile.
- Pair with the `updateConfig` hook to push settings/catalogs into consuming projects.
<!--
Source references:
- https://pnpm.io/config-dependencies
- https://pnpm.io/pnpmfile#hooksupdateconfigconfig-config--promiseconfig
-->
references/features-global-virtual-store.md---
name: pnpm-global-virtual-store
description: Global virtual store for shared node_modules across checkouts, git-worktree multi-agent setups, and isolated global packages
---
# Global Virtual Store, Git Worktrees & Global Packages
## Global virtual store
By default each project has its own `node_modules/.pnpm` virtual store containing hard links to the content-addressable store. With the **global virtual store** enabled, pnpm keeps one shared virtual store at `<store-path>/links/` (find it via `pnpm store path`), and each project's `node_modules` contains only **symlinks** into it.
```yaml title="pnpm-workspace.yaml"
enableGlobalVirtualStore: true
```
```
# Default (per-project .pnpm with hard links)
project-a/node_modules/lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
# Global virtual store (symlink to shared location)
project-a/node_modules/lodash -> <store>/links/@/lodash/4.17.21/<hash>/node_modules/lodash
project-b/node_modules/lodash -> <store>/links/@/lodash/4.17.21/<hash>/node_modules/lodash # same target
```
- **Package identity = hash of the dependency graph.** Two projects with the same `lodash@4.17.21` and the same transitive tree point at the exact same directory (NixOS-style). Different peers ⇒ separate entries.
- **Near-zero per-project cost** and **instant installs** once a version is in the store.
- In **pnpm v11** it is the default for `pnpm dlx`/`pnx` and global installs; for **project** installs it is still **opt-in/experimental**.
### Limitations
- **CI:** auto-disabled (no warm cache to benefit from).
- **Trust:** the store is shared writable state — only for mutually trusting projects/users/jobs; protect the path with filesystem permissions.
- **ESM hoisting:** relies on `NODE_PATH`, which Node ignores for ESM imports. If ESM deps import undeclared packages, resolution fails. Fix with `packageExtensions` or the `@pnpm/plugin-esm-node-path` config dependency.
## Git worktrees for multi-agent development
Git worktrees let you check out many branches simultaneously, each in its own directory, sharing one `.git` object store. Combined with the global virtual store, every worktree gets a fully functional `node_modules` that is almost free on disk — ideal for running multiple AI agents in parallel.
```sh
# Bare repo as the hub, one worktree per branch/agent
git clone --bare https://github.com/your-org/your-monorepo.git your-monorepo
cd your-monorepo
git worktree add ./main main
git worktree add ./feature-auth feat/auth
git worktree add ./fix-api fix/api-error
```
```yaml title="pnpm-workspace.yaml"
packages:
- 'packages/*'
enableGlobalVirtualStore: true
```
```sh
cd main && pnpm install # first install fills the global store
cd ../feature-auth && pnpm install # subsequent worktrees: nearly instant, just symlinks
```
Each worktree has its own `node_modules` tree (so agents can install different versions on different branches without conflict), but all package contents come from the one shared store. Remove a worktree with `git worktree remove ./feature-auth`.
> The pnpm repo itself uses this setup and ships helper scripts (`pnpm worktree:new <branch|pr>`). Assumes all worktrees/agents share the same trust boundary.
## Global packages (v11 isolated installs)
`pnpm add -g` was redesigned in v11 for isolation. Each globally installed package (or group) gets its own install directory with its own `package.json`, `node_modules/`, and lockfile, so global tools can't break each other via peer/hoisting conflicts. Installs are stored at `{pnpmHomeDir}/global/v11/{hash}/` and share the global virtual store.
```sh
pnpm add -g typescript prettier # space-separated = separate isolated installs each
pnpm add -g eslint,prettier # comma-separated = ONE shared install group
pnpm remove -g eslint # removes only eslint's group
pnpm add -g --allow-build=esbuild esbuild # pre-approve build scripts
pnpm list -g # always works at depth 0
pnpm bin -g # global bin dir = $PNPM_HOME/bin
```
- `pnpm install -g` (no args) is **not** supported — use `pnpm add -g <pkg>`.
- Binaries live in `$PNPM_HOME/bin` (not `$PNPM_HOME` directly). Run `pnpm setup` after upgrading to put it on PATH.
- Register a local package's bins globally with `pnpm add -g .` (replaces `pnpm link --global`).
- `pnpm list -g --depth=<n>` (n>0) only works for a single install group.
## Key Points
- `enableGlobalVirtualStore: true` ⇒ `node_modules` is symlinks into one shared, hash-addressed store.
- Best for many checkouts of the same repo (git worktrees, parallel agents); auto-disabled in CI.
- Watch out for ESM packages importing undeclared deps (NODE_PATH limitation).
- v11 global installs are isolated per package; comma-list to share a group; bins live in `$PNPM_HOME/bin`.
<!--
Source references:
- https://pnpm.io/global-virtual-store
- https://pnpm.io/git-worktrees
- https://pnpm.io/global-packages
- https://pnpm.io/settings#enableglobalvirtualstore
-->
references/features-hooks.md---
name: pnpm-hooks
description: Customize resolution, config, packing, and fetching with .pnpmfile.mjs hooks, finders, and custom resolvers/fetchers
---
# pnpm Hooks (.pnpmfile.mjs)
pnpm hooks customize installation. Declare them in `.pnpmfile.mjs` (ESM, preferred) or `.pnpmfile.cjs` (CommonJS), located next to the lockfile (workspace root for a monorepo).
> The modern format uses ESM `export const hooks = { ... }`. The old CommonJS `module.exports = { hooks }` still works in `.pnpmfile.cjs`.
## Setup
```js title=".pnpmfile.mjs"
export const hooks = {
readPackage,
afterAllResolved,
updateConfig,
beforePacking,
}
```
## Hook reference
| Hook | When | Use |
|------|------|-----|
| `readPackage(pkg, ctx)` | after a dependency manifest is parsed | mutate a dependency's `package.json` (affects resolution) |
| `afterAllResolved(lockfile, ctx)` | after resolution | mutate the lockfile before it's written |
| `updateConfig(config)` | before install | mutate pnpm's settings (great with config dependencies) |
| `beforePacking(pkg)` | before `pnpm pack`/`publish` tarball | customize the **published** manifest only |
| `preResolution(opts)` | after reading lockfiles, before resolution | inspect/modify lockfile objects |
| `importPackage(dir, opts)` | when writing to node_modules | change how packages are linked |
## readPackage
Called for every package before resolution. Common uses:
```js title=".pnpmfile.mjs"
function readPackage(pkg, context) {
// Add a missing peer dependency
if (pkg.name === 'some-broken-package') {
pkg.peerDependencies = { ...pkg.peerDependencies, react: '*' }
}
// Pin a transitive version
if (pkg.dependencies?.lodash) pkg.dependencies.lodash = '^4.17.21'
// Drop a problematic optional dep
delete pkg.optionalDependencies?.fsevents
// Replace a deprecated dep
if (pkg.dependencies?.['old-pkg']) {
pkg.dependencies['new-pkg'] = pkg.dependencies['old-pkg']
delete pkg.dependencies['old-pkg']
}
return pkg
}
export const hooks = { readPackage }
```
> Mutations are not written to disk; they only affect resolution. Delete `pnpm-lock.yaml` to re-resolve an already-locked dependency. Removing `scripts` here does **not** stop a build — use the `allowBuilds` setting instead. To persist a change to a dependency's files, use `pnpm patch`.
## updateConfig
Modify pnpm's own settings programmatically — most powerful when shipped in a config dependency so settings are shared across repos.
```js title=".pnpmfile.mjs"
export const hooks = {
updateConfig(config) {
return Object.assign(config, {
enablePrePostScripts: false,
optimisticRepeatInstall: true,
resolutionMode: 'lowest-direct',
verifyDepsBeforeRun: 'install',
})
}
}
```
```js
// Add a catalog entry from a plugin
export const hooks = {
updateConfig(config) {
config.catalogs.default ??= {}
config.catalogs.default['is-odd'] = '1.0.0'
return config
}
}
```
## beforePacking
Customize the manifest that ends up in the published tarball without touching your local `package.json`.
```js title=".pnpmfile.mjs"
export const hooks = {
beforePacking(pkg) {
delete pkg.devDependencies
pkg.main = './dist/index.js'
return pkg
}
}
```
## afterAllResolved
```js title=".pnpmfile.mjs"
export const hooks = {
afterAllResolved(lockfile, context) {
context.log(`Resolved ${Object.keys(lockfile.packages || {}).length} packages`)
return lockfile
}
}
```
## Finders (pnpm list / why)
Custom predicates used via `--find-by`:
```js title=".pnpmfile.mjs"
export const finders = {
react17: (ctx) => ctx.readManifest().peerDependencies?.react === '^17.0.0'
}
```
```bash
pnpm why --find-by=react17
```
## Custom resolvers & fetchers (advanced)
Register top-level `resolvers`/`fetchers` to support new package schemes (e.g. `my-protocol:pkg`). Each is an object with cheap `canResolve`/`canFetch` guards plus `resolve`/`fetch`. Custom resolvers run before built-ins; custom resolution `type` fields must use the `custom:` prefix.
```js title=".pnpmfile.cjs"
const resolver = {
canResolve: (dep) => dep.alias.startsWith('@company/'),
resolve: async (dep) => ({
id: `${dep.alias}@${dep.bareSpecifier}`,
resolution: { type: 'custom:cdn', cdnUrl: '...' },
}),
}
const fetcher = {
canFetch: (id, res) => res.type === 'custom:cdn',
fetch: (cafs, res, opts, fetchers) =>
fetchers.remoteTarball(cafs, { tarball: res.cdnUrl, integrity: res.integrity }, opts),
}
module.exports = { resolvers: [resolver], fetchers: [fetcher] }
```
> `hooks.fetchers` was removed in v11 — use the top-level `fetchers` export instead.
## Related settings
```yaml title="pnpm-workspace.yaml"
ignorePnpmfile: false # ignore the pnpmfile entirely
pnpmfile: ['.pnpmfile.mjs'] # local pnpmfile location(s)
globalPnpmfile: ~/.pnpm/global_pnpmfile.mjs
```
## Hooks vs Overrides
| | Hooks (.pnpmfile) | Overrides (pnpm-workspace.yaml) |
|--|-------------------|---------------------------------|
| Logic | JavaScript | declarative |
| Scope | any manifest field, config, lockfile, packing | versions |
| Use when | conditional/complex fixes | simple version pins |
Prefer `overrides`/`packageExtensions` for simple cases; use hooks for conditional logic, config sharing, or packing tweaks.
## Key Points
- Prefer `.pnpmfile.mjs` with `export const hooks`/`finders`/`resolvers`/`fetchers`.
- New hooks: `updateConfig` (mutate settings), `beforePacking` (published manifest), `preResolution`, `importPackage`.
- Pair `updateConfig` with config dependencies to share settings/catalogs across repos.
- `--ignore-scripts` does **not** disable the pnpmfile; use `ignorePnpmfile`.
<!--
Source references:
- https://pnpm.io/pnpmfile
- https://pnpm.io/finders
- https://pnpm.io/config-dependencies
-->
references/features-overrides.md---
name: pnpm-overrides
description: Force specific versions of dependencies including transitive dependencies
---
# pnpm Overrides
Overrides let you force specific versions of packages, including transitive dependencies. Useful for fixing security vulnerabilities or compatibility issues.
## Basic Syntax
Define overrides in `pnpm-workspace.yaml`. They can only be set at the **root** of the project.
> The `pnpm.overrides` field in `package.json` is **no longer read** (pnpm no longer reads any settings from `package.json#pnpm`). Move overrides to `pnpm-workspace.yaml`.
```yaml title="pnpm-workspace.yaml"
packages:
- 'packages/*'
overrides:
# Override all versions of a package
lodash: ^4.17.21
# Override specific version range
"foo@^1.0.0": ^1.2.3
# Override nested dependency (only zoo inside qar@1)
"qar@1>zoo": "2"
# Override to different package
"underscore": "npm:lodash@^4.17.21"
# Reference a catalog so the version stays in sync
"react": "catalog:"
```
## Override Patterns
### Override all instances
```yaml
overrides:
lodash: ^4.17.21
```
Forces all lodash installations to use ^4.17.21.
### Override specific parent version
```yaml
overrides:
"foo@^1.0.0": ^1.2.3
```
Only override foo when the requested version matches ^1.0.0.
### Override nested dependency
```yaml
overrides:
"express>cookie": ^0.6.0
"foo@1.x>bar@^2.0.0>qux": ^1.0.0
```
Override cookie only when it's a dependency of express.
### Replace with different package
```yaml
overrides:
# Replace underscore with lodash
"underscore": "npm:lodash@^4.17.21"
# Use local file
"some-pkg": "file:./local-pkg"
# Use git
"some-pkg": "github:user/repo#commit"
```
### Remove a dependency
```yaml
overrides:
"unwanted-pkg": "-"
"foo@1.0.0>bar": "-" # great for skipping unused optionalDependencies
```
The `-` removes the package entirely.
### Override peer dependencies
Overrides also apply to `peerDependencies`:
```yaml title="pnpm-workspace.yaml"
overrides:
"react-dom>react": "18.1.0"
```
- Semver ranges, `workspace:`, and `catalog:` keep the entry as a peer dependency.
- Non-range specifiers (`link:`, `file:`) move it into `dependencies`.
- `-` removes the peer dependency entirely.
## Common Use Cases
### Security Fix
Force patched version of vulnerable package:
```yaml
overrides:
# Fix CVE in transitive dependency
"minimist": "^1.2.6"
"json5": "^2.2.3"
```
### Deduplicate Dependencies
Force single version when multiple are installed:
```yaml
overrides:
"react": "^18.2.0"
"react-dom": "^18.2.0"
```
### Fix Peer Dependency Issues
```yaml
overrides:
"@types/react": "^18.2.0"
```
### Replace Deprecated Package
```yaml
overrides:
"request": "npm:@cypress/request@^3.0.0"
```
## Hooks Alternative
For more complex scenarios, use `.pnpmfile.mjs`:
```js title=".pnpmfile.mjs"
function readPackage(pkg, context) {
// Override dependency version
if (pkg.dependencies?.lodash) {
pkg.dependencies.lodash = '^4.17.21'
}
// Add missing peer dependency
if (pkg.name === 'some-package') {
pkg.peerDependencies = {
...pkg.peerDependencies,
react: '*'
}
}
return pkg
}
export const hooks = {
readPackage
}
```
Or extend a manifest declaratively with `packageExtensions` (no JS needed):
```yaml title="pnpm-workspace.yaml"
packageExtensions:
react-redux:
peerDependencies:
react-dom: '*'
```
## Overrides vs Catalogs
| Feature | Overrides | Catalogs |
|---------|-----------|----------|
| Affects | All dependencies (including transitive) | Direct dependencies only |
| Usage | Automatic | Explicit `catalog:` reference |
| Purpose | Force versions, fix issues | Version management |
| Granularity | Can target specific parents | Package-wide only |
## Debugging
Check which version is resolved:
```bash
# See resolved versions
pnpm why lodash
# List all versions
pnpm list lodash --depth=Infinity
```
<!--
Source references:
- https://pnpm.io/settings#overrides
- https://pnpm.io/settings#packageextensions
- https://pnpm.io/pnpmfile
-->
references/features-patches.md---
name: pnpm-patches
description: Patch third-party packages directly with customized fixes
---
# pnpm Patches
pnpm's patching feature lets you modify third-party packages directly. Useful for applying fixes before upstream releases or customizing package behavior.
## Creating a Patch
### Step 1: Initialize Patch
```bash
pnpm patch <pkg>@<version>
# Example
pnpm patch express@4.18.2
```
This creates a temporary directory with the package source and outputs the path:
```
You can now edit the following folder: /tmp/abc123...
```
### Step 2: Edit Files
Navigate to the temporary directory and make your changes:
```bash
cd /tmp/abc123...
# Edit files as needed
```
### Step 3: Commit Patch
```bash
pnpm patch-commit <path-from-step-1>
# Example
pnpm patch-commit /tmp/abc123...
```
This creates a `.patch` file in `patches/` and records it in `pnpm-workspace.yaml`:
```
patches/
└── express@4.18.2.patch
```
```yaml title="pnpm-workspace.yaml"
patchedDependencies:
express@4.18.2: patches/express@4.18.2.patch
```
> `patchedDependencies` (like all pnpm settings) now lives in `pnpm-workspace.yaml`, not the `package.json#pnpm` field.
## Patch File Format
Patches use standard unified diff format:
```diff
diff --git a/lib/router/index.js b/lib/router/index.js
index abc123..def456 100644
--- a/lib/router/index.js
+++ b/lib/router/index.js
@@ -100,6 +100,7 @@ function createRouter() {
// Original code
- const timeout = 30000;
+ const timeout = 60000; // Extended timeout
return router;
}
```
## Managing Patches
### List Patched Packages
```bash
pnpm list --depth=0
# Shows (patched) marker for patched packages
```
### Update a Patch
```bash
# Edit existing patch
pnpm patch express@4.18.2
# After editing
pnpm patch-commit <path>
```
### Remove a Patch
```bash
pnpm patch-remove <pkg>@<version>
# Example
pnpm patch-remove express@4.18.2
```
Or manually:
1. Delete the patch file from `patches/`
2. Remove the entry from `patchedDependencies` in `pnpm-workspace.yaml`
3. Run `pnpm install`
## Patch Configuration
### Multiple Packages / Workspaces
Patches are shared across the whole workspace from the root `pnpm-workspace.yaml`:
```yaml title="pnpm-workspace.yaml"
patchedDependencies:
express@4.18.2: patches/express@4.18.2.patch
lodash@4.17.21: patches/lodash@4.17.21.patch
'@types/node@20.10.0': patches/@types__node@20.10.0.patch
```
A version-less key (`express:`) patches every installed version. All workspace packages using a matching version get the patch.
### Patches from a config dependency
Patch files can live inside a shared config dependency and be referenced by path:
```yaml title="pnpm-workspace.yaml"
configDependencies:
my-patches: '1.0.0'
patchedDependencies:
react: node_modules/.pnpm-config/my-patches/react.patch
```
### allowUnusedPatches
```yaml title="pnpm-workspace.yaml"
allowUnusedPatches: true # don't fail when a listed patch wasn't applied
```
> `ignorePatchFailures` was **removed** in v11. A patch that fails to apply now always throws. When several patches are grouped, all errors are reported together at the end.
## Best Practices
1. **Version specificity**: Patches are tied to exact versions. Update patches when upgrading dependencies.
2. **Document patches**: Add comments explaining why the patch exists:
```bash
# In patches/README.md
## express@4.18.2.patch
Fixes timeout issue. PR pending: https://github.com/expressjs/express/pull/1234
```
3. **Minimize patches**: Keep patches small and focused. Large patches are hard to maintain.
4. **Track upstream**: Note upstream issues/PRs so you can remove patches when fixed.
5. **Test patches**: Ensure patched code works correctly in your use case.
## Troubleshooting
### Patch fails to apply
```
ERR_PNPM_PATCH_FAILED Cannot apply patch
```
The package version changed. Recreate the patch:
```bash
pnpm patch-remove express@4.18.2
pnpm patch express@4.18.2
# Reapply changes
pnpm patch-commit <path>
```
### Patch not applied
Ensure:
1. Version in `patchedDependencies` matches installed version exactly
2. Run `pnpm install` after adding patch configuration
<!--
Source references:
- https://pnpm.io/cli/patch
- https://pnpm.io/cli/patch-commit
- https://pnpm.io/config-dependencies
-->
references/features-peer-deps.md---
name: pnpm-peer-dependencies
description: Handling peer dependencies with auto-install and resolution rules
---
# pnpm Peer Dependencies
pnpm has strict peer dependency handling by default. It provides configuration options to control how peer dependencies are resolved and reported.
All peer-dependency settings live in `pnpm-workspace.yaml` (camelCase). The `package.json#pnpm` field is no longer read.
## Auto-Install Peer Dependencies
By default (since v8), pnpm automatically installs missing non-optional peer dependencies:
```yaml title="pnpm-workspace.yaml"
autoInstallPeers: true
```
On conflicting requirements (e.g. one dep needs `react@^16`, another `react@^17`), pnpm installs nothing and prints a warning — resolve it manually.
## Strict Peer Dependencies
```yaml title="pnpm-workspace.yaml"
strictPeerDependencies: true # default false
```
When strict, commands fail on a missing or invalid peer dependency in the tree.
## Resolve from workspace root
```yaml title="pnpm-workspace.yaml"
resolvePeersFromWorkspaceRoot: true # default; install shared peers once at the root
```
## Deduplicate peers
```yaml title="pnpm-workspace.yaml"
dedupePeerDependents: true # default; share package instances across projects when peers match
dedupePeers: false # v10.33+: version-only peer suffixes (name@version), fewer instances
```
## Peer Dependency Rules
```yaml title="pnpm-workspace.yaml"
peerDependencyRules:
ignoreMissing:
- '@babel/*'
- eslint
allowedVersions:
react: '17 || 18'
allowAny:
- '@types/*'
```
### ignoreMissing
Suppress warnings for missing peer dependencies. Patterns: exact name (`react`), scope (`@babel/*`), or `*` (not recommended).
```yaml title="pnpm-workspace.yaml"
peerDependencyRules:
ignoreMissing:
- '@babel/*'
- eslint
- webpack
```
### allowedVersions
Allow specific versions that would otherwise warn. Target a specific parent with `parent>peer`.
```yaml title="pnpm-workspace.yaml"
peerDependencyRules:
allowedVersions:
react: '17'
'button@2>react': '17' # only when react is a peer of button@2
```
### allowAny
Resolve matching peers from any version, ignoring the declared range.
```yaml title="pnpm-workspace.yaml"
peerDependencyRules:
allowAny:
- '@types/*'
- eslint
```
## Adding Peer Dependencies via packageExtensions
Declaratively add a missing peer dependency without JS:
```yaml title="pnpm-workspace.yaml"
packageExtensions:
problematic-package:
peerDependencies:
react: '*'
```
For conditional logic, use a `readPackage` hook in `.pnpmfile.mjs` instead.
## Peer Dependencies in Workspaces
Workspace packages can satisfy peer dependencies:
```json
// packages/app/package.json
{
"dependencies": {
"react": "^18.2.0",
"@myorg/components": "workspace:^"
}
}
// packages/components/package.json
{
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0"
}
}
```
The workspace `app` provides `react` which satisfies `components`' peer dependency.
## Common Scenarios
### Monorepo with Shared React
```yaml
# pnpm-workspace.yaml
catalog:
react: ^18.2.0
react-dom: ^18.2.0
```
```json
// packages/ui/package.json
{
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
// apps/web/package.json
{
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:",
"@myorg/ui": "workspace:^"
}
}
```
### Suppress ESLint Plugin Warnings
```yaml title="pnpm-workspace.yaml"
peerDependencyRules:
ignoreMissing:
- eslint
- '@typescript-eslint/parser'
```
### Allow Multiple Major Versions
```yaml title="pnpm-workspace.yaml"
peerDependencyRules:
allowedVersions:
webpack: '4 || 5'
postcss: '7 || 8'
```
## Debugging Peer Dependencies
```bash
# Report unmet/missing peers straight from the lockfile (v11)
pnpm peers check
# See why a package is installed
pnpm why <package>
# Check dependency tree
pnpm list --depth=Infinity
```
## Best Practices
1. **Keep `autoInstallPeers` on** for convenience (default in v8+)
2. **Use `peerDependencyRules`** instead of blanket-ignoring warnings
3. **Document suppressed warnings** explaining why they're safe
4. **Keep peer ranges wide** in libraries (e.g. `"react": "^17 || ^18"`)
5. **Run `pnpm peers check`** in CI to catch peer regressions
<!--
Source references:
- https://pnpm.io/settings#peerdependencyrules
- https://pnpm.io/settings#autoinstallpeers
- https://pnpm.io/cli/peers
-->
references/features-supply-chain-security.md--- name: pnpm-supply-chain-security description: Build-script approval (allowBuilds), minimum release age, trust policy, and exotic-subdep blocking for safer installs --- # pnpm Supply-Chain Security pnpm blocks several attack vectors by default. Agents installing dependencies must understand these, since installs can fail or prompt on them. ## Build-script approval (allowBuilds) By default pnpm does **not** run dependency lifecycle scripts (`preinstall`/`install`/`postinstall`). Packages must be explicitly approved. Approval lives in one `allowBuilds` map in `pnpm-workspace.yaml`. ```yaml title="pnpm-workspace.yaml" allowBuilds: esbuild: true core-js: false # version selectors are supported nx@21.6.4 || 21.6.5: true ``` - Packages **not listed** are unreviewed and blocked by default. - `strictDepBuilds: true` (default) ⇒ unreviewed builds make install exit non-zero (`ERR_PNPM_IGNORED_BUILDS`). Set `false` to warn instead. - During install, unreviewed packages with build scripts are auto-added to `pnpm-workspace.yaml` with a placeholder so you can set `true`/`false`. > `allowBuilds` replaces the removed `onlyBuiltDependencies`, `neverBuiltDependencies`, `ignoredBuiltDependencies`, `onlyBuiltDependenciesFile`, and `ignoreDepScripts`. ### Approving builds ```bash pnpm approve-builds # interactive prompt pnpm approve-builds --all # approve all pending pnpm approve-builds esbuild fsevents !core-js # ! = deny pnpm add --allow-build=esbuild my-bundler # approve while adding pnpm add -g --allow-build=esbuild esbuild # global (replaces approve-builds -g) ``` ### Escape hatch (dangerous) ```yaml title="pnpm-workspace.yaml" dangerouslyAllowAllBuilds: true # runs ALL build scripts now and in the future — avoid ``` ## Minimum release age Delay installing freshly published versions so malicious releases (usually pulled within an hour) are avoided. Applies to **all** deps, including transitive. ```yaml title="pnpm-workspace.yaml" minimumReleaseAge: 1440 # minutes; default 1440 (1 day) since v11 minimumReleaseAgeExclude: # always install newest of these immediately - webpack - '@myorg/*' - nx@21.6.5 # exempt a specific version ``` - `minimumReleaseAgeStrict` — when no in-range version satisfies the age, fail (default when you set `minimumReleaseAge` yourself) vs. fall back. - `minimumReleaseAgeIgnoreMissingTime` — skip the check for registries that omit the `time` field (default `true`). ## Trust policy Fail if a package's trust level **decreased** vs earlier releases (e.g. was published by a trusted publisher, now only has provenance or nothing). ```yaml title="pnpm-workspace.yaml" trustPolicy: no-downgrade # off (default) | no-downgrade trustPolicyExclude: - 'chokidar@4.0.3' trustPolicyIgnoreAfter: 525600 # ignore the check for pkgs published > N minutes ago ``` ## Block exotic transitive sources ```yaml title="pnpm-workspace.yaml" blockExoticSubdeps: true # default ``` When `true`, only **direct** dependencies may use exotic sources (git repos, direct tarball URLs); all transitive deps must come from a trusted source (registry, local path, workspace link, or trusted GitHub repos). ## Lockfile integrity Since v11, a downloaded tarball whose hash doesn't match `pnpm-lock.yaml` is a hard error (`ERR_PNPM_TARBALL_INTEGRITY`) — protecting committed lockfiles from a compromised registry/proxy. `--force` and `pnpm update` do **not** bypass it. ```bash pnpm install --update-checksums # narrow opt-in after verifying the new bytes ``` ## Trusted store/cache The content-addressable store, global virtual store, and metadata cache are part of pnpm's trust domain. Share them only between mutually trusting users/jobs and protect with filesystem permissions. `verifyStoreIntegrity` (default `true`) detects accidental corruption but does not make a writable-by-untrusted store safe. ## Key Points - Dependency build scripts are blocked until approved via `allowBuilds` / `pnpm approve-builds`; unreviewed builds fail by default (`strictDepBuilds`). - `minimumReleaseAge` (default 1 day in v11) delays new releases; `trustPolicy: no-downgrade` blocks trust regressions; `blockExoticSubdeps` limits transitive git/tarball sources. - Tarball integrity mismatches are fatal; use `--update-checksums` only after verification. - Treat the store/cache as trusted shared state. <!-- Source references: - https://pnpm.io/settings#allowbuilds - https://pnpm.io/cli/approve-builds - https://pnpm.io/settings#minimumreleaseage - https://pnpm.io/settings#trustpolicy - https://pnpm.io/settings#blockexoticsubdeps - https://pnpm.io/supply-chain-security -->
SKILL.md--- name: pnpm description: Node.js package manager with strict dependency resolution. Use when running pnpm specific commands, configuring workspaces via pnpm-workspace.yaml, or managing dependencies with catalogs, patches, overrides, config dependencies, or the global virtual store. metadata: author: Anthony Fu version: "2026.6.22" source: Generated from https://github.com/pnpm/pnpm, scripts located at https://github.com/antfu/skills --- pnpm is a fast, disk space efficient package manager. It uses a content-addressable store to deduplicate packages across all projects on a machine, and enforces strict dependency resolution by default, preventing phantom dependencies. **Configuration model (important):** pnpm settings now live in `pnpm-workspace.yaml` (and the global `config.yaml`) using **camelCase** keys. `.npmrc` is used **only** for authentication/registry credentials, and the `pnpm` field of `package.json` is no longer read. When working in a pnpm project, check `pnpm-workspace.yaml` for settings/workspace structure and `.npmrc` only for auth. Always use `--frozen-lockfile` (or `pnpm ci`) in CI. > The skill is based on pnpm 10.x, generated at 2026-06-22. It also covers v11 behavior changes (config split, isolated global packages, `allowBuilds`, `pmOnFail`, global virtual store) where current docs describe them. ## Core | Topic | Description | Reference | |-------|-------------|-----------| | CLI Commands | install/add/remove/update, run, dlx/pnx, workspace, runtime, publishing (version, view, sbom, stage) | [core-cli](references/core-cli.md) | | Configuration | pnpm-workspace.yaml settings (camelCase), global config.yaml, packageConfigs, .npmrc auth | [core-config](references/core-config.md) | | Workspaces | Monorepo support: filtering, workspace protocol, shared lockfile, packageConfigs | [core-workspaces](references/core-workspaces.md) | | Store | Content-addressable store, virtual store, node linker modes, frozen/read-only store | [core-store](references/core-store.md) | ## Features | Topic | Description | Reference | |-------|-------------|-----------| | Catalogs | Centralized dependency versions; catalogMode, catalog: in overrides | [features-catalogs](references/features-catalogs.md) | | Overrides | Force versions (incl. transitive & peer deps); packageExtensions | [features-overrides](references/features-overrides.md) | | Patches | Modify third-party packages; patchedDependencies in pnpm-workspace.yaml | [features-patches](references/features-patches.md) | | Aliases | Install under custom names (npm:) and registry aliases (namedRegistries) | [features-aliases](references/features-aliases.md) | | Hooks | .pnpmfile.mjs hooks (readPackage, updateConfig, beforePacking), finders, resolvers/fetchers | [features-hooks](references/features-hooks.md) | | Peer Dependencies | Auto-install, strict mode, rules, dedupePeers, peers check | [features-peer-deps](references/features-peer-deps.md) | | Config Dependencies | Share hooks/settings/catalogs/patches across repos via configDependencies | [features-config-dependencies](references/features-config-dependencies.md) | | Global Virtual Store | Shared node_modules, git-worktree multi-agent setups, isolated global packages | [features-global-virtual-store](references/features-global-virtual-store.md) | | Supply-Chain Security | Build approval (allowBuilds), minimumReleaseAge, trustPolicy, lockfile integrity | [features-supply-chain-security](references/features-supply-chain-security.md) | ## Best Practices | Topic | Description | Reference | |-------|-------------|-----------| | CI/CD Setup | GitHub Actions, GitLab, Docker, pnpm ci, store caching, frozen lockfiles | [best-practices-ci](references/best-practices-ci.md) | | Migration | npm/Yarn → pnpm, phantom deps, and pnpm v10 → v11 config migration | [best-practices-migration](references/best-practices-migration.md) | | Performance | Install optimizations, allowBuilds, global virtual store, workspace parallelization | [best-practices-performance](references/best-practices-performance.md) |