Skip to content

Commit de35965

Browse files
committed
refactor(fs): Stop fabricating inode 0 for in-memory resources
createStat() previously returned a synthetic statInfo with ino: 0, which Resource.clone() forwarded as statInfo into the new instance. The constructor's `this.#inode ??= statInfo.ino` then set the clone's inode to 0, so memory-only resources read through Memory.byPath / Memory.byGlob advertised a fabricated inode. That defeated the inode short-circuit in the build cache's isResourceUnchanged whenever cached metadata was later compared against an FS-backed read of the same path. Set ino to undefined in createStat() so memory-only resources and their clones report getInode() === undefined, leaving the cache to fall back on size + lastModified + integrity. Add Resource tests covering inode propagation through construction, clone, and setBuffer for both memory-only and FS-backed resources.
1 parent 34cc0d5 commit de35965

2 files changed

Lines changed: 63 additions & 1 deletion

File tree

packages/fs/lib/Resource.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,7 @@ function createStat(size, isDirectory = false, lastModified) {
954954
isSymbolicLink: fnFalse,
955955
isFIFO: fnFalse,
956956
isSocket: fnFalse,
957-
ino: 0,
957+
ino: undefined,
958958
size, // Might be undefined
959959
atimeMs: now.getTime(),
960960
mtimeMs: mtime.getTime(),

packages/fs/test/lib/Resource.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1979,3 +1979,65 @@ test("getIntegrity: Works with large content", async (t) => {
19791979
t.is(integrity, "sha256-j5kLoLV3tRzwCeoEk2jBa72hsh4bk74HqCR1i7JTw5s=",
19801980
"Correct integrity for large content");
19811981
});
1982+
1983+
test("getInode: Memory-only resource returns undefined", (t) => {
1984+
const resource = new Resource({
1985+
path: "/my/path/to/resource",
1986+
string: "content"
1987+
});
1988+
1989+
t.is(resource.getInode(), undefined,
1990+
"Resource constructed without statInfo or inode must not advertise an inode");
1991+
});
1992+
1993+
test("getInode: Clone of memory-only resource returns undefined", async (t) => {
1994+
const resource = new Resource({
1995+
path: "/my/path/to/resource",
1996+
string: "content"
1997+
});
1998+
1999+
const clone = await resource.clone();
2000+
2001+
t.is(clone.getInode(), undefined,
2002+
"Clone must not fabricate an inode for a memory-only resource");
2003+
});
2004+
2005+
test("getInode: FS-backed resource returns the real inode", async (t) => {
2006+
const fsPath = path.join("test", "fixtures", "application.a", "webapp", "index.html");
2007+
const statInfo = await stat(fsPath);
2008+
const resource = new Resource({
2009+
path: "/app/index.html",
2010+
statInfo,
2011+
createStream: () => createReadStream(fsPath)
2012+
});
2013+
2014+
t.is(resource.getInode(), statInfo.ino,
2015+
"Resource constructed with statInfo carries the real inode");
2016+
});
2017+
2018+
test("getInode: Clone of FS-backed resource preserves the inode", async (t) => {
2019+
const resource = createBasicResource();
2020+
const inode = resource.getInode();
2021+
2022+
const clone = await resource.clone();
2023+
2024+
t.not(inode, undefined, "Sanity: original resource has an inode");
2025+
t.is(clone.getInode(), inode,
2026+
"Clone preserves the inode of an FS-backed resource");
2027+
});
2028+
2029+
test("getInode: Preserved across setBuffer", (t) => {
2030+
const fsPath = path.join("test", "fixtures", "application.a", "webapp", "index.html");
2031+
const statInfo = statSync(fsPath);
2032+
const resource = new Resource({
2033+
path: "/app/index.html",
2034+
statInfo,
2035+
buffer: Buffer.from("original")
2036+
});
2037+
const inode = resource.getInode();
2038+
2039+
resource.setBuffer(Buffer.from("modified"));
2040+
2041+
t.is(resource.getInode(), inode,
2042+
"Inode is unchanged after setBuffer (same on-disk slot, content modified)");
2043+
});

0 commit comments

Comments
 (0)