Monorepo Management with PNPM
Best practices for managing large PNPM monorepos following Constructive conventions. Covers workspace configuration, dependency management, selective builds, and package organization.
When to Apply
Use this skill when:
-
Managing a multi-package repository
-
Configuring workspace dependencies
-
Setting up selective builds and filtering
-
Organizing packages in a large codebase
-
Configuring lerna versioning strategies
Workspace Configuration
pnpm-workspace.yaml
Define package locations:
packages:
- 'packages/*'
For larger projects with multiple directories:
packages:
- 'packages/*'
- 'pgpm/*'
- 'graphql/*'
- 'postgres/*'
- 'apps/*'
- 'libs/*'
Root package.json
{ "name": "my-workspace", "version": "0.0.1", "private": true, "scripts": { "build": "pnpm -r run build", "test": "pnpm -r run test", "lint": "pnpm -r run lint", "clean": "pnpm -r run clean", "deps": "pnpm up -r -i -L" } }
Key points:
-
Root is always private: true
-
Scripts use pnpm -r for recursive execution
-
deps script for interactive dependency updates
Workspace Dependencies
workspace:* Protocol
Reference internal packages:
{ "dependencies": { "my-utils": "workspace:*" } }
When published, workspace:* is replaced with the actual version.
Dependency Types
Protocol Behavior
workspace:*
Latest version in workspace
workspace:^
Compatible version range
workspace:~
Patch version range
Adding Workspace Dependencies
Add workspace dependency
pnpm add my-utils --filter my-app --workspace
Add external dependency to specific package
pnpm add lodash --filter my-app
Add dev dependency to root
pnpm add -D typescript -w
Filtering and Selective Builds
--filter Flag
Run commands on specific packages:
Single package
pnpm --filter my-app run build
Multiple packages
pnpm --filter my-app --filter my-utils run build
Glob patterns
pnpm --filter "my-*" run build
Package and its dependencies
pnpm --filter my-app... run build
Package and its dependents
pnpm --filter ...my-utils run build
Exclude packages
pnpm --filter "!my-legacy" run build
Dependency-Aware Builds
Build package and all its dependencies
pnpm --filter my-app... run build
Build only dependencies (not the package itself)
pnpm --filter "my-app^..." run build
Build dependents (packages that depend on this)
pnpm --filter "...my-utils" run build
Changed Packages
Packages changed since main
pnpm --filter "...[origin/main]" run build
Packages changed in last commit
pnpm --filter "...[HEAD~1]" run build
Package Organization
Directory Structure
Organize by domain or type:
my-workspace/ ├── packages/ # Shared utilities │ ├── utils/ │ ├── types/ │ └── config/ ├── apps/ # Applications │ ├── web/ │ └── api/ ├── libs/ # Domain libraries │ ├── auth/ │ └── database/ ├── pgpm/ # PGPM modules (if hybrid) │ └── migrations/ ├── pnpm-workspace.yaml ├── lerna.json └── package.json
Naming Conventions
Type Convention Example
Scoped packages @org/package-name
@myorg/utils
Internal packages package-name
my-utils
Apps app-name
web-app
Lerna Configuration
Independent Versioning
Each package versioned separately:
{ "$schema": "node_modules/lerna/schemas/lerna-schema.json", "version": "independent", "npmClient": "pnpm", "command": { "publish": { "allowBranch": "main", "conventionalCommits": true } } }
Best for: utility libraries with different release cycles.
Fixed Versioning
All packages share same version:
{ "$schema": "node_modules/lerna/schemas/lerna-schema.json", "version": "1.0.0", "npmClient": "pnpm", "command": { "publish": { "allowBranch": "main", "conventionalCommits": true } } }
Best for: tightly coupled packages that release together.
Versioning Commands
Version changed packages
pnpm lerna version
Version with specific bump
pnpm lerna version patch pnpm lerna version minor pnpm lerna version major
Preview without changes
pnpm lerna version --no-git-tag-version --no-push
Dependency Management
Update Dependencies
Interactive update all packages
pnpm up -r -i -L
Update specific dependency everywhere
pnpm up lodash -r
Update to latest
pnpm up lodash@latest -r
Check for Outdated
pnpm outdated -r
Dedupe Dependencies
pnpm dedupe
Build Optimization
Parallel Builds
PNPM runs in parallel by default. Control concurrency:
Limit concurrent tasks
pnpm -r --workspace-concurrency=4 run build
Topological Order
Dependencies are built first automatically:
Builds in dependency order
pnpm -r run build
Caching
Use turbo or nx for build caching in large repos:
{ "scripts": { "build": "turbo run build" } }
CI/CD Patterns
Install Dependencies
pnpm install --frozen-lockfile
Build Changed Packages
Build packages changed since main
pnpm --filter "...[origin/main]" run build
Test Changed Packages
pnpm --filter "...[origin/main]" run test
Publish Workflow
Version and publish
pnpm lerna version --yes pnpm lerna publish from-package --yes
Common Commands Reference
Command Description
pnpm install
Install all dependencies
pnpm -r run build
Build all packages
pnpm -r run test
Test all packages
pnpm --filter <pkg> run <cmd>
Run command in specific package
pnpm --filter <pkg>... run <cmd>
Run in package and dependencies
pnpm add <dep> --filter <pkg>
Add dependency to package
pnpm add <dep> -w
Add dependency to root
pnpm up -r -i -L
Interactive dependency update
pnpm lerna version
Version packages
pnpm lerna publish
Publish packages
Hybrid Workspaces
Some repos (like Constructive) have both PNPM packages and PGPM modules:
pnpm-workspace.yaml
packages:
- 'packages/*' # TypeScript packages
- 'pgpm/*' # PGPM CLI and tools
- 'postgres/*' # PostgreSQL utilities
The root may also have a pgpm.json for SQL module configuration.
Troubleshooting
Dependency Resolution Issues
Clear cache and reinstall
pnpm store prune rm -rf node_modules pnpm install
Workspace Link Issues
Check workspace links
pnpm why <package-name>
Build Order Issues
Verify dependency graph
pnpm list -r --depth=0
Best Practices
-
Keep root private: Never publish the root package
-
Use workspace protocol: Always workspace:* for internal deps
-
Consistent structure: Same directory layout across packages
-
Shared config: Extend root tsconfig.json, eslint.config.js
-
Filter in CI: Only build/test changed packages
-
Lock file: Always commit pnpm-lock.yaml
-
Dedupe regularly: Run pnpm dedupe periodically
-
Document dependencies: Clear README for each package
References
-
Related skill: pnpm-workspace for workspace setup
-
Related skill: pnpm-publishing for publishing workflow
-
Related skill: pgpm-workspace for SQL module workspaces