From 843326bf87ac2679bf3d3c41ea5fb551b7878e55 Mon Sep 17 00:00:00 2001 From: Michael Potthoff Date: Fri, 21 Aug 2026 00:28:44 +0200 Subject: [PATCH] fix(sea): files inside symlinks are not resolved correctly (#295) --- prelude/sea-vfs-setup.js | 25 ++++++++++++++++++++----- test/test-99-#295/index.js | 5 +++++ test/test-99-#295/lib | 1 + test/test-99-#295/main.js | 31 +++++++++++++++++++++++++++++++ test/test-99-#295/package.json | 6 ++++++ test/test-99-#295/reallib/log.js | 3 +++ 6 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 test/test-99-#295/index.js create mode 120000 test/test-99-#295/lib create mode 100644 test/test-99-#295/main.js create mode 100644 test/test-99-#295/package.json create mode 100644 test/test-99-#295/reallib/log.js diff --git a/prelude/sea-vfs-setup.js b/prelude/sea-vfs-setup.js index 94a34ea58..48018d820 100644 --- a/prelude/sea-vfs-setup.js +++ b/prelude/sea-vfs-setup.js @@ -307,6 +307,10 @@ class SEAProvider extends MemoryProvider { this._manifest = seaManifest; this._fileCache = new Map(); + // Precompute whether the manifest has any symlinks. + // If a project has no symlinks, there is also no need to resolve them. + this._hasSymlinks = Object.keys(seaManifest.symlinks).length > 0; + // Pick the per-file decompressor once at construction time. Absent or 0 = // uncompressed archive (backward compat with pre-#250 SEA binaries). The // shared helper raises a uniformly-worded error when the host Node.js is @@ -337,15 +341,26 @@ class SEAProvider extends MemoryProvider { } _resolveSymlink(p) { - // Fast path: the vast majority of lookups (~30K per startup on large - // projects) are not symlinks. A single object-has-key check avoids - // entering the loop and the i++/target fetch overhead for the common - // case. + // Fast path: if the manifest has no symlinks, skip the loop entirely. + if (!this._hasSymlinks) return p; var symlinks = this._manifest.symlinks; - if (symlinks[p] === undefined) return p; var original = p; for (var i = 0; i < MAX_SYMLINK_DEPTH; i++) { + // First check the full path, then walk up the directory tree to find a symlink. var target = symlinks[p]; + if (!target) { + var parentIdx = p.lastIndexOf('/'); + while (parentIdx > 0) { + var parent = p.slice(0, parentIdx); + target = symlinks[parent]; + if (target) { + // Resolve the symlink and append the remainder of the original path. + target = target + p.slice(parentIdx); + break; + } + parentIdx = parent.lastIndexOf('/'); + } + } if (!target) return p; p = target; } diff --git a/test/test-99-#295/index.js b/test/test-99-#295/index.js new file mode 100644 index 000000000..960ef24a6 --- /dev/null +++ b/test/test-99-#295/index.js @@ -0,0 +1,5 @@ +'use strict'; + +const log = require('./lib/log'); + +log(42); diff --git a/test/test-99-#295/lib b/test/test-99-#295/lib new file mode 120000 index 000000000..7b6a06f01 --- /dev/null +++ b/test/test-99-#295/lib @@ -0,0 +1 @@ +./reallib \ No newline at end of file diff --git a/test/test-99-#295/main.js b/test/test-99-#295/main.js new file mode 100644 index 000000000..d8592e8de --- /dev/null +++ b/test/test-99-#295/main.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node + +'use strict'; + +const assert = require('assert'); +const utils = require('../utils.js'); + +// Enhanced SEA requires Node.js >= 22 +if (utils.getNodeMajorVersion() < 22) { + return; +} + +assert(__dirname === process.cwd()); + +// test symlinks on unix only // TODO junction +if (process.platform === 'win32') return; + +const input = './package.json'; +const testName = 'test-99-#295'; + +const newcomers = utils.seaHostOutputs(testName); + +const before = utils.filesBefore(newcomers); + +utils.runSeaHostOnly(input, testName); + +const expectedOutput = '42\n'; + +utils.assertSeaOutput(testName, expectedOutput); + +utils.filesAfter(before, newcomers, { tolerateWindowsEbusy: true }); diff --git a/test/test-99-#295/package.json b/test/test-99-#295/package.json new file mode 100644 index 000000000..a06b50260 --- /dev/null +++ b/test/test-99-#295/package.json @@ -0,0 +1,6 @@ +{ + "name": "test-99-#295", + "version": "1.0.0", + "main": "index.js", + "bin": "index.js" +} diff --git a/test/test-99-#295/reallib/log.js b/test/test-99-#295/reallib/log.js new file mode 100644 index 000000000..2e92b2d93 --- /dev/null +++ b/test/test-99-#295/reallib/log.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = console.log;