# 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//` 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 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.