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>
144 lines
5.6 KiB
Markdown
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.
|