Claude Skill

shared-monorepo-pnpm-workspaces

pnpm workspace protocol, filtering, catalogs, shared dependencies, publishing, and CI/CD for monorepo management

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download agents-inc-skills-dist_plugins_shared-monorepo-pnpm-workspaces_skills_shared-monorepo-pnpm-workspaces-3a51ef5.zip · 21 KB
Part of agents-inc/skills — 130 skills

Install

skills CLI npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/shared-monorepo-pnpm-workspaces/skills/shared-monorepo-pnpm-workspaces
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
Git git clone https://github.com/agents-inc/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

pnpm Workspaces for Monorepo Management

Quick Guide: pnpm 10.x workspaces for monorepo management. pnpm-workspace.yaml defines workspace packages. workspace:* protocol for internal linking. catalog: protocol for dependency version synchronization. --filter for targeted commands. Shared tsconfig and tooling config across packages. Changesets for versioning and publishing. Strict dependency isolation by default.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST use workspace:* protocol for ALL internal package dependencies -- never hardcode versions)

(You MUST use --frozen-lockfile in CI -- pnpm enables this by default in CI environments)

(You MUST define workspace packages in pnpm-workspace.yaml at the repository root)

(You MUST put pnpm-specific settings in pnpm-workspace.yaml -- NOT .npmrc (pnpm v10 change))

(You MUST use catalog: protocol when sharing dependency versions across 3+ packages)

(You MUST use --filter for targeted commands instead of pnpm -r when only specific packages changed)

</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 pnpm-workspace.yaml for workspace package discovery
  • Linking internal packages with workspace:* protocol
  • Synchronizing dependency versions with catalog: protocol
  • Running scripts across workspaces with --filter or -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:

  • pnpm-workspace.yaml setup and workspace package discovery
  • Workspace protocol (workspace:*, workspace:^, workspace:~)
  • Catalogs for dependency version synchronization
  • Filtering commands (--filter, -F, glob patterns, dependency selectors)
  • Running scripts across workspaces (-r, --parallel, --workspace-concurrency)
  • Settings in pnpm-workspace.yaml (v10: settings moved from .npmrc)
  • Publishing with publishConfig and changesets
  • CI/CD with GitHub Actions, caching, and --frozen-lockfile
  • Shared TypeScript configuration patterns
  • Monorepo directory structure conventions

Detailed resources:





<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 workspace:* (breaks local linking, installs from registry)
  • Settings in .npmrc that should be in pnpm-workspace.yaml (silently ignored in pnpm v10)
  • Missing pnpm-workspace.yaml at repo root (pnpm will not recognize workspace packages)
  • Running pnpm install without --frozen-lockfile in CI (lockfile can mutate silently)
  • Using --parallel for build commands when packages depend on each other (race conditions)

Medium Priority Issues:

  • Not using catalog: when 3+ packages share the same dependency version (version drift)
  • Running pnpm -r build in CI instead of --filter "...[origin/main]" (wastes time)
  • Missing private: true on internal packages (risk of accidental npm publish)
  • Not configuring allowBuilds (or the older onlyBuiltDependencies) allowlist (blocks all install scripts in v10)

Common Mistakes:

  • Mixing package managers (npm install in a pnpm workspace breaks the lockfile)
  • Using shamefullyHoist: true as a quick fix instead of declaring missing dependencies properly
  • Forgetting fetch-depth: 0 in GitHub Actions checkout (breaks git-based change detection)
  • Running different pnpm versions locally vs CI (lockfile format incompatibility)

Gotchas & Edge Cases:

  • workspace:* is replaced with the actual version on pnpm publish -- this is expected behavior, not a bug
  • catalog: entries must match the dependency name exactly -- typos silently fall through
  • --filter "...[origin/main]" requires git history -- use fetch-depth: 0 or at minimum fetch-depth: 2
  • pnpm v10 blocks ALL lifecycle scripts by default -- use pnpm approve-builds to allowlist packages that need postinstall (like esbuild, sharp), or configure allowBuilds in pnpm-workspace.yaml
  • injectWorkspacePackages: true is required for pnpm deploy (or use pnpm deploy --legacy to bypass)
  • Circular workspace dependencies cause unpredictable script execution order -- use disallowWorkspaceCycles: true
  • saveWorkspaceProtocol: rolling (default) means pnpm add auto-saves with workspace: -- this is correct behavior

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST use workspace:* protocol for ALL internal package dependencies -- never hardcode versions)

(You MUST use --frozen-lockfile in CI -- pnpm enables this by default in CI environments)

(You MUST define workspace packages in pnpm-workspace.yaml at the repository root)

(You MUST put pnpm-specific settings in pnpm-workspace.yaml -- NOT .npmrc (pnpm v10 change))

(You MUST use catalog: protocol when sharing dependency versions across 3+ packages)

(You MUST use --filter for targeted commands instead of pnpm -r when only specific packages changed)

Failure to follow these rules will cause broken dependency resolution, version drift, missed CI caching, and security vulnerabilities.

</critical_reminders>

Files (skills)
  • examples
    • ci.md 2.8 KB
      # pnpm Workspaces -- CI/CD Pipeline Examples
      
      > GitHub Actions workflows for build, test, and automated release. Reference from [SKILL.md](../SKILL.md).
      
      **Related examples:**
      
      - [core.md](core.md) -- Workspace initialization, pnpm-workspace.yaml, settings
      - [packages.md](packages.md) -- Shared packages, TypeScript config, workspace protocol
      - [scripts.md](scripts.md) -- Running scripts, filtering, dependency management
      - [publishing.md](publishing.md) -- Changesets, versioning, publishing, Docker
      
      ---
      
      ## GitHub Actions: Build + Test
      
      ```yaml
      name: CI
      
      on:
        push:
          branches: [main]
        pull_request:
          branches: [main]
      
      jobs:
        ci:
          runs-on: ubuntu-latest
          steps:
            - name: Checkout
              uses: actions/checkout@v4
              with:
                fetch-depth: 0
      
            - name: Setup pnpm
              uses: pnpm/action-setup@v4
              with:
                version: 10
      
            - name: Setup Node.js
              uses: actions/setup-node@v4
              with:
                node-version: 22
                cache: "pnpm"
      
            - name: Install
              run: pnpm install
      
            - name: Typecheck
              run: pnpm -r --parallel typecheck
      
            - name: Lint
              run: pnpm -r --parallel lint
      
            - name: Build (affected only)
              run: pnpm --filter "...[origin/main]" build
      
            - name: Test (affected only)
              run: pnpm --filter "...[origin/main]" test
      ```
      
      **Why good:** `pnpm/action-setup@v4` handles pnpm installation, `cache: "pnpm"` in setup-node caches the store, `--frozen-lockfile` is automatic in CI (prevents lockfile mutations), `--filter "...[origin/main]"` only builds/tests changed packages, `fetch-depth: 0` enables git-based change detection
      
      ---
      
      ## GitHub Actions: Automated Release with Changesets
      
      ```yaml
      name: Release
      
      on:
        push:
          branches: [main]
      
      concurrency: ${{ github.workflow }}-${{ github.ref }}
      
      permissions:
        contents: write
        pull-requests: write
      
      jobs:
        release:
          runs-on: ubuntu-latest
          steps:
            - name: Checkout
              uses: actions/checkout@v4
      
            - name: Setup pnpm
              uses: pnpm/action-setup@v4
              with:
                version: 10
      
            - name: Setup Node.js
              uses: actions/setup-node@v4
              with:
                node-version: 22
                cache: "pnpm"
                registry-url: "https://registry.npmjs.org"
      
            - name: Install
              run: pnpm install
      
            - name: Build
              run: pnpm -r build
      
            - name: Create Release PR or Publish
              uses: changesets/action@v1
              with:
                version: pnpm changeset version
                publish: pnpm publish -r --access=public
              env:
                GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
                NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
      ```
      
      **Why good:** `concurrency` prevents duplicate runs, `permissions` grants write access for PR creation, `changesets/action` auto-creates version bump PRs and publishes on merge, pnpm store is cached between runs
      
    • core.md 4.7 KB
      # pnpm Workspaces -- Core Examples
      
      > Workspace initialization and configuration examples. Reference from [SKILL.md](../SKILL.md).
      
      **Related examples:**
      
      - [packages.md](packages.md) -- Shared packages, TypeScript config, workspace protocol
      - [scripts.md](scripts.md) -- Running scripts, filtering, dependency management
      - [publishing.md](publishing.md) -- Changesets, versioning, publishing
      - [ci.md](ci.md) -- CI/CD pipelines, GitHub Actions, Docker
      
      ---
      
      ## Minimal Workspace
      
      ```yaml
      # pnpm-workspace.yaml
      packages:
        - "apps/*"
        - "packages/*"
      ```
      
      ```json
      {
        "name": "my-monorepo",
        "private": true,
        "packageManager": "pnpm@10.32.1",
        "scripts": {
          "build": "pnpm -r build",
          "dev": "pnpm -r --parallel dev",
          "test": "pnpm -r test",
          "lint": "pnpm -r --parallel lint",
          "clean": "pnpm -r exec rm -rf dist node_modules"
        }
      }
      ```
      
      **Why good:** `private: true` prevents accidental root publish, `packageManager` field ensures consistent pnpm version, glob patterns auto-discover packages
      
      ---
      
      ## Full Workspace with Catalogs and Settings
      
      ```yaml
      # pnpm-workspace.yaml
      packages:
        - "apps/*"
        - "packages/*"
        - "tools/*"
      
      # Dependency version catalog
      catalog:
        react: ^19.0.0
        react-dom: ^19.0.0
        typescript: ^5.7.0
        vitest: ^3.0.0
        zod: ^3.24.0
        "@changesets/cli": ^2.27.0
      
      # Workspace settings (moved from .npmrc in v10)
      linkWorkspacePackages: true
      saveWorkspaceProtocol: rolling
      disallowWorkspaceCycles: true
      strictPeerDependencies: true
      
      # Security: allowlist packages that can run install scripts
      # Use allowBuilds (preferred) or onlyBuiltDependencies (legacy)
      allowBuilds:
        esbuild: true
        sharp: true
        "@swc/core": true
      ```
      
      **Why good:** Single file defines workspace structure, dependency versions, and pnpm settings. `disallowWorkspaceCycles: true` catches circular dependencies at install time. `allowBuilds` explicitly trusts only necessary packages to run install scripts.
      
      ---
      
      ## v10 Settings Migration
      
      In pnpm v10, most settings moved from `.npmrc` to `pnpm-workspace.yaml`. Only auth and registry settings remain in `.npmrc`.
      
      ```yaml
      # pnpm-workspace.yaml (pnpm v10+)
      packages:
        - "apps/*"
        - "packages/*"
      
      # Settings that were previously in .npmrc
      linkWorkspacePackages: true
      saveWorkspaceProtocol: rolling
      shamefullyHoist: false
      strictPeerDependencies: true
      ```
      
      ```ini
      # .npmrc (pnpm v10+ -- ONLY auth and registry settings)
      //registry.npmjs.org/:_authToken=${NPM_TOKEN}
      @myorg:registry=https://npm.pkg.github.com
      //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
      ```
      
      **Why good:** Single source of truth for pnpm configuration, `.npmrc` only contains secrets and registry URLs, settings are version-controlled alongside workspace definition
      
      ```yaml
      # BAD: Settings in .npmrc (pnpm v10+)
      # These will be IGNORED by pnpm v10
      # shamefully-hoist=true
      # link-workspace-packages=true
      ```
      
      **Why bad:** pnpm v10 no longer reads non-auth settings from `.npmrc`, settings are silently ignored leading to unexpected behavior
      
      ---
      
      ## Recommended Directory Structure
      
      ```
      my-monorepo/
        apps/
          web/                    # Web application
            package.json
            tsconfig.json         # Extends shared config
          api/                    # API server
            package.json
            tsconfig.json
        packages/
          ui/                     # Shared UI components
            package.json
            tsconfig.json
            src/
              index.ts            # Barrel file with named exports
          types/                  # Shared TypeScript types
            package.json
            tsconfig.json
            src/
              index.ts
          config-typescript/      # Shared tsconfig base
            package.json
            tsconfig.base.json
            tsconfig.react.json
          config-eslint/          # Shared ESLint config
            package.json
            index.js
        pnpm-workspace.yaml      # Workspace definition + settings
        package.json              # Root package.json (workspace scripts)
        pnpm-lock.yaml            # Single lockfile for all packages
        .npmrc                    # Auth and registry settings only (v10+)
      ```
      
      ---
      
      ## Root package.json
      
      ```json
      {
        "name": "my-monorepo",
        "private": true,
        "scripts": {
          "build": "pnpm -r build",
          "dev": "pnpm -r --parallel dev",
          "test": "pnpm -r test",
          "lint": "pnpm -r --parallel lint",
          "clean": "pnpm -r exec rm -rf dist node_modules",
          "changeset": "changeset",
          "version-packages": "changeset version && pnpm install",
          "release": "pnpm build && pnpm publish -r --access=public"
        },
        "devDependencies": {
          "@changesets/cli": "catalog:",
          "typescript": "catalog:"
        }
      }
      ```
      
      **Why good:** Root scripts provide workspace-wide commands, `private: true` prevents accidental root publish, shared devDependencies hoisted to root reduce duplication
      
      See [reference.md](../reference.md) for a complete settings lookup table.
      
    • packages.md 7.1 KB
      # pnpm Workspaces -- Shared Packages Examples
      
      > Workspace protocol, internal packages, and shared configuration examples. Reference from [SKILL.md](../SKILL.md).
      
      **Related examples:**
      
      - [core.md](core.md) -- Workspace initialization, pnpm-workspace.yaml, settings
      - [scripts.md](scripts.md) -- Running scripts, filtering, dependency management
      - [publishing.md](publishing.md) -- Changesets, versioning, publishing
      - [ci.md](ci.md) -- CI/CD pipelines, GitHub Actions, Docker
      
      ---
      
      ## Workspace Protocol
      
      ### Good: Internal Dependencies with workspace:\*
      
      ```json
      {
        "name": "@repo/web-app",
        "private": true,
        "dependencies": {
          "@repo/ui": "workspace:*",
          "@repo/types": "workspace:*",
          "@repo/api-client": "workspace:*",
          "react": "catalog:",
          "react-dom": "catalog:"
        },
        "devDependencies": {
          "@repo/config-typescript": "workspace:*",
          "@repo/config-eslint": "workspace:*",
          "typescript": "catalog:"
        }
      }
      ```
      
      **Why good:** `workspace:*` guarantees local linking for internal packages, `catalog:` centralizes external dependency versions, clear separation of internal vs external dependencies
      
      ### Bad: Hardcoded Versions for Internal Packages
      
      ```json
      {
        "name": "@repo/web-app",
        "dependencies": {
          "@repo/ui": "^1.0.0",
          "@repo/types": "1.2.3",
          "react": "^19.0.0"
        }
      }
      ```
      
      **Why bad:** Hardcoded internal versions may pull from npm instead of local workspace, different packages may have different versions of the same internal dependency, manual version bumps required on every change
      
      ### Publishing: workspace:^ for Flexible Ranges
      
      ```json
      {
        "name": "@repo/ui",
        "version": "2.1.0",
        "dependencies": {
          "@repo/types": "workspace:^"
        }
      }
      ```
      
      After `pnpm publish`:
      
      ```json
      {
        "name": "@repo/ui",
        "version": "2.1.0",
        "dependencies": {
          "@repo/types": "^2.1.0"
        }
      }
      ```
      
      **When to use:** Publishing packages to npm where consumers need semver flexibility
      
      ### Aliasing
      
      ```json
      {
        "dependencies": {
          "ui-v2": "workspace:@repo/ui@*"
        }
      }
      ```
      
      **When to use:** Migrating between package versions in the same workspace, running two versions of an internal package side by side
      
      ---
      
      ## Catalog Examples
      
      ### Default Catalog
      
      ```yaml
      # pnpm-workspace.yaml
      catalog:
        react: ^19.0.0
        react-dom: ^19.0.0
        typescript: ^5.7.0
        vitest: ^3.0.0
        zod: ^3.24.0
      ```
      
      ```json
      {
        "name": "@repo/web-app",
        "dependencies": {
          "react": "catalog:",
          "react-dom": "catalog:",
          "zod": "catalog:"
        },
        "devDependencies": {
          "typescript": "catalog:",
          "vitest": "catalog:"
        }
      }
      ```
      
      ### Named Catalogs for Version Migration
      
      ```yaml
      # pnpm-workspace.yaml
      catalogs:
        react18:
          react: ^18.3.1
          react-dom: ^18.3.1
          "@types/react": ^18.3.0
        react19:
          react: ^19.0.0
          react-dom: ^19.0.0
          "@types/react": ^19.0.0
      ```
      
      ```json
      {
        "name": "@repo/legacy-dashboard",
        "dependencies": {
          "react": "catalog:react18",
          "react-dom": "catalog:react18"
        }
      }
      ```
      
      ```json
      {
        "name": "@repo/new-app",
        "dependencies": {
          "react": "catalog:react19",
          "react-dom": "catalog:react19"
        }
      }
      ```
      
      **Why good:** Named catalogs allow gradual migration between major versions, each app declares its target version explicitly, centralized management of both version tracks
      
      ### Strict Catalog Enforcement
      
      ```yaml
      # pnpm-workspace.yaml
      catalogMode: strict
      catalog:
        react: ^19.0.0
        typescript: ^5.7.0
      ```
      
      With `catalogMode: strict`, this will **fail** on `pnpm install`:
      
      ```json
      {
        "dependencies": {
          "react": "^18.0.0"
        }
      }
      ```
      
      Error: Package "react" must use `catalog:` protocol when `catalogMode` is `strict`.
      
      **When to use:** Enforce consistent versions across all packages with no exceptions
      
      ---
      
      ## Shared TypeScript Configuration
      
      ### Configuration Package
      
      ```
      packages/config-typescript/
        package.json
        tsconfig.base.json
        tsconfig.react.json
        tsconfig.node.json
      ```
      
      ```json
      {
        "name": "@repo/config-typescript",
        "private": true,
        "exports": {
          "./base": "./tsconfig.base.json",
          "./react": "./tsconfig.react.json",
          "./node": "./tsconfig.node.json"
        }
      }
      ```
      
      ### Base Configuration
      
      ```json
      {
        "$schema": "https://json.schemastore.org/tsconfig",
        "compilerOptions": {
          "strict": true,
          "target": "ES2022",
          "module": "ESNext",
          "moduleResolution": "bundler",
          "esModuleInterop": true,
          "isolatedModules": true,
          "skipLibCheck": true,
          "declaration": true,
          "declarationMap": true,
          "sourceMap": true,
          "forceConsistentCasingInFileNames": true,
          "resolveJsonModule": true,
          "verbatimModuleSyntax": true,
          "noUncheckedIndexedAccess": true
        }
      }
      ```
      
      ### React Configuration (extends base)
      
      ```json
      {
        "extends": "./tsconfig.base.json",
        "compilerOptions": {
          "jsx": "react-jsx",
          "lib": ["DOM", "DOM.Iterable", "ES2022"],
          "noEmit": true
        }
      }
      ```
      
      ### Node.js Configuration (extends base)
      
      ```json
      {
        "extends": "./tsconfig.base.json",
        "compilerOptions": {
          "module": "Node16",
          "moduleResolution": "Node16",
          "lib": ["ES2022"],
          "outDir": "./dist",
          "rootDir": "./src"
        }
      }
      ```
      
      ### Consumer Usage
      
      ```json
      {
        "extends": "@repo/config-typescript/react",
        "compilerOptions": {
          "baseUrl": ".",
          "paths": {
            "@/*": ["./src/*"]
          }
        },
        "include": ["src/**/*.ts", "src/**/*.tsx"],
        "exclude": ["node_modules", "dist"]
      }
      ```
      
      **Why good:** Consistent TypeScript settings across all packages, base config with strict options, variants for different environments (browser vs node), consumer packages only add project-specific paths
      
      ---
      
      ## Shared ESLint Configuration
      
      ### Configuration Package
      
      ```json
      {
        "name": "@repo/config-eslint",
        "private": true,
        "dependencies": {
          "@repo/config-typescript": "workspace:*"
        },
        "exports": {
          ".": "./index.js",
          "./react": "./react.js"
        }
      }
      ```
      
      ### Consumer Usage (flat config)
      
      ```js
      // apps/web/eslint.config.js
      import baseConfig from "@repo/config-eslint";
      import reactConfig from "@repo/config-eslint/react";
      
      export default [...baseConfig, ...reactConfig];
      ```
      
      ---
      
      ## Internal Package Examples
      
      ### Shared UI Package
      
      ```json
      {
        "name": "@repo/ui",
        "version": "1.0.0",
        "private": true,
        "sideEffects": false,
        "exports": {
          ".": {
            "types": "./src/index.ts",
            "default": "./src/index.ts"
          },
          "./button": {
            "types": "./src/components/button/index.ts",
            "default": "./src/components/button/index.ts"
          }
        },
        "dependencies": {
          "@repo/types": "workspace:*"
        },
        "peerDependencies": {
          "react": "catalog:",
          "react-dom": "catalog:"
        },
        "devDependencies": {
          "@repo/config-typescript": "workspace:*",
          "typescript": "catalog:"
        }
      }
      ```
      
      **Why good:** `exports` defines explicit public API (prevents internal path imports), `sideEffects: false` enables tree-shaking, React in `peerDependencies` (not dependencies) prevents version duplication, `private: true` prevents accidental npm publish, source exports during development for fast HMR
      
      ### Shared Types Package
      
      ```json
      {
        "name": "@repo/types",
        "version": "1.0.0",
        "private": true,
        "sideEffects": false,
        "exports": {
          ".": {
            "types": "./src/index.ts",
            "default": "./src/index.ts"
          }
        },
        "devDependencies": {
          "@repo/config-typescript": "workspace:*",
          "typescript": "catalog:"
        }
      }
      ```
      
    • pnpm-workspaces.md 15.3 KB
      # pnpm Workspaces - Practical Examples
      
      > Practical examples for pnpm workspace setup, filtering, catalogs, publishing, CI pipelines, and shared configuration. See [../SKILL.md](../SKILL.md) for core concepts and [../reference.md](../reference.md) for quick command reference.
      
      ---
      
      ## Complete Workspace Setup
      
      ### Minimal Workspace
      
      ```yaml
      # pnpm-workspace.yaml
      packages:
        - "apps/*"
        - "packages/*"
      ```
      
      ```json
      {
        "name": "my-monorepo",
        "private": true,
        "packageManager": "pnpm@10.32.1",
        "scripts": {
          "build": "pnpm -r build",
          "dev": "pnpm -r --parallel dev",
          "test": "pnpm -r test",
          "lint": "pnpm -r --parallel lint",
          "clean": "pnpm -r exec rm -rf dist node_modules"
        }
      }
      ```
      
      ### Full Workspace with Catalogs and Settings
      
      ```yaml
      # pnpm-workspace.yaml
      packages:
        - "apps/*"
        - "packages/*"
        - "tools/*"
      
      # Dependency version catalog
      catalog:
        react: ^19.0.0
        react-dom: ^19.0.0
        typescript: ^5.7.0
        vitest: ^3.0.0
        zod: ^3.24.0
        "@changesets/cli": ^2.27.0
      
      # Workspace settings (moved from .npmrc in v10)
      linkWorkspacePackages: true
      saveWorkspaceProtocol: rolling
      disallowWorkspaceCycles: true
      strictPeerDependencies: true
      
      # Security: allowlist packages that need install scripts
      onlyBuiltDependencies:
        - esbuild
        - sharp
        - "@swc/core"
      ```
      
      **Why good:** Single file defines workspace structure, dependency versions, and pnpm settings. `disallowWorkspaceCycles: true` catches circular dependencies at install time. `onlyBuiltDependencies` explicitly trusts only necessary packages.
      
      ---
      
      ## Workspace Protocol Examples
      
      ### Good: Internal Dependencies with workspace:\*
      
      ```json
      {
        "name": "@repo/web-app",
        "private": true,
        "dependencies": {
          "@repo/ui": "workspace:*",
          "@repo/types": "workspace:*",
          "@repo/api-client": "workspace:*",
          "react": "catalog:",
          "react-dom": "catalog:"
        },
        "devDependencies": {
          "@repo/config-typescript": "workspace:*",
          "@repo/config-eslint": "workspace:*",
          "typescript": "catalog:"
        }
      }
      ```
      
      **Why good:** `workspace:*` guarantees local linking for internal packages, `catalog:` centralizes external dependency versions, clear separation of internal vs external dependencies
      
      ### Bad: Hardcoded Versions for Internal Packages
      
      ```json
      {
        "name": "@repo/web-app",
        "dependencies": {
          "@repo/ui": "^1.0.0",
          "@repo/types": "1.2.3",
          "react": "^19.0.0"
        }
      }
      ```
      
      **Why bad:** Hardcoded internal versions may pull from npm instead of local workspace, different packages may have different versions of the same internal dependency, manual version bumps required on every change
      
      ### Publishing: workspace:^ for Flexible Ranges
      
      ```json
      {
        "name": "@repo/ui",
        "version": "2.1.0",
        "dependencies": {
          "@repo/types": "workspace:^"
        }
      }
      ```
      
      After `pnpm publish`:
      
      ```json
      {
        "name": "@repo/ui",
        "version": "2.1.0",
        "dependencies": {
          "@repo/types": "^2.1.0"
        }
      }
      ```
      
      **When to use:** Publishing packages to npm where consumers need semver flexibility
      
      ---
      
      ## Catalog Examples
      
      ### Default Catalog
      
      ```yaml
      # pnpm-workspace.yaml
      catalog:
        react: ^19.0.0
        react-dom: ^19.0.0
        next: ^15.0.0
        typescript: ^5.7.0
        vitest: ^3.0.0
        zod: ^3.24.0
        drizzle-orm: ^0.38.0
        hono: ^4.7.0
      ```
      
      ```json
      {
        "name": "@repo/web-app",
        "dependencies": {
          "react": "catalog:",
          "react-dom": "catalog:",
          "next": "catalog:",
          "zod": "catalog:"
        },
        "devDependencies": {
          "typescript": "catalog:",
          "vitest": "catalog:"
        }
      }
      ```
      
      ### Named Catalogs for Version Migration
      
      ```yaml
      # pnpm-workspace.yaml
      catalogs:
        react18:
          react: ^18.3.1
          react-dom: ^18.3.1
          "@types/react": ^18.3.0
        react19:
          react: ^19.0.0
          react-dom: ^19.0.0
          "@types/react": ^19.0.0
      ```
      
      ```json
      {
        "name": "@repo/legacy-dashboard",
        "dependencies": {
          "react": "catalog:react18",
          "react-dom": "catalog:react18"
        }
      }
      ```
      
      ```json
      {
        "name": "@repo/new-app",
        "dependencies": {
          "react": "catalog:react19",
          "react-dom": "catalog:react19"
        }
      }
      ```
      
      **Why good:** Named catalogs allow gradual migration between major versions, each app declares its target version explicitly, centralized management of both version tracks
      
      ### Strict Catalog Enforcement
      
      ```yaml
      # pnpm-workspace.yaml
      catalogMode: strict
      catalog:
        react: ^19.0.0
        typescript: ^5.7.0
      ```
      
      With `catalogMode: strict`, this will **fail** on `pnpm install`:
      
      ```json
      {
        "dependencies": {
          "react": "^18.0.0"
        }
      }
      ```
      
      Error: Package "react" must use `catalog:` protocol when `catalogMode` is `strict`.
      
      **When to use:** Enforce consistent versions across all packages with no exceptions
      
      ---
      
      ## Filtering Command Examples
      
      ### Common Development Workflows
      
      ```bash
      # Start dev server for a specific app
      pnpm --filter @repo/web-app dev
      
      # Build a package and all its dependencies
      pnpm --filter "@repo/web-app..." build
      
      # Run tests for changed packages since main branch
      pnpm --filter "...[origin/main]" test
      
      # Lint everything except docs
      pnpm --filter "!@repo/docs" --filter "@repo/*" lint
      
      # Add a dependency to a specific package
      pnpm --filter @repo/api-server add hono
      
      # Add a workspace dependency
      pnpm --filter @repo/web-app add @repo/ui --workspace
      
      # Remove a dependency from a package
      pnpm --filter @repo/web-app remove lodash
      ```
      
      ### CI-Optimized Filtering
      
      ```bash
      # Build only changed packages and their dependents
      pnpm --filter "...[origin/main]..." build
      
      # Test changed packages (ignore README changes)
      pnpm --filter "...[origin/main]" \
        --changed-files-ignore-pattern="**/*.md" \
        test
      
      # Type-check only packages in the packages/ directory
      pnpm --filter "./packages/**" typecheck
      
      # Build with failure on no matches (catches filter typos)
      pnpm --filter "@repo/web-app" --fail-if-no-match build
      ```
      
      ### Dependency Graph Exploration
      
      ```bash
      # See what depends on @repo/types
      pnpm --filter "...@repo/types" list --depth 0
      
      # See what @repo/web-app depends on
      pnpm --filter "@repo/web-app..." list --depth 0
      
      # List all workspace packages
      pnpm -r list --depth -1
      ```
      
      ---
      
      ## Shared TypeScript Configuration
      
      ### Configuration Package
      
      ```
      packages/config-typescript/
        package.json
        tsconfig.base.json
        tsconfig.react.json
        tsconfig.node.json
      ```
      
      ```json
      {
        "name": "@repo/config-typescript",
        "private": true,
        "exports": {
          "./base": "./tsconfig.base.json",
          "./react": "./tsconfig.react.json",
          "./node": "./tsconfig.node.json"
        }
      }
      ```
      
      ### Base Configuration
      
      ```json
      {
        "$schema": "https://json.schemastore.org/tsconfig",
        "compilerOptions": {
          "strict": true,
          "target": "ES2022",
          "module": "ESNext",
          "moduleResolution": "bundler",
          "esModuleInterop": true,
          "isolatedModules": true,
          "skipLibCheck": true,
          "declaration": true,
          "declarationMap": true,
          "sourceMap": true,
          "forceConsistentCasingInFileNames": true,
          "resolveJsonModule": true,
          "verbatimModuleSyntax": true,
          "noUncheckedIndexedAccess": true
        }
      }
      ```
      
      ### React Configuration (extends base)
      
      ```json
      {
        "extends": "./tsconfig.base.json",
        "compilerOptions": {
          "jsx": "react-jsx",
          "lib": ["DOM", "DOM.Iterable", "ES2022"],
          "noEmit": true
        }
      }
      ```
      
      ### Node.js Configuration (extends base)
      
      ```json
      {
        "extends": "./tsconfig.base.json",
        "compilerOptions": {
          "module": "Node16",
          "moduleResolution": "Node16",
          "lib": ["ES2022"],
          "outDir": "./dist",
          "rootDir": "./src"
        }
      }
      ```
      
      ### Consumer Usage
      
      ```json
      {
        "extends": "@repo/config-typescript/react",
        "compilerOptions": {
          "baseUrl": ".",
          "paths": {
            "@/*": ["./src/*"]
          }
        },
        "include": ["src/**/*.ts", "src/**/*.tsx"],
        "exclude": ["node_modules", "dist"]
      }
      ```
      
      **Why good:** Consistent TypeScript settings across all packages, base config with strict options, variants for different environments (browser vs node), consumer packages only add project-specific paths
      
      ---
      
      ## Shared ESLint Configuration
      
      ### Configuration Package
      
      ```json
      {
        "name": "@repo/config-eslint",
        "private": true,
        "dependencies": {
          "@repo/config-typescript": "workspace:*"
        },
        "exports": {
          ".": "./index.js",
          "./react": "./react.js"
        }
      }
      ```
      
      ### Consumer Usage (flat config)
      
      ```js
      // apps/web/eslint.config.js
      import baseConfig from "@repo/config-eslint";
      import reactConfig from "@repo/config-eslint/react";
      
      export default [...baseConfig, ...reactConfig];
      ```
      
      ---
      
      ## Publishing Workflow with Changesets
      
      ### Setup
      
      ```bash
      # Install changesets in workspace root
      pnpm add -Dw @changesets/cli
      
      # Initialize changesets
      pnpm changeset init
      ```
      
      ### Changesets Configuration
      
      ```json
      {
        "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
        "changelog": "@changesets/cli/changelog",
        "commit": false,
        "fixed": [],
        "linked": [["@repo/ui", "@repo/types"]],
        "access": "public",
        "baseBranch": "main",
        "updateInternalDependencies": "patch",
        "ignore": ["@repo/web-app", "@repo/api-server"]
      }
      ```
      
      **Key options:**
      
      - `linked`: Packages that should always share the same version
      - `access`: `"public"` for scoped packages on npm
      - `ignore`: Private packages that should not be versioned/published
      - `updateInternalDependencies`: How to bump internal dep references
      
      ### Development Workflow
      
      ```bash
      # 1. Make code changes across packages
      
      # 2. Create a changeset describing the change
      pnpm changeset
      # Interactive prompt: select packages, bump type, description
      
      # 3. Commit the changeset file
      # .changeset/cool-dogs-dance.md gets committed with your PR
      
      # 4. When ready to release (usually automated):
      pnpm changeset version   # Bumps versions, generates changelogs
      pnpm install              # Update lockfile
      pnpm publish -r           # Publish to npm
      ```
      
      ### Root package.json Scripts
      
      ```json
      {
        "scripts": {
          "changeset": "changeset",
          "version-packages": "changeset version && pnpm install",
          "release": "pnpm build && pnpm publish -r --access=public"
        }
      }
      ```
      
      ---
      
      ## CI/CD Pipeline Examples
      
      ### GitHub Actions: Build + Test
      
      ```yaml
      name: CI
      
      on:
        push:
          branches: [main]
        pull_request:
          branches: [main]
      
      jobs:
        ci:
          runs-on: ubuntu-latest
          steps:
            - name: Checkout
              uses: actions/checkout@v4
              with:
                fetch-depth: 0
      
            - name: Setup pnpm
              uses: pnpm/action-setup@v4
              with:
                version: 10
      
            - name: Setup Node.js
              uses: actions/setup-node@v4
              with:
                node-version: 22
                cache: "pnpm"
      
            - name: Install
              run: pnpm install
      
            - name: Typecheck
              run: pnpm -r --parallel typecheck
      
            - name: Lint
              run: pnpm -r --parallel lint
      
            - name: Build (affected only)
              run: pnpm --filter "...[origin/main]" build
      
            - name: Test (affected only)
              run: pnpm --filter "...[origin/main]" test
      ```
      
      ### GitHub Actions: Automated Release with Changesets
      
      ```yaml
      name: Release
      
      on:
        push:
          branches: [main]
      
      concurrency: ${{ github.workflow }}-${{ github.ref }}
      
      permissions:
        contents: write
        pull-requests: write
      
      jobs:
        release:
          runs-on: ubuntu-latest
          steps:
            - name: Checkout
              uses: actions/checkout@v4
      
            - name: Setup pnpm
              uses: pnpm/action-setup@v4
              with:
                version: 10
      
            - name: Setup Node.js
              uses: actions/setup-node@v4
              with:
                node-version: 22
                cache: "pnpm"
                registry-url: "https://registry.npmjs.org"
      
            - name: Install
              run: pnpm install
      
            - name: Build
              run: pnpm -r build
      
            - name: Create Release PR or Publish
              uses: changesets/action@v1
              with:
                version: pnpm changeset version
                publish: pnpm publish -r --access=public
              env:
                GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
                NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
      ```
      
      **Why good:** `concurrency` prevents duplicate runs, `permissions` grants write access for PR creation, `changesets/action` auto-creates version bump PRs and publishes on merge, pnpm store is cached between runs
      
      ---
      
      ## Internal Package Setup Example
      
      ### Shared UI Package
      
      ```json
      {
        "name": "@repo/ui",
        "version": "1.0.0",
        "private": true,
        "sideEffects": false,
        "exports": {
          ".": {
            "types": "./src/index.ts",
            "default": "./src/index.ts"
          },
          "./button": {
            "types": "./src/components/button/index.ts",
            "default": "./src/components/button/index.ts"
          }
        },
        "dependencies": {
          "@repo/types": "workspace:*"
        },
        "peerDependencies": {
          "react": "catalog:",
          "react-dom": "catalog:"
        },
        "devDependencies": {
          "@repo/config-typescript": "workspace:*",
          "typescript": "catalog:"
        }
      }
      ```
      
      **Why good:** `exports` defines explicit public API (prevents internal path imports), `sideEffects: false` enables tree-shaking, React in `peerDependencies` (not dependencies) prevents version duplication, `private: true` prevents accidental npm publish, source exports during development for fast HMR
      
      ### Shared Types Package
      
      ```json
      {
        "name": "@repo/types",
        "version": "1.0.0",
        "private": true,
        "sideEffects": false,
        "exports": {
          ".": {
            "types": "./src/index.ts",
            "default": "./src/index.ts"
          }
        },
        "devDependencies": {
          "@repo/config-typescript": "workspace:*",
          "typescript": "catalog:"
        }
      }
      ```
      
      ---
      
      ## Docker with pnpm Workspaces
      
      ### Multi-Stage Dockerfile
      
      ```dockerfile
      # Stage 1: Install dependencies
      FROM node:22-alpine AS deps
      RUN corepack enable pnpm
      
      WORKDIR /app
      COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
      COPY apps/api/package.json ./apps/api/
      COPY packages/types/package.json ./packages/types/
      
      RUN pnpm install --frozen-lockfile
      
      # Stage 2: Build
      FROM node:22-alpine AS builder
      RUN corepack enable pnpm
      
      WORKDIR /app
      COPY --from=deps /app/node_modules ./node_modules
      COPY . .
      RUN pnpm --filter @repo/api build
      
      # Stage 3: Production
      FROM node:22-alpine AS runner
      RUN corepack enable pnpm
      
      WORKDIR /app
      COPY --from=builder /app/apps/api/dist ./dist
      COPY --from=builder /app/apps/api/package.json ./
      
      ENV NODE_ENV=production
      CMD ["node", "dist/index.js"]
      ```
      
      ### Using pnpm deploy (Recommended)
      
      ```yaml
      # pnpm-workspace.yaml
      injectWorkspacePackages: true # Required for pnpm deploy
      ```
      
      ```dockerfile
      FROM node:22-alpine AS builder
      RUN corepack enable pnpm
      
      WORKDIR /app
      COPY . .
      RUN pnpm install --frozen-lockfile
      RUN pnpm --filter @repo/api build
      RUN pnpm --filter @repo/api deploy ./pruned
      
      FROM node:22-alpine AS runner
      WORKDIR /app
      COPY --from=builder /app/pruned .
      ENV NODE_ENV=production
      CMD ["node", "dist/index.js"]
      ```
      
      **Why good:** `pnpm deploy` creates a standalone directory with only the production dependencies for a specific package, dramatically smaller Docker images, `injectWorkspacePackages: true` required for deploy to resolve workspace deps correctly
      
      ---
      
      ## Migration from npm/yarn
      
      ### Step-by-Step
      
      ```bash
      # 1. Remove old lockfile and node_modules
      rm -rf node_modules package-lock.json yarn.lock
      
      # 2. Create pnpm-workspace.yaml
      cat > pnpm-workspace.yaml << 'EOF'
      packages:
        - "apps/*"
        - "packages/*"
      EOF
      
      # 3. Set packageManager in root package.json
      # "packageManager": "pnpm@10.32.1"
      
      # 4. Install with pnpm
      pnpm install
      
      # 5. Convert internal dependencies to workspace:*
      # In each package.json, change "@repo/ui": "^1.0.0" to "@repo/ui": "workspace:*"
      
      # 6. Re-install to generate proper lockfile
      pnpm install
      ```
      
      **Key differences from npm/yarn:**
      
      - pnpm creates strict `node_modules` (no phantom dependencies)
      - You may need to add missing dependency declarations that npm/yarn silently resolved
      - `shamefullyHoist: true` can ease migration but should be removed once deps are fixed
      
    • publishing.md 4.9 KB
      # pnpm Workspaces -- Publishing & Versioning Examples
      
      > Changesets, publishConfig, versioning, and Docker deployment examples. Reference from [SKILL.md](../SKILL.md).
      
      **Related examples:**
      
      - [core.md](core.md) -- Workspace initialization, pnpm-workspace.yaml, settings
      - [packages.md](packages.md) -- Shared packages, TypeScript config, workspace protocol
      - [scripts.md](scripts.md) -- Running scripts, filtering, dependency management
      - [ci.md](ci.md) -- CI/CD pipelines, GitHub Actions, Docker
      
      ---
      
      ## publishConfig
      
      ```json
      {
        "name": "@repo/ui",
        "version": "1.0.0",
        "private": false,
        "main": "./src/index.ts",
        "publishConfig": {
          "main": "./dist/index.js",
          "types": "./dist/index.d.ts",
          "exports": {
            ".": {
              "types": "./dist/index.d.ts",
              "import": "./dist/index.js"
            }
          },
          "access": "public"
        },
        "scripts": {
          "build": "tsup src/index.ts --format esm --dts"
        }
      }
      ```
      
      **Why good:** `main` points to source during development (fast HMR), `publishConfig.main` points to built output on publish, `access: public` required for scoped packages on npm
      
      ---
      
      ## Changesets Setup
      
      ```bash
      # Install changesets in workspace root
      pnpm add -Dw @changesets/cli
      
      # Initialize changesets
      pnpm changeset init
      ```
      
      ### Configuration
      
      ```json
      {
        "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
        "changelog": "@changesets/cli/changelog",
        "commit": false,
        "fixed": [],
        "linked": [["@repo/ui", "@repo/types"]],
        "access": "public",
        "baseBranch": "main",
        "updateInternalDependencies": "patch",
        "ignore": ["@repo/web-app", "@repo/api-server"]
      }
      ```
      
      **Key options:**
      
      - `linked`: Packages that should always share the same version
      - `access`: `"public"` for scoped packages on npm
      - `ignore`: Private packages that should not be versioned/published
      - `updateInternalDependencies`: How to bump internal dep references
      
      ### Development Workflow
      
      ```bash
      # 1. Make code changes across packages
      
      # 2. Create a changeset describing the change
      pnpm changeset
      # Interactive prompt: select packages, bump type, description
      
      # 3. Commit the changeset file
      # .changeset/cool-dogs-dance.md gets committed with your PR
      
      # 4. When ready to release (usually automated):
      pnpm changeset version   # Bumps versions, generates changelogs
      pnpm install              # Update lockfile
      pnpm publish -r           # Publish to npm
      ```
      
      ### Root package.json Scripts
      
      ```json
      {
        "scripts": {
          "changeset": "changeset",
          "version-packages": "changeset version && pnpm install",
          "release": "pnpm build && pnpm publish -r --access=public"
        }
      }
      ```
      
      ---
      
      ## Docker with pnpm Workspaces
      
      ### Multi-Stage Dockerfile
      
      ```dockerfile
      # Stage 1: Install dependencies
      FROM node:22-alpine AS deps
      RUN corepack enable pnpm
      
      WORKDIR /app
      COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
      COPY apps/api/package.json ./apps/api/
      COPY packages/types/package.json ./packages/types/
      
      RUN pnpm install --frozen-lockfile
      
      # Stage 2: Build
      FROM node:22-alpine AS builder
      RUN corepack enable pnpm
      
      WORKDIR /app
      COPY --from=deps /app/node_modules ./node_modules
      COPY . .
      RUN pnpm --filter @repo/api build
      
      # Stage 3: Production
      FROM node:22-alpine AS runner
      RUN corepack enable pnpm
      
      WORKDIR /app
      COPY --from=builder /app/apps/api/dist ./dist
      COPY --from=builder /app/apps/api/package.json ./
      
      ENV NODE_ENV=production
      CMD ["node", "dist/index.js"]
      ```
      
      ### Using pnpm deploy (Recommended)
      
      ```yaml
      # pnpm-workspace.yaml
      injectWorkspacePackages: true # Required for pnpm deploy
      ```
      
      ```dockerfile
      FROM node:22-alpine AS builder
      RUN corepack enable pnpm
      
      WORKDIR /app
      COPY . .
      RUN pnpm install --frozen-lockfile
      RUN pnpm --filter @repo/api build
      RUN pnpm --filter @repo/api deploy ./pruned
      
      FROM node:22-alpine AS runner
      WORKDIR /app
      COPY --from=builder /app/pruned .
      ENV NODE_ENV=production
      CMD ["node", "dist/index.js"]
      ```
      
      **Why good:** `pnpm deploy` creates a standalone directory with only the production dependencies for a specific package, dramatically smaller Docker images, `injectWorkspacePackages: true` required for deploy to resolve workspace deps correctly. Use `pnpm deploy --legacy` if you cannot enable `injectWorkspacePackages` globally.
      
      ---
      
      ## Migration from npm/yarn
      
      ### Step-by-Step
      
      ```bash
      # 1. Remove old lockfile and node_modules
      rm -rf node_modules package-lock.json yarn.lock
      
      # 2. Create pnpm-workspace.yaml
      cat > pnpm-workspace.yaml << 'EOF'
      packages:
        - "apps/*"
        - "packages/*"
      EOF
      
      # 3. Set packageManager in root package.json
      # "packageManager": "pnpm@10.32.1"
      
      # 4. Install with pnpm
      pnpm install
      
      # 5. Convert internal dependencies to workspace:*
      # In each package.json, change "@repo/ui": "^1.0.0" to "@repo/ui": "workspace:*"
      
      # 6. Re-install to generate proper lockfile
      pnpm install
      ```
      
      **Key differences from npm/yarn:**
      
      - pnpm creates strict `node_modules` (no phantom dependencies)
      - You may need to add missing dependency declarations that npm/yarn silently resolved
      - `shamefullyHoist: true` can ease migration but should be removed once deps are fixed
      
    • scripts.md 4.3 KB
      # pnpm Workspaces -- Scripts & Filtering Examples
      
      > Running scripts, filtering commands, and dependency management examples. Reference from [SKILL.md](../SKILL.md).
      
      **Related examples:**
      
      - [core.md](core.md) -- Workspace initialization, pnpm-workspace.yaml, settings
      - [packages.md](packages.md) -- Shared packages, TypeScript config, workspace protocol
      - [publishing.md](publishing.md) -- Changesets, versioning, publishing
      - [ci.md](ci.md) -- CI/CD pipelines, GitHub Actions, Docker
      
      ---
      
      ## Filtering Commands
      
      ### Common Development Workflows
      
      ```bash
      # Start dev server for a specific app
      pnpm --filter @repo/web-app dev
      
      # Build a package and all its dependencies
      pnpm --filter "@repo/web-app..." build
      
      # Run tests for changed packages since main branch
      pnpm --filter "...[origin/main]" test
      
      # Lint everything except docs
      pnpm --filter "!@repo/docs" --filter "@repo/*" lint
      
      # Add a dependency to a specific package
      pnpm --filter @repo/api-server add some-package
      
      # Add a workspace dependency
      pnpm --filter @repo/web-app add @repo/ui --workspace
      
      # Remove a dependency from a package
      pnpm --filter @repo/web-app remove lodash
      ```
      
      ### Package Name Matching
      
      ```bash
      # Exact package name
      pnpm --filter @repo/web-app build
      
      # Glob pattern
      pnpm --filter "@repo/*" build
      
      # Short form
      pnpm -F @repo/web-app dev
      ```
      
      ### Dependency and Dependent Selection
      
      ```bash
      # Package and ALL its dependencies (transitive)
      pnpm --filter "web-app..." build
      
      # Only dependencies of a package (excludes the package itself)
      pnpm --filter "web-app^..." build
      
      # Package and ALL its dependents (what depends on it)
      pnpm --filter "...@repo/ui" build
      
      # Only dependents (excludes the package itself)
      pnpm --filter "...^@repo/ui" build
      ```
      
      ### Directory and Change-Based Filtering
      
      ```bash
      # All packages in a directory
      pnpm --filter "./packages/**" test
      
      # All packages changed since a git ref
      pnpm --filter "...[origin/main]" test
      
      # Changed packages and their dependents
      pnpm --filter "...[origin/main]..." build
      
      # Exclude a package
      pnpm --filter "!@repo/docs" build
      ```
      
      ### CI-Optimized Filtering
      
      ```bash
      # Build only changed packages and their dependents
      pnpm --filter "...[origin/main]..." build
      
      # Test changed packages (ignore README changes)
      pnpm --filter "...[origin/main]" \
        --changed-files-ignore-pattern="**/*.md" \
        test
      
      # Type-check only packages in the packages/ directory
      pnpm --filter "./packages/**" typecheck
      
      # Build with failure on no matches (catches filter typos)
      pnpm --filter "@repo/web-app" --fail-if-no-match build
      ```
      
      **Why good:** Targeted execution saves CI time, dependency-aware filtering ensures correct build order, change-based filtering only rebuilds what changed
      
      ### Dependency Graph Exploration
      
      ```bash
      # See what depends on @repo/types
      pnpm --filter "...@repo/types" list --depth 0
      
      # See what @repo/web-app depends on
      pnpm --filter "@repo/web-app..." list --depth 0
      
      # List all workspace packages
      pnpm -r list --depth -1
      ```
      
      ---
      
      ## Recursive Script Execution
      
      ### Topological vs Parallel
      
      ```bash
      # Run build in all packages (topological order -- respects dependency graph)
      pnpm -r build
      
      # Run in all packages including the root
      pnpm -r --include-workspace-root build
      
      # Run tests in parallel (ignores dependency graph)
      pnpm -r --parallel test
      
      # Control concurrency (4 packages at a time, topological order)
      pnpm -r --workspace-concurrency 4 build
      ```
      
      ### Correct Ordering
      
      ```bash
      # CORRECT: Build in dependency order (packages build before their dependents)
      pnpm -r build
      
      # CORRECT: Tests can run in parallel (no build artifact dependencies)
      pnpm -r --parallel test
      
      # CORRECT: Lint can run in parallel (no cross-package dependencies)
      pnpm -r --parallel lint
      ```
      
      ```bash
      # BAD: Building in parallel when packages depend on each other
      pnpm -r --parallel build
      ```
      
      **Why bad:** Packages that depend on other packages may start building before their dependencies finish, causing build failures with missing modules
      
      ---
      
      ## Dependency Management
      
      ### Adding Dependencies
      
      ```bash
      # Add a dependency to a specific package
      pnpm --filter @repo/web-app add zod
      
      # Add a dev dependency to the workspace root
      pnpm add -Dw vitest
      
      # Add a workspace package as a dependency
      pnpm --filter @repo/web-app add @repo/ui --workspace
      
      # Update a dependency across all packages
      pnpm -r update typescript
      
      # Clean all packages
      pnpm -r exec rm -rf dist node_modules
      ```
      
  • reference.md 8.2 KB
    # pnpm Workspaces Quick Reference
    
    > Quick reference for pnpm workspace commands, protocol syntax, and configuration options. See [SKILL.md](SKILL.md) for detailed patterns and [examples/](examples/) for practical examples.
    
    ---
    
    ## Workspace Protocol Syntax
    
    | Protocol      | Development | On Publish              | Use When                                       |
    | ------------- | ----------- | ----------------------- | ---------------------------------------------- |
    | `workspace:*` | Local link  | Exact version (`1.5.0`) | Internal packages (default choice)             |
    | `workspace:^` | Local link  | Caret range (`^1.5.0`)  | Publishing packages that need flexible ranges  |
    | `workspace:~` | Local link  | Tilde range (`~1.5.0`)  | Publishing packages with tight version control |
    
    ---
    
    ## Catalog Protocol Syntax
    
    | Syntax              | Meaning                                                      |
    | ------------------- | ------------------------------------------------------------ |
    | `"catalog:"`        | Use version from default `catalog:` in `pnpm-workspace.yaml` |
    | `"catalog:default"` | Explicit reference to default catalog                        |
    | `"catalog:<name>"`  | Use version from named catalog in `catalogs:`                |
    
    ---
    
    ## Filter Commands
    
    ### Package Selection
    
    ```bash
    pnpm --filter <package-name> <cmd>    # Exact package
    pnpm --filter "@scope/*" <cmd>        # Glob pattern
    pnpm -F <package-name> <cmd>          # Short form
    ```
    
    ### Dependency / Dependent Selection
    
    ```bash
    pnpm --filter "pkg..." <cmd>          # Package + all its dependencies
    pnpm --filter "pkg^..." <cmd>         # Only dependencies (excludes pkg)
    pnpm --filter "...pkg" <cmd>          # Package + all its dependents
    pnpm --filter "...^pkg" <cmd>         # Only dependents (excludes pkg)
    ```
    
    ### Directory and Change-Based
    
    ```bash
    pnpm --filter "./packages/**" <cmd>   # All packages in directory
    pnpm --filter "[origin/main]" <cmd>   # Changed packages since ref
    pnpm --filter "...[origin/main]" <cmd>  # Changed + their dependents
    ```
    
    ### Exclusion
    
    ```bash
    pnpm --filter "!pkg-name" <cmd>       # Exclude package
    pnpm --filter "!./lib" <cmd>          # Exclude directory
    ```
    
    ### Advanced Options
    
    ```bash
    --filter-prod                          # Omit devDependencies during selection
    --test-pattern="test/*"                # Prevent dependent execution for test-only changes
    --changed-files-ignore-pattern="**/*.md"  # Exclude files from change detection
    --fail-if-no-match                     # Error if no packages match
    ```
    
    ---
    
    ## Script Execution
    
    ```bash
    pnpm -r <cmd>                         # Recursive, topological order
    pnpm -r --parallel <cmd>              # Parallel, ignores dependency graph
    pnpm -r --workspace-concurrency 4 <cmd>  # Topological, max 4 concurrent
    pnpm -r --include-workspace-root <cmd>   # Include root package
    ```
    
    ---
    
    ## Dependency Management
    
    ```bash
    pnpm add -Dw <pkg>                    # Add dev dep to workspace root
    pnpm --filter <name> add <pkg>        # Add dep to specific package
    pnpm --filter <name> add <pkg> --workspace  # Add workspace package as dep
    pnpm -r update <pkg>                  # Update dep across all packages
    pnpm -r exec rm -rf dist node_modules # Clean all packages
    ```
    
    ---
    
    ## pnpm-workspace.yaml Settings (v10+)
    
    ### Workspace Definition
    
    ```yaml
    packages:
      - "apps/*"
      - "packages/*"
    ```
    
    ### Dependency Resolution
    
    | Setting                   | Default   | Purpose                                 |
    | ------------------------- | --------- | --------------------------------------- |
    | `linkWorkspacePackages`   | `false`   | Link local packages to `node_modules`   |
    | `saveWorkspaceProtocol`   | `rolling` | Auto-save `workspace:` protocol         |
    | `preferWorkspacePackages` | `false`   | Prefer workspace packages over registry |
    | `disallowWorkspaceCycles` | `false`   | Fail install on circular deps           |
    | `ignoreWorkspaceCycles`   | `false`   | Suppress cycle warnings                 |
    
    ### Hoisting
    
    | Setting                  | Default | Purpose                                     |
    | ------------------------ | ------- | ------------------------------------------- |
    | `shamefullyHoist`        | `false` | Hoist everything to root (avoid this)       |
    | `hoist`                  | `true`  | Hoist to hidden `.pnpm/node_modules`        |
    | `hoistPattern`           | `["*"]` | Which packages to hoist                     |
    | `publicHoistPattern`     | `[]`    | Hoist to root `node_modules`                |
    | `hoistWorkspacePackages` | `true`  | Symlink workspace packages per hoist config |
    
    ### Injection
    
    | Setting                   | Default | Purpose                                               |
    | ------------------------- | ------- | ----------------------------------------------------- |
    | `injectWorkspacePackages` | `false` | Hard-link workspace deps (required for `pnpm deploy`) |
    
    ### Catalogs
    
    | Setting                 | Default  | Purpose                                  |
    | ----------------------- | -------- | ---------------------------------------- |
    | `catalogMode`           | `manual` | `strict` / `prefer` / `manual`           |
    | `cleanupUnusedCatalogs` | `false`  | Remove unused catalog entries on install |
    
    ### Security (v10)
    
    | Setting                 | Purpose                                                                  |
    | ----------------------- | ------------------------------------------------------------------------ |
    | `allowBuilds`           | Map-based allowlist for install scripts (preferred, per-package boolean) |
    | `onlyBuiltDependencies` | Array-based allowlist (legacy, still supported)                          |
    
    ---
    
    ## .npmrc (v10+)
    
    In pnpm v10, `.npmrc` should ONLY contain auth and registry settings:
    
    ```ini
    //registry.npmjs.org/:_authToken=${NPM_TOKEN}
    @myorg:registry=https://npm.pkg.github.com
    //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
    ```
    
    ---
    
    ## Build Script Approval
    
    ```bash
    pnpm approve-builds                   # Interactive approval of packages needing install scripts
    pnpm approve-builds --all             # Approve all pending builds without prompting
    ```
    
    ---
    
    ## Changesets Commands
    
    ```bash
    pnpm add -Dw @changesets/cli          # Install
    pnpm changeset init                    # Initialize
    pnpm changeset                         # Create changeset (interactive)
    pnpm changeset version                 # Bump versions + changelogs
    pnpm install                           # Update lockfile after bumps
    pnpm publish -r                        # Publish updated packages
    pnpm publish -r --access=public        # Publish scoped public packages
    ```
    
    ---
    
    ## CI Checklist
    
    - [ ] Use `pnpm/action-setup@v4` with explicit version
    - [ ] Use `actions/setup-node@v4` with `cache: "pnpm"`
    - [ ] `--frozen-lockfile` is default in CI (do not disable)
    - [ ] Use `fetch-depth: 0` for change-based filtering
    - [ ] Use `--filter "...[origin/main]"` for affected-only builds
    - [ ] Pin pnpm version to match local development
    - [ ] Configure `NPM_TOKEN` secret for publishing
    - [ ] Configure `allowBuilds` (or `onlyBuiltDependencies`) for packages needing install scripts
    
    ---
    
    ## pnpm v10 Breaking Changes
    
    | Change                                                 | Migration                                           |
    | ------------------------------------------------------ | --------------------------------------------------- |
    | Settings moved from `.npmrc` to `pnpm-workspace.yaml`  | Move non-auth settings to YAML                      |
    | Lifecycle scripts blocked by default                   | Add `allowBuilds` map or run `pnpm approve-builds`  |
    | `pnpm deploy` requires `injectWorkspacePackages: true` | Add setting to workspace config (or use `--legacy`) |
    | JSR support via `jsr:` protocol                        | Use for JSR packages                                |
    | `devEngines.runtime` support                           | Specify runtime versions in `package.json`          |
    
    ---
    
    ## Resources
    
    **Official Documentation:**
    
    - pnpm Workspaces: https://pnpm.io/workspaces
    - pnpm Settings: https://pnpm.io/settings
    - pnpm Filtering: https://pnpm.io/filtering
    - pnpm Catalogs: https://pnpm.io/catalogs
    - pnpm CI: https://pnpm.io/continuous-integration
    - pnpm Changesets: https://pnpm.io/using-changesets
    
    **Tools:**
    
    - Changesets: https://github.com/changesets/changesets
    - pnpm GitHub Action: https://github.com/pnpm/action-setup
    
  • SKILL.md 16.1 KB
    ---
    name: shared-monorepo-pnpm-workspaces
    description: pnpm workspace protocol, filtering, catalogs, shared dependencies, publishing, and CI/CD for monorepo management
    ---
    
    # pnpm Workspaces for Monorepo Management
    
    > **Quick Guide:** pnpm 10.x workspaces for monorepo management. `pnpm-workspace.yaml` defines workspace packages. `workspace:*` protocol for internal linking. `catalog:` protocol for dependency version synchronization. `--filter` for targeted commands. Shared `tsconfig` and tooling config across packages. Changesets for versioning and publishing. Strict dependency isolation by default.
    
    ---
    
    <critical_requirements>
    
    ## CRITICAL: Before Using This Skill
    
    > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
    
    **(You MUST use `workspace:*` protocol for ALL internal package dependencies -- never hardcode versions)**
    
    **(You MUST use `--frozen-lockfile` in CI -- pnpm enables this by default in CI environments)**
    
    **(You MUST define workspace packages in `pnpm-workspace.yaml` at the repository root)**
    
    **(You MUST put pnpm-specific settings in `pnpm-workspace.yaml` -- NOT `.npmrc` (pnpm v10 change))**
    
    **(You MUST use `catalog:` protocol when sharing dependency versions across 3+ packages)**
    
    **(You MUST use `--filter` for targeted commands instead of `pnpm -r` when only specific packages changed)**
    
    </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 `pnpm-workspace.yaml` for workspace package discovery
    - Linking internal packages with `workspace:*` protocol
    - Synchronizing dependency versions with `catalog:` protocol
    - Running scripts across workspaces with `--filter` or `-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:**
    
    - `pnpm-workspace.yaml` setup and workspace package discovery
    - Workspace protocol (`workspace:*`, `workspace:^`, `workspace:~`)
    - Catalogs for dependency version synchronization
    - Filtering commands (`--filter`, `-F`, glob patterns, dependency selectors)
    - Running scripts across workspaces (`-r`, `--parallel`, `--workspace-concurrency`)
    - Settings in `pnpm-workspace.yaml` (v10: settings moved from `.npmrc`)
    - Publishing with `publishConfig` and changesets
    - CI/CD with GitHub Actions, caching, and `--frozen-lockfile`
    - Shared TypeScript configuration patterns
    - Monorepo directory structure conventions
    
    **Detailed resources:**
    
    - [Core Setup](examples/core.md) -- pnpm-workspace.yaml, .npmrc, directory structure, settings
    - [Shared Packages](examples/packages.md) -- workspace protocol, catalogs, TypeScript/ESLint config
    - [Scripts & Filtering](examples/scripts.md) -- --filter, recursive execution, dependency management
    - [Publishing & Versioning](examples/publishing.md) -- changesets, publishConfig, Docker
    - [CI/CD Pipelines](examples/ci.md) -- GitHub Actions, automated release
    - [Quick Command Reference](reference.md) -- 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 `pnpm-lock.yaml` at the workspace root for all packages
    
    **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)
    
    </philosophy>
    
    ---
    
    <patterns>
    
    ## Core Patterns
    
    ### Pattern 1: Workspace Configuration (`pnpm-workspace.yaml`)
    
    The `pnpm-workspace.yaml` file at the repository root defines which directories contain workspace packages. Every pnpm workspace MUST have this file.
    
    ```yaml
    # pnpm-workspace.yaml
    packages:
      - "apps/*"
      - "packages/*"
    ```
    
    In pnpm v10, settings moved from `.npmrc` to this file:
    
    ```yaml
    # 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](examples/core.md) for full workspace setup, directory structure, and all settings.
    
    ---
    
    ### Pattern 2: Workspace Protocol (`workspace:*`)
    
    The workspace protocol ensures internal packages always resolve to the local workspace version, never from the registry.
    
    | Protocol      | During Development     | After `pnpm publish`                        |
    | ------------- | ---------------------- | ------------------------------------------- |
    | `workspace:*` | Links to local package | Replaced with exact version (e.g., `1.5.0`) |
    | `workspace:^` | Links to local package | Replaced with caret range (e.g., `^1.5.0`)  |
    | `workspace:~` | Links to local package | Replaced with tilde range (e.g., `~1.5.0`)  |
    
    ```json
    {
      "dependencies": {
        "@repo/ui": "workspace:*",
        "@repo/types": "workspace:*"
      }
    }
    ```
    
    **Why good:** Guarantees local linking, pnpm refuses to resolve externally, version conversion on publish ensures correct semver
    
    ```json
    {
      "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](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:`.
    
    ```yaml
    # pnpm-workspace.yaml
    catalog:
      react: ^19.0.0
      react-dom: ^19.0.0
      typescript: ^5.7.0
    ```
    
    ```json
    {
      "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:
    
    ```yaml
    catalogs:
      react18:
        react: ^18.3.1
      react19:
        react: ^19.0.0
    ```
    
    See [examples/packages.md](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.
    
    ```bash
    # 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
    
    ```bash
    # 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](examples/scripts.md) for all filter variants, CI-optimized patterns, and graph exploration.
    
    ---
    
    ### Pattern 5: Running Scripts Across Workspaces
    
    ```bash
    # 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
    ```
    
    ```bash
    # 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](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.
    
    ```json
    {
      "name": "@repo/config-typescript",
      "private": true,
      "exports": {
        "./base": "./tsconfig.base.json",
        "./react": "./tsconfig.react.json",
        "./node": "./tsconfig.node.json"
      }
    }
    ```
    
    Consumer usage:
    
    ```json
    {
      "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](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.
    
    ```json
    {
      "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
    
    ```bash
    pnpm changeset          # Create changeset
    pnpm changeset version  # Bump versions + changelogs
    pnpm publish -r         # Publish to npm
    ```
    
    See [examples/publishing.md](examples/publishing.md) for changesets config, Docker deployment, and migration.
    
    ---
    
    ### Pattern 8: CI/CD with GitHub Actions
    
    ```yaml
    - 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](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
    - `--frozen-lockfile` (default in CI) skips resolution for fastest installs
    
    **Workspace Performance:**
    
    - Use `--filter` to run commands only on affected packages
    - Use `--parallel` for tasks without cross-package dependencies (test, lint)
    - Use `--workspace-concurrency` to control parallelism on resource-constrained CI
    - Change-based filtering (`--filter "...[origin/main]"`) skips unchanged packages
    
    **Disk Savings:**
    
    - Global store deduplicates across projects (`pnpm store path`)
    - Typical savings: 50-70% less disk space compared to npm
    
    **CI Caching:**
    
    ```yaml
    - 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 `workspace:*` (breaks local linking, installs from registry)
    - Settings in `.npmrc` that should be in `pnpm-workspace.yaml` (silently ignored in pnpm v10)
    - Missing `pnpm-workspace.yaml` at repo root (pnpm will not recognize workspace packages)
    - Running `pnpm install` without `--frozen-lockfile` in CI (lockfile can mutate silently)
    - Using `--parallel` for build commands when packages depend on each other (race conditions)
    
    **Medium Priority Issues:**
    
    - Not using `catalog:` when 3+ packages share the same dependency version (version drift)
    - Running `pnpm -r build` in CI instead of `--filter "...[origin/main]"` (wastes time)
    - Missing `private: true` on internal packages (risk of accidental npm publish)
    - Not configuring `allowBuilds` (or the older `onlyBuiltDependencies`) allowlist (blocks all install scripts in v10)
    
    **Common Mistakes:**
    
    - Mixing package managers (npm install in a pnpm workspace breaks the lockfile)
    - Using `shamefullyHoist: true` as a quick fix instead of declaring missing dependencies properly
    - Forgetting `fetch-depth: 0` in GitHub Actions checkout (breaks git-based change detection)
    - Running different pnpm versions locally vs CI (lockfile format incompatibility)
    
    **Gotchas & Edge Cases:**
    
    - `workspace:*` is replaced with the actual version on `pnpm publish` -- this is expected behavior, not a bug
    - `catalog:` entries must match the dependency name exactly -- typos silently fall through
    - `--filter "...[origin/main]"` requires git history -- use `fetch-depth: 0` or at minimum `fetch-depth: 2`
    - pnpm v10 blocks ALL lifecycle scripts by default -- use `pnpm approve-builds` to allowlist packages that need `postinstall` (like `esbuild`, `sharp`), or configure `allowBuilds` in `pnpm-workspace.yaml`
    - `injectWorkspacePackages: true` is required for `pnpm deploy` (or use `pnpm deploy --legacy` to bypass)
    - Circular workspace dependencies cause unpredictable script execution order -- use `disallowWorkspaceCycles: true`
    - `saveWorkspaceProtocol: rolling` (default) means `pnpm add` auto-saves with `workspace:` -- this is correct behavior
    
    </red_flags>
    
    ---
    
    <critical_reminders>
    
    ## CRITICAL REMINDERS
    
    > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
    
    **(You MUST use `workspace:*` protocol for ALL internal package dependencies -- never hardcode versions)**
    
    **(You MUST use `--frozen-lockfile` in CI -- pnpm enables this by default in CI environments)**
    
    **(You MUST define workspace packages in `pnpm-workspace.yaml` at the repository root)**
    
    **(You MUST put pnpm-specific settings in `pnpm-workspace.yaml` -- NOT `.npmrc` (pnpm v10 change))**
    
    **(You MUST use `catalog:` protocol when sharing dependency versions across 3+ packages)**
    
    **(You MUST use `--filter` for targeted commands instead of `pnpm -r` when only specific packages changed)**
    
    **Failure to follow these rules will cause broken dependency resolution, version drift, missed CI caching, and security vulnerabilities.**
    
    </critical_reminders>
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related