Files
development-monorepo-pnpm-3…/references/workspace-and-tooling.md
root-at-skicandClaude Opus 5 ff69bcca2f development-monorepo-pnpm skill: initial import
Full-stack pnpm + Turborepo monorepo skill, independent of the separate
backend and frontend monorepo skills it draws on.

Repository shape is split four ways at the top level: applications-backend,
applications-frontend, packages-backend, packages-frontend — so a path
states which runtime a file ends up in.

Defaults: NestJS for backend services, React with IBM Carbon Design for
browser applications; the project's stated architecture overrides both.

Covers workspace globs, the Turborepo pipeline, filtered validation,
blast-radius checks, Module Federation, turbo prune image builds, and Helm.
Documents one sanctioned cross-boundary dependency: API contract types owned
by the backend, imported type-only by the frontend.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 23:49:02 +03:00

5.6 KiB

Workspace and Tooling

Root-level configuration: workspace globs, the Turborepo pipeline and its caching, root scripts, Prettier, dependency overrides, and lockfile discipline.

Read this when adding a workspace, changing turbo.json or root scripts, debugging a task that does not run or a cache that never hits, or touching pnpm.overrides.


1. Workspace globs

# pnpm-workspace.yaml
packages:
  - "applications-backend/*"
  - "applications-frontend/*"
  - "packages-backend/*"
  - "packages-frontend/*"

Exactly one level of nesting under each root. A directory that no glob matches is invisible to pnpm and to Turborepo — it will not install dependencies, will not build, and will not fail loudly. The symptom is usually "my new package isn't found by its consumer".

After adding a workspace, run pnpm install so the workspace links are created.

2. Root package.json

{
  "packageManager": "pnpm@10.17.1",
  "engines": { "node": ">=22", "pnpm": ">=10" },
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "check-types": "turbo run check-types",
    "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,scss}\"",
    "publish-packages": "turbo run build:ci lint && changeset version && changeset publish",
    "prepush": "turbo build --filter=...[HEAD^]"
  }
}
  • packageManager pins the exact pnpm version; corepack reads it, so every contributor and CI runner resolves dependencies identically.
  • Root scripts always delegate to Turborepo, never to a single workspace — calling a workspace script directly bypasses the dependency graph and builds stale upstream code.
  • Root dependencies are tooling only: turbo, prettier, husky, changesets, typescript.

3. Turborepo pipeline

Task Depends on Cached outputs
build ^build, check-types, lint dist/**, .next/**
build:ci ^build:ci dist/**, dist-internal/**
lint ^lint —
check-types ^check-types —
dev — — (persistent, never cached)

^task means the task must complete in every upstream workspace dependency first. That is what makes --filter safe: filtering to one workspace still builds what it depends on.

Two consequences worth internalising:

  • build does not run until check-types and lint pass. A type or lint error is a build failure for every downstream workspace, not a warning in one.
  • Every output directory a workspace produces must be listed in outputs. An unlisted directory is not restored from cache, so a "cache hit" silently yields a missing build artifact — which surfaces later as a container image built from nothing.

Task names in a workspace's package.json must match the task names in turbo.json. A mismatch is not an error; Turborepo just skips that workspace, and the task appears to pass.

Cache inputs include $TURBO_DEFAULT$ and .env*.

4. Dependency rules

  • Add a dependency to the smallest workspace that needs it.
  • Inter-workspace dependencies use workspace:*.
  • Run pnpm install after every package.json edit; commit the pnpm-lock.yaml change together with the edit that caused it. A lockfile committed separately is unreviewable.
  • Never hand-edit pnpm-lock.yaml.

pnpm.overrides — security pins

{ "pnpm": { "overrides": { "undici": "^6.21.1", "ws": "^8.17.1" } } }

Each entry exists because a transitive dependency shipped a vulnerability its direct parent had not yet picked up. Removing an entry silently reinstates the vulnerable version and nothing in the build complains — no error, no warning, a green pipeline.

Remove one only after confirming the direct dependency now resolves to a fixed version, and record that verification in the change.

5. Formatting

Root .prettierrc is the single authority for the whole repository. Do not add per-package Prettier config: two configs mean every file formatted from one side produces churn diffs when touched from the other.

pnpm format

6. Taskfile.yml

The root Taskfile.yml is a thin delegator — it only includes: module Taskfiles under .scripts/. Never add inline commands to it. New automation becomes a .scripts/<module>/ with its own Taskfile, which is then included.

7. Diagnosing common failures

Symptom Likely cause
New workspace not found by its consumer no matching glob, or pnpm install not re-run
Task appears to pass but does nothing script name does not match a turbo.json task
Cache hit but missing build artifact output directory not listed in outputs
Lockfile churn on every install packageManager not pinned, or pnpm version drift
Build fails only in CI --frozen-lockfile exposing an uncommitted lockfile change
Vulnerability reappears after an install an overrides entry was removed
pnpm list --depth 0
pnpm why <package>
turbo run build --dry-run          # what would run, and in what order
turbo run build --filter=...[HEAD^]

8. Mistakes to avoid

  • Adding a workspace outside the four roots, or without updating the globs.
  • Calling a workspace script directly instead of going through Turborepo.
  • Adding a root dependency for a need local to one workspace.
  • Omitting an output directory from a task's outputs.
  • A workspace script name that does not match its turbo.json task.
  • Removing a pnpm.overrides entry without a security review.
  • Per-package Prettier configuration.
  • Inline commands in the root Taskfile.yml.
  • Committing a lockfile change apart from the package.json edit that caused it.