Files
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

144 lines
5.6 KiB
Markdown

# 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
```yaml
# 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`
```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
```json
{ "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.
```bash
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 |
```bash
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.