fs.readlinkSync on snapshot paths does not match Node's behaviour, differently in each packaging mode.
Probe package: lib -> ./reallib (directory symlink), reallib/log.js, entry requiring ./lib/log.
--- unpackaged node ---
lib -> ./reallib
lib/log.js -> EINVAL readlink (correct: not a symlink)
--- standard mode ---
lib -> ENOENT readlink
lib/log.js -> ENOENT readlink
--- SEA mode ---
lib -> /snapshot/rlprobe/reallib
lib/log.js -> /snapshot/rlprobe/reallib/log.js
fs.realpathSync is correct in both modes (SEA since 6df8da1 on the #296 branch).
Three separate problems:
Standard mode never patches fs.readlinkSync. prelude/bootstrap.js carries only a // fs.promises.readlink ? note, and the throw comes straight from node:fs. The classic bootstrap patches fs itself and does not go through @roberts_lando/vfs, so readlink fails on every snapshot path, symlink or not. Entirely a pkg-side gap.
The manifest stores the resolved target, not the literal one. lib/walker.ts:459 does this.symLinks[file] = realFile and lib/sea-assets.ts:136 only POSIX-normalizes it, so the manifest holds "/rlprobe/lib": "/rlprobe/reallib" where the on-disk link is ./reallib. Even once the provider is consulted, pkg can only return a resolved absolute path. True readlink semantics would mean storing the raw fs.readlinkSync result alongside the resolved one — a manifest format change, and findCommonJunctionPoint rewrites both paths before storage, so it needs care. Alternatively this is documented as realpath-shaped and left alone.
Non-symlinks give ENOENT instead of EINVAL. SEAProvider.readlinkSync falls through to super.readlinkSync, and MemoryProvider's tree holds only directories, so a real archive file that is not a symlink raises ENOENT. It should raise EINVAL when the path is in manifest.stats but absent from symlinks.
Related, on the VFS side: the fs.readlinkSync patch in module_hooks.js answers via findVFSForRealpath and never calls provider.readlinkSync, so SEAProvider.readlinkSync is unreachable in a packaged binary today. That routing change belongs in @roberts_lando/vfs; the three items above remain regardless.
Found while reviewing #296. None of these are caused by that PR.
fs.readlinkSyncon snapshot paths does not match Node's behaviour, differently in each packaging mode.Probe package:
lib -> ./reallib(directory symlink),reallib/log.js, entry requiring./lib/log.fs.realpathSyncis correct in both modes (SEA since6df8da1on the #296 branch).Three separate problems:
Standard mode never patches
fs.readlinkSync.prelude/bootstrap.jscarries only a// fs.promises.readlink ?note, and the throw comes straight fromnode:fs. The classic bootstrap patchesfsitself and does not go through@roberts_lando/vfs, so readlink fails on every snapshot path, symlink or not. Entirely a pkg-side gap.The manifest stores the resolved target, not the literal one.
lib/walker.ts:459doesthis.symLinks[file] = realFileandlib/sea-assets.ts:136only POSIX-normalizes it, so the manifest holds"/rlprobe/lib": "/rlprobe/reallib"where the on-disk link is./reallib. Even once the provider is consulted, pkg can only return a resolved absolute path. Truereadlinksemantics would mean storing the rawfs.readlinkSyncresult alongside the resolved one — a manifest format change, andfindCommonJunctionPointrewrites both paths before storage, so it needs care. Alternatively this is documented as realpath-shaped and left alone.Non-symlinks give
ENOENTinstead ofEINVAL.SEAProvider.readlinkSyncfalls through tosuper.readlinkSync, andMemoryProvider's tree holds only directories, so a real archive file that is not a symlink raisesENOENT. It should raiseEINVALwhen the path is inmanifest.statsbut absent fromsymlinks.Related, on the VFS side: the
fs.readlinkSyncpatch inmodule_hooks.jsanswers viafindVFSForRealpathand never callsprovider.readlinkSync, soSEAProvider.readlinkSyncis unreachable in a packaged binary today. That routing change belongs in@roberts_lando/vfs; the three items above remain regardless.Found while reviewing #296. None of these are caused by that PR.