Skip to content

Commit af0c086

Browse files
robertsLandoclaude
andauthored
fix(sea): strip trailing slashes in manifest key lookup (#261)
SEA provider's toManifestKey did not strip trailing separators, so fs.access('/snapshot/.../dist/') and similar calls missed manifest keys (which are stored without trailing slashes) and threw ENOENT. Classic (non-SEA) bootstrap already normalizes via removeTrailingSlashes() in lib/common.ts — this brings SEA to parity. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 976f35b commit af0c086

3 files changed

Lines changed: 52 additions & 7 deletions

File tree

prelude/sea-vfs-setup.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -183,17 +183,34 @@ try {
183183
}
184184
perf.end('manifest parse');
185185

186-
// Manifest keys are always POSIX (forward slashes, no drive letter). Gate
187-
// the regex on platform: on POSIX hosts paths already match, so the replace
188-
// is a pure allocation and we skip it (~30K calls per startup on large
189-
// projects). On Windows, backslash normalization is mandatory.
186+
// Manifest keys are always POSIX (forward slashes, no drive letter) and
187+
// carry no trailing separator. Gate the backslash regex on platform: on
188+
// POSIX hosts paths already match, so the replace is a pure allocation and
189+
// we skip it (~30K calls per startup on large projects). On Windows,
190+
// backslash normalization is mandatory.
191+
//
192+
// Trailing separators are stripped on both platforms so lookups with paths
193+
// like `/snapshot/.../dist/` — common when a path is joined with a blank
194+
// segment or produced by libraries that append `/` to directory paths —
195+
// match the non-slashed manifest keys. The root '/' is preserved as-is.
196+
// Mirrors removeTrailingSlashes() in lib/common.ts, which handles the same
197+
// case for the classic (non-SEA) bootstrap.
198+
function _stripTrailingSeps(p) {
199+
var i = p.length;
200+
while (i > 1) {
201+
var c = p.charCodeAt(i - 1);
202+
if (c !== 47 /* / */ && c !== 92 /* \\ */) break;
203+
i--;
204+
}
205+
return i === p.length ? p : p.slice(0, i);
206+
}
190207
var toManifestKey =
191208
process.platform === 'win32'
192209
? function (p) {
193-
return p.replace(/\\/g, '/');
210+
return _stripTrailingSeps(p.replace(/\\/g, '/'));
194211
}
195212
: function (p) {
196-
return p;
213+
return _stripTrailingSeps(p);
197214
};
198215

199216
function _enoent(syscall, filePath) {

test/test-89-sea-fs-ops/index.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,26 @@ try {
4040
} catch (e) {
4141
console.log('read-missing:' + e.code);
4242
}
43+
44+
// Regression: directory lookups with trailing slash must resolve (issue #260).
45+
// Libraries like nestjs-i18n append '/' to __dirname; classic pkg strips it
46+
// via normalizePath, SEA must do the same in the manifest-key normalizer.
47+
const dirSlash = __dirname + '/';
48+
console.log('exists-dir-slash:' + fs.existsSync(dirSlash));
49+
console.log('stat-dir-slash-isDir:' + fs.statSync(dirSlash).isDirectory());
50+
console.log('readdir-dir-slash:' + fs.readdirSync(dirSlash).sort().join(','));
51+
try {
52+
fs.accessSync(dirSlash);
53+
console.log('accessSync-dir-slash:ok');
54+
} catch (e) {
55+
console.log('accessSync-dir-slash:' + e.code);
56+
}
57+
58+
require('fs/promises')
59+
.access(dirSlash)
60+
.then(function () {
61+
console.log('access-promise-dir-slash:ok');
62+
})
63+
.catch(function (e) {
64+
console.log('access-promise-dir-slash:' + e.code);
65+
});

test/test-89-sea-fs-ops/main.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ const expectedOutput =
3131
'readdir:data.json,index.js,package.json\n' +
3232
'readFile:ok\n' +
3333
'stat-missing:ENOENT\n' +
34-
'read-missing:ENOENT\n';
34+
'read-missing:ENOENT\n' +
35+
'exists-dir-slash:true\n' +
36+
'stat-dir-slash-isDir:true\n' +
37+
'readdir-dir-slash:data.json,index.js,package.json\n' +
38+
'accessSync-dir-slash:ok\n' +
39+
'access-promise-dir-slash:ok\n';
3540

3641
utils.assertSeaOutput(testName, expectedOutput);
3742

0 commit comments

Comments
 (0)