Patch `@sveltejs/adapter-vercel` to fall back to Windows junctions on EPERM — so `pnpm -r build` is green on Windows without Developer Mode

0065-windows-build-adapter-vercel-junction-patch

decision read as Explain confidence asserted status active 2026-06-20 owner forward-deployed-engineer
Reversibility
two-way door

DEC-0065 — Patch adapter-vercel for Windows-junction build compatibility

Reversibility: two-way door. The fix is a committed pnpm patch; removing the patchedDependencies entry in pnpm-workspace.yaml and the patches/@sveltejs__adapter-vercel@6.3.3.patch file reverts to upstream on the next install. Nothing else depends on it.

Context

pnpm -r build failed on Windows (this dev environment) at @sveltejs/adapter-vercel@6.3.3:

Error: EPERM: operation not permitted, symlink … .vercel/output/functions/…
    at adapt (…/@sveltejs/adapter-vercel/index.js)

@dossier/app has one dynamic route (/api/subscribe POST — Buttondown), so the adapter emits a serverless function and symlinks to assemble it. Windows fs.symlinkSync requires SeCreateSymbolicLinkPrivilege (admin / Developer Mode), absent in a normal shell → EPERM. There are two symlink sites: the per-function node_modules bundle (index.js ~792, source !== realpath pnpm links) and the route→function dedup links (index.js ~477/478, *.func → the shared ![-].func).

Decision

Ship a committed pnpm patch (patches/@sveltejs__adapter-vercel@6.3.3.patch, wired through pnpm-workspace.yaml patchedDependencies so it reapplies on every install, on every machine). At both symlink sites, wrap the symlinkSync in a guard that — only on win32 with an EPERM — falls back to:

  • a junction for directories (fs.symlinkSync(absoluteTarget, link, 'junction') — needs no elevation; requires an absolute target, which both sites have), and
  • a copy for files.

Non-Windows platforms never throw, so they run the original upstream path untouched.

Why a patch (and not the alternatives)

  • Enable Developer Mode / run elevated — an OS/host setting, not an in-repo durable fix; can't be committed, breaks the next clean checkout.
  • node-linker=hoisted — would make node_modules real dirs (so the adapter copies instead of symlinking), but it changes the whole repo's install strategy (perf, disk, determinism) to fix one local build. Too broad.
  • A committed pnpm patch (chosen) — surgical, durable, reapplied on install, scoped to the exact failing lines, platform-guarded, and invisible to deployment.

Deploy safety

The .vercel/output shipped to production is built by Vercel's own Linux build, not the local Windows build. On Linux the original symlinkSync succeeds and the patch's fallback is never reached, so the deployed bundle is byte-identical to upstream. The patch only makes the local Windows build green (for verification / DX). DEC-0043's deploy posture is unchanged.

Verification (no fabricated status)

  • pnpm --filter @dossier/app build✔ done (.vercel/output produced).
  • pnpm -r buildexit 0 (the whole monorepo, the originally-failing command).
  • svelte-check / the app + monorepo test suites stay green (unaffected — the patch only changes Windows fs calls during adapt).

Consequences

  • The Windows build limitation noted across DEC-0063/DEC-0064 reports is resolved; local full-build verification is now possible on Windows.
  • A future adapter-vercel upgrade past 6.3.3 will drop the version-pinned patch; if the symlink code is unchanged upstream, re-pin the patch to the new version (or upstream the junction fallback). The patch is small and self-documented (inline comments at both sites).