Learn-skills.dev shared-monorepo-pnpm-workspaces
pnpm workspace protocol, filtering, catalogs, shared dependencies, publishing, and CI/CD for monorepo management
git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/shared-monorepo-pnpm-workspaces" ~/.claude/skills/neversight-learn-skills-dev-shared-monorepo-pnpm-workspaces && rm -rf "$T"
data/skills-md/agents-inc/skills/shared-monorepo-pnpm-workspaces/SKILL.mdpnpm Workspaces for Monorepo Management
Quick Guide: pnpm 10.x workspaces for monorepo management.
defines workspace packages.pnpm-workspace.yamlprotocol for internal linking.workspace:*protocol for dependency version synchronization.catalog:for targeted commands. Shared--filterand tooling config across packages. Changesets for versioning and publishing. Strict dependency isolation by default.tsconfig
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use
protocol for ALL internal package dependencies -- never hardcode versions)workspace:*
(You MUST use
in CI -- pnpm enables this by default in CI environments)--frozen-lockfile
(You MUST define workspace packages in
at the repository root)pnpm-workspace.yaml
(You MUST put pnpm-specific settings in
-- NOT pnpm-workspace.yaml
(pnpm v10 change)).npmrc
(You MUST use
protocol when sharing dependency versions across 3+ packages)catalog:
(You MUST use
for targeted commands instead of --filter
when only specific packages changed)pnpm -r
</critical_requirements>
Auto-detection: pnpm-workspace.yaml, pnpm workspaces, workspace protocol, workspace:*, catalog:, pnpm filter, pnpm recursive, pnpm monorepo, pnpm-lock.yaml, .npmrc pnpm, pnpm catalogs, pnpm publish
When to use:
- Setting up a monorepo with pnpm workspaces
- Configuring
for workspace package discoverypnpm-workspace.yaml - Linking internal packages with
protocolworkspace:* - Synchronizing dependency versions with
protocolcatalog: - Running scripts across workspaces with
or--filter-r - Publishing packages from a pnpm workspace
- Setting up CI/CD pipelines with pnpm caching
- Sharing TypeScript, ESLint, or Prettier config across workspace packages
- Migrating from npm/yarn workspaces to pnpm
When NOT to use:
- Single-package projects with no shared code
- Projects using Bun or Yarn as their package manager
- Projects that need npm compatibility exclusively (e.g., npm workspaces)
- Task orchestration logic (use a dedicated task runner on top of pnpm)
Key patterns covered:
setup and workspace package discoverypnpm-workspace.yaml- Workspace protocol (
,workspace:*
,workspace:^
)workspace:~ - Catalogs for dependency version synchronization
- Filtering commands (
,--filter
, glob patterns, dependency selectors)-F - Running scripts across workspaces (
,-r
,--parallel
)--workspace-concurrency - Settings in
(v10: settings moved frompnpm-workspace.yaml
).npmrc - Publishing with
and changesetspublishConfig - CI/CD with GitHub Actions, caching, and
--frozen-lockfile - Shared TypeScript configuration patterns
- Monorepo directory structure conventions
Detailed resources:
- Core Setup -- pnpm-workspace.yaml, .npmrc, directory structure, settings
- Shared Packages -- workspace protocol, catalogs, TypeScript/ESLint config
- Scripts & Filtering -- --filter, recursive execution, dependency management
- Publishing & Versioning -- changesets, publishConfig, Docker
- CI/CD Pipelines -- GitHub Actions, automated release
- Quick Command Reference -- condensed lookup table
<philosophy>
Philosophy
pnpm workspaces provide strict, efficient monorepo management with content-addressable storage. Unlike npm/yarn, pnpm creates a non-flat
node_modules where packages can only access their declared dependencies -- this strictness catches missing dependency declarations early. The workspace protocol (workspace:*) ensures internal packages always link locally, while catalogs (catalog:) centralize version management to eliminate version drift.
Core principles:
- Strict by default -- packages cannot access undeclared dependencies
- Disk efficient -- content-addressable store shares identical files across projects
- Security first -- v10 blocks lifecycle scripts by default to prevent supply chain attacks
- Single lockfile -- one
at the workspace root for all packagespnpm-lock.yaml
When to use pnpm workspaces:
- Monorepos with multiple apps and shared packages
- Projects that need strict dependency isolation
- Teams that want disk-efficient dependency storage
- Publishing multiple related npm packages from one repo
When NOT to use:
- Single-package projects (no workspace benefits)
- Projects deeply invested in Yarn PnP or Berry features
- Environments where only npm is available (some CI/CD, restricted corporate setups)
<patterns>
Core Patterns
Pattern 1: Workspace Configuration (pnpm-workspace.yaml
)
pnpm-workspace.yamlThe
pnpm-workspace.yaml file at the repository root defines which directories contain workspace packages. Every pnpm workspace MUST have this file.
# pnpm-workspace.yaml packages: - "apps/*" - "packages/*"
In pnpm v10, settings moved from
.npmrc to this file:
# pnpm-workspace.yaml (v10+) packages: - "apps/*" - "packages/*" linkWorkspacePackages: true saveWorkspaceProtocol: rolling disallowWorkspaceCycles: true
Why good: Single source of truth for workspace definition and settings
See examples/core.md for full workspace setup, directory structure, and all settings.
Pattern 2: Workspace Protocol (workspace:*
)
workspace:*The workspace protocol ensures internal packages always resolve to the local workspace version, never from the registry.
| Protocol | During Development | After |
|---|---|---|
| Links to local package | Replaced with exact version (e.g., ) |
| Links to local package | Replaced with caret range (e.g., ) |
| Links to local package | Replaced with tilde range (e.g., ) |
{ "dependencies": { "@repo/ui": "workspace:*", "@repo/types": "workspace:*" } }
Why good: Guarantees local linking, pnpm refuses to resolve externally, version conversion on publish ensures correct semver
{ "dependencies": { "@repo/ui": "^1.0.0" } }
Why bad: Hardcoded versions may install from npm registry instead of local workspace, version mismatches across packages
See examples/packages.md for protocol variants, aliasing, and internal package setup.
Pattern 3: Catalogs for Version Synchronization
Catalogs define dependency versions once in
pnpm-workspace.yaml and reference them across all packages with catalog:.
# pnpm-workspace.yaml catalog: react: ^19.0.0 react-dom: ^19.0.0 typescript: ^5.7.0
{ "dependencies": { "react": "catalog:", "react-dom": "catalog:" } }
Why good: Single version source of truth, updating one line updates all packages, eliminates merge conflicts
Named catalogs support version migration:
catalogs: react18: react: ^18.3.1 react19: react: ^19.0.0
See examples/packages.md for named catalogs, strict enforcement, and full examples.
Pattern 4: Filtering Commands
--filter (or -F) restricts commands to specific packages instead of running across the entire workspace.
# Exact package pnpm --filter @repo/web-app build # Package and ALL its dependencies pnpm --filter "web-app..." build # Changed packages since main pnpm --filter "...[origin/main]" test # Exclude a package pnpm --filter "!@repo/docs" build
Why good: Targeted execution saves CI time, dependency-aware filtering ensures correct build order
# BAD: Running everything when only one package changed pnpm -r build
Why bad: Wastes CI time rebuilding all packages, no change detection
See examples/scripts.md for all filter variants, CI-optimized patterns, and graph exploration.
Pattern 5: Running Scripts Across Workspaces
# Topological order (respects dependency graph) -- use for build pnpm -r build # Parallel (ignores dependency graph) -- use for test, lint pnpm -r --parallel test # Controlled concurrency pnpm -r --workspace-concurrency 4 build
# BAD: Building in parallel when packages depend on each other pnpm -r --parallel build
Why bad: Packages may build before their dependencies finish, causing missing module failures
See examples/scripts.md for dependency management and adding dependencies.
Pattern 6: Shared TypeScript Configuration
Share TypeScript compiler options across all workspace packages using a configuration package.
{ "name": "@repo/config-typescript", "private": true, "exports": { "./base": "./tsconfig.base.json", "./react": "./tsconfig.react.json", "./node": "./tsconfig.node.json" } }
Consumer usage:
{ "extends": "@repo/config-typescript/react", "include": ["src/**/*.ts", "src/**/*.tsx"] }
Why good: Single source of truth for TypeScript settings, changes propagate to all packages
See examples/packages.md for full base/react/node configs and ESLint sharing.
Pattern 7: Publishing from Workspaces
Use
publishConfig to control what gets published and changesets for versioning.
{ "name": "@repo/ui", "main": "./src/index.ts", "publishConfig": { "main": "./dist/index.js", "types": "./dist/index.d.ts", "access": "public" } }
Why good: Source during development (fast HMR), built output on publish
pnpm changeset # Create changeset pnpm changeset version # Bump versions + changelogs pnpm publish -r # Publish to npm
See examples/publishing.md for changesets config, Docker deployment, and migration.
Pattern 8: CI/CD with GitHub Actions
- uses: pnpm/action-setup@v4 with: version: 10 - uses: actions/setup-node@v4 with: node-version: 22 cache: "pnpm" - run: pnpm install - run: pnpm --filter "...[origin/main]" build - run: pnpm --filter "...[origin/main]" test
Why good:
pnpm/action-setup@v4 handles installation, cache: "pnpm" caches the store, --frozen-lockfile is automatic in CI, change-based filtering only builds affected packages
See examples/ci.md for complete workflows including automated release with changesets.
</patterns><performance>
Performance Optimization
Install Performance:
- pnpm uses content-addressable storage -- identical files are stored once on disk
- Warm installs are up to 2x faster than npm/yarn due to hard linking
(default in CI) skips resolution for fastest installs--frozen-lockfile
Workspace Performance:
- Use
to run commands only on affected packages--filter - Use
for tasks without cross-package dependencies (test, lint)--parallel - Use
to control parallelism on resource-constrained CI--workspace-concurrency - Change-based filtering (
) skips unchanged packages--filter "...[origin/main]"
Disk Savings:
- Global store deduplicates across projects (
)pnpm store path - Typical savings: 50-70% less disk space compared to npm
CI Caching:
- uses: actions/setup-node@v4 with: node-version: 22 cache: "pnpm"
This caches the pnpm content-addressable store between runs, making subsequent installs near-instant.
</performance><decision_framework>
Decision Framework
When to Use Catalogs vs Direct Versions
Does 3+ packages use this dependency? YES -> Use catalog: protocol NO -> Direct version is fine Will this dependency version be updated frequently? YES -> Use catalog: (one-line update) NO -> Direct version is acceptable
workspace:* vs workspace:^ vs workspace:~
Are you publishing packages to npm? NO -> Use workspace:* (exact local linking, version irrelevant) YES -> Do consumers need flexible version ranges? YES -> workspace:^ (caret range on publish) NO -> workspace:* (exact version on publish)
When to Use --filter vs -r
Running a command in CI? YES -> Use --filter "...[origin/main]" (only affected packages) NO -> Running locally? ALL packages -> pnpm -r <cmd> ONE package -> pnpm --filter <name> <cmd> Is the command order-dependent (build)? YES -> Use -r (topological order) or --filter with ... NO -> Use --parallel for speed (lint, test)
pnpm vs npm vs Yarn Workspaces
Need strict dependency isolation? YES -> pnpm (non-flat node_modules by default) NO -> Any works Need disk efficiency? YES -> pnpm (content-addressable store) NO -> Any works Need zero-config PnP (no node_modules)? YES -> Yarn Berry with PnP NO -> pnpm or npm
</decision_framework>
<red_flags>
RED FLAGS
High Priority Issues:
- Hardcoded versions for internal packages instead of
(breaks local linking, installs from registry)workspace:* - Settings in
that should be in.npmrc
(silently ignored in pnpm v10)pnpm-workspace.yaml - Missing
at repo root (pnpm will not recognize workspace packages)pnpm-workspace.yaml - Running
withoutpnpm install
in CI (lockfile can mutate silently)--frozen-lockfile - Using
for build commands when packages depend on each other (race conditions)--parallel
Medium Priority Issues:
- Not using
when 3+ packages share the same dependency version (version drift)catalog: - Running
in CI instead ofpnpm -r build
(wastes time)--filter "...[origin/main]" - Missing
on internal packages (risk of accidental npm publish)private: true - Not configuring
(or the olderallowBuilds
) allowlist (blocks all install scripts in v10)onlyBuiltDependencies
Common Mistakes:
- Mixing package managers (npm install in a pnpm workspace breaks the lockfile)
- Using
as a quick fix instead of declaring missing dependencies properlyshamefullyHoist: true - Forgetting
in GitHub Actions checkout (breaks git-based change detection)fetch-depth: 0 - Running different pnpm versions locally vs CI (lockfile format incompatibility)
Gotchas & Edge Cases:
is replaced with the actual version onworkspace:*
-- this is expected behavior, not a bugpnpm publish
entries must match the dependency name exactly -- typos silently fall throughcatalog:
requires git history -- use--filter "...[origin/main]"
or at minimumfetch-depth: 0fetch-depth: 2- pnpm v10 blocks ALL lifecycle scripts by default -- use
to allowlist packages that needpnpm approve-builds
(likepostinstall
,esbuild
), or configuresharp
inallowBuildspnpm-workspace.yaml
is required forinjectWorkspacePackages: true
(or usepnpm deploy
to bypass)pnpm deploy --legacy- Circular workspace dependencies cause unpredictable script execution order -- use
disallowWorkspaceCycles: true
(default) meanssaveWorkspaceProtocol: rolling
auto-saves withpnpm add
-- this is correct behaviorworkspace:
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use
protocol for ALL internal package dependencies -- never hardcode versions)workspace:*
(You MUST use
in CI -- pnpm enables this by default in CI environments)--frozen-lockfile
(You MUST define workspace packages in
at the repository root)pnpm-workspace.yaml
(You MUST put pnpm-specific settings in
-- NOT pnpm-workspace.yaml
(pnpm v10 change)).npmrc
(You MUST use
protocol when sharing dependency versions across 3+ packages)catalog:
(You MUST use
for targeted commands instead of --filter
when only specific packages changed)pnpm -r
Failure to follow these rules will cause broken dependency resolution, version drift, missed CI caching, and security vulnerabilities.
</critical_reminders>