fix: patch ESM resolve.js during asset build to restore ENOENT catch

Node 22.22+ / 24 removed the try/catch around realpathSync in
  finalizeResolution. realpathSync calls binding.lstat which throws ENOENT
  for VFS paths (they don't exist on real FS). In Node 24 resolve.js is
  loaded eagerly during bootstrap before user code runs, so runtime patches
  to fs.realpathSync arrive too late.

  Patch the Node source directly during asset builds so the ENOENT guard
  is compiled into the binary. This silently no-ops on older Node versions
  where the pattern doesn't match (they already catch ENOENT internally).
This commit is contained in:
2026-05-19 03:08:27 -07:00
parent bc2c72d4e8
commit 4d5eb4d2d7
+8
View File
@@ -95,6 +95,14 @@ export default async function main(
"assert(!CJSLoader.hasLoadedAnyUserCJSModule)",
"/*assert(!CJSLoader.hasLoadedAnyUserCJSModule)*/"
);
// Node 22.22+ / 24 removed the ENOENT catch from finalizeResolution in the
// ESM resolver. realpathSync calls binding.lstat which throws ENOENT for VFS
// paths (they don't exist on real FS). Restore the catch so VFS imports work.
await compiler.replaceInFileAsync(
"lib/internal/modules/esm/resolve.js",
/const real = realpathSync\(path, \{ encoding: ['"]utf8['"] \}\);/,
"let real; try { real = realpathSync(path, { encoding: 'utf8' }); } catch (e) { if (e.code !== 'ENOENT') throw e; real = path; }"
);
const { contents: nodeccContents } = await compiler.readFileAsync(
"src/node.cc"
);