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;