From fb13e0470a74e6110859f6f66c32b15ece41011d Mon Sep 17 00:00:00 2001 From: joshua Date: Thu, 11 Jun 2026 17:33:38 +0900 Subject: [PATCH] fix(open-next): recreate pnpm symlinks as junctions on Windows in copyTracedFiles On Windows, recreating a pnpm directory symlink from the raw readlinkSync value produces a file-type symlink when the target does not exist yet (Node autodetects the type and falls back to "file"), which Windows cannot traverse as a directory. esbuild then fails with "Cannot read directory ...: Access is denied" / could not resolve errors. Create a junction instead, with the raw target resolved against the destination parent directory - semantically identical to what the relative symlink means on Linux. Junctions only apply to directories, need no admin rights or Developer Mode, and resolve lazily once the target is populated. Fixes #1184 Co-Authored-By: Claude Fable 5 --- .changeset/windows-pnpm-symlink-junction.md | 7 ++++++ .../open-next/src/build/copyTracedFiles.ts | 24 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 .changeset/windows-pnpm-symlink-junction.md diff --git a/.changeset/windows-pnpm-symlink-junction.md b/.changeset/windows-pnpm-symlink-junction.md new file mode 100644 index 000000000..75adc3ea5 --- /dev/null +++ b/.changeset/windows-pnpm-symlink-junction.md @@ -0,0 +1,7 @@ +--- +"@opennextjs/aws": patch +--- + +Fix broken symlink recreation in `copyTracedFiles` on Windows + +On Windows, recreating pnpm directory symlinks from the raw `readlinkSync` value produced file-type symlinks pointing at directories (Node falls back to `type: "file"` when the target does not exist yet), which esbuild could not traverse ("Cannot read directory ...: Access is denied"). Directory links are now recreated as junctions whose target is resolved against the destination's parent directory, matching the semantics of the relative symlink on Linux. diff --git a/packages/open-next/src/build/copyTracedFiles.ts b/packages/open-next/src/build/copyTracedFiles.ts index 9f6689c0f..f6ea7f68d 100644 --- a/packages/open-next/src/build/copyTracedFiles.ts +++ b/packages/open-next/src/build/copyTracedFiles.ts @@ -322,7 +322,29 @@ File ${serverPath} does not exist } if (symlink) { try { - symlinkSync(symlink, to); + if (process.platform === "win32") { + // On Windows, recreating the link from the raw readlink value breaks: + // `symlinkSync` without a `type` autodetects from the target, and when + // the target does not exist yet (common here, since entries are copied + // in traversal order) it falls back to a *file*-type symlink pointing + // at a directory, which Windows cannot traverse (esbuild later fails + // with "Cannot read directory ...: Access is denied"). + // Instead, create a junction whose target is the raw value resolved + // against the destination's parent directory — semantically identical + // to what the relative symlink means on Linux (it points into the + // mirrored output structure). Junctions only apply to directories + // (every pnpm link recreated here is one), need no admin rights or + // Developer Mode, and resolve lazily once the target is populated. + const rawTarget = symlink.startsWith("\\\\?\\") + ? symlink.slice(4) + : symlink; + const target = path.isAbsolute(rawTarget) + ? rawTarget + : path.resolve(path.dirname(to), rawTarget); + symlinkSync(target, to, "junction"); + } else { + symlinkSync(symlink, to); + } } catch (e: any) { if (e.code !== "EEXIST") { throw e;