directus-main — pnpm to Yarn migration

Six pnpm mechanisms with no exact Yarn equivalent — six real decisions, two of them wrong at first.

Directus's own monorepo is pnpm-native: a 331-entry catalog: version table, pnpm.overrides , a regex script-filter, pnpm deploy , pnpm fetch 's layer-cache trick, and an onlyBuiltDependencies allowlist. None of those have an exact Yarn Berry equivalent — each one got a real decision, tested against a real install, not a find-and-replace. Two of those first-pass decisions were wrong and got corrected properly. See System Architecture for where this manifest layer sits relative to real source.

What Changed

catalog: protocol Yarn's own catalog config · overrides resolutions · pnpm deploy custom worktree script · onlyBuiltDependencies enableScripts: true

Six mechanisms, six outcomes: two required no special handling once verified ( overrides , the regex-script-filter dropped for explicit lists), two got a working replacement built ( catalog: , pnpm deploy ), and two are accepted, disclosed regressions with no real equivalent ( enableScripts , pnpm fetch 's layer-caching).

Real Gaps Found

01 · build-breaking

isolated-vm@5.0.3 — incompatible with Node 26's V8

The version inherited from Directus's own catalog failed to compile identically on Windows and the GitHub Actions Ubuntu runner. Real compiler output ( gh run view --log-failed ) showed the native addon calling V8 APIs Node 26's bundled V8 has since removed or changed — Template::SetAccessor , GetPrototype , internal-field accessor signatures. Every other native module in the ~2,150-package graph built cleanly, ruling out a missing-toolchain theory first. Fixed by bumping to 7.0.1 , verified with a real yarn install .

02 · process correction

"Yarn has no shared-catalog feature" — wrong, corrected

Stood documented as fact — in this repo's own docs, its wiki, and several commit messages — until yarn config -v 's full settings dump (not yarn help , which doesn't mention it) surfaced real catalog / catalogs config keys. Verified in a throwaway scratch project before touching this repo.

First pass — wrong

// "Yarn has no catalog: // equivalent" — assumed, // never actually checked "lodash": "4.17.21"

Corrected — verified

// .yarnrc.yml catalog: { lodash: 4.17.21 } // package.json "lodash": "catalog:"
03 · destructive-action correction

deploy-production.mjs — pruned the live repo's own node_modules

pnpm deploy --legacy --prod has no Yarn equivalent; the first replacement script pruned this repo's own root node_modules in place — a real regression from pnpm's separately-built deploy output. Reconsidered: yarn workspaces focus --production only needs a checkout at the right commit, not the live working tree, so the script now runs inside a disposable git worktree instead. Verified end-to-end — a working dist/ with 724 node_modules entries came out the other side.

04 · upstream tooling gap

@directus/tsconfig — neither config sets rootDir

A real gap in Directus's own published @directus/tsconfig npm package (v4.0.0): with outDir set but no explicit rootDir , TypeScript falls back to inferring it from the include set's "common source directory." Real tsc 5.9.3 builds identically either way — confirmed by running it directly and diffing output before/after — but newer TS tooling errors on the implicit inference. Fixed with an explicit "rootDir": "./src" in packages/types/tsconfig.json , the value already being inferred.

05 · false positive

Webhint — stale bundled schema flagged a valid tsconfig value

The VS Code Webhint extension's typescript-config/is-valid hint flagged lib: ["ES2023"] as invalid. It isn't — TypeScript 5.9.3 (the version actually installed) has supported ES2023 as a lib value since 5.2; the hint's bundled schema is just out of date. Confirmed against the real installed TypeScript version before suppressing, and scoped the suppression to that one hint only.

Still Open

06 · tracked, not fixed

release-notes-generator — still imports removed pnpm internals

package.json no longer lists @pnpm/workspace.find-packages , @pnpm/workspace.pkgs-graph , or @pnpm/logger — but the actual source file that imports them, src/utils/process-packages.ts , hasn't been rewritten yet. Caught and tracked rather than assumed fixed just because the manifest looked clean. Planned replacement: read each workspace's package.json directly and build the dependency graph from workspace:* references — no package-manager-specific API needed at all.

Accepted, Not Fixed

enableScripts: true — a real security-posture downgrade

pnpm's onlyBuiltDependencies is a default-deny list for which packages may run install/postinstall scripts. Yarn Berry has no scoped equivalent — enableScripts: true is its permissive default. Accepted and documented rather than silently inherited.

Docker build-cache regression — no pnpm fetch equivalent

pnpm fetch populates the store from just the lockfile, before the full source tree is copied in — a real Docker layer-caching optimization. Yarn workspaces need every workspace's package.json present to resolve at all, so there's no equivalent partial-copy trick without fragile wildcards. Both Dockerfiles now do one COPY . . then yarn install — any source change invalidates the install layer too.

i

Five gaps found and fixed, one tracked openly, two accepted trade-offs — all six categories recorded in this repo's own todo.md , not just here. See Known Gaps for the rest of what's still open across CI, testing, and Docker.