Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/windows-pnpm-symlink-junction.md
Original file line number Diff line number Diff line change
@@ -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.
24 changes: 23 additions & 1 deletion packages/open-next/src/build/copyTracedFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down