2026-03-07 15:42:19 +01:00
|
|
|
export function createFsSync(metadataCache, contentCache, transport) {
|
|
|
|
|
return {
|
|
|
|
|
existsSync(path) {
|
|
|
|
|
return metadataCache.has(path);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
statSync(path) {
|
|
|
|
|
const stat = metadataCache.toStat(path);
|
|
|
|
|
if (!stat) {
|
2026-03-10 20:49:10 +01:00
|
|
|
const err = new Error(
|
|
|
|
|
`ENOENT: no such file or directory, stat '${path}'`,
|
|
|
|
|
);
|
|
|
|
|
err.code = "ENOENT";
|
2026-03-07 15:42:19 +01:00
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
return stat;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
accessSync(path, mode) {
|
|
|
|
|
if (!metadataCache.has(path)) {
|
2026-03-10 20:49:10 +01:00
|
|
|
const err = new Error(
|
|
|
|
|
`ENOENT: no such file or directory, access '${path}'`,
|
|
|
|
|
);
|
|
|
|
|
err.code = "ENOENT";
|
2026-03-07 15:42:19 +01:00
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
readFileSync(path, encoding) {
|
2026-03-10 20:49:10 +01:00
|
|
|
if (typeof encoding === "object") encoding = encoding?.encoding;
|
|
|
|
|
|
|
|
|
|
const meta = metadataCache.get(path);
|
|
|
|
|
if (meta && meta.type === "directory") {
|
|
|
|
|
const e = new Error("EISDIR: illegal operation on a directory, read");
|
|
|
|
|
e.code = "EISDIR";
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
2026-03-07 15:42:19 +01:00
|
|
|
|
|
|
|
|
const cached = contentCache.get(path);
|
|
|
|
|
if (cached !== null) {
|
2026-03-10 20:49:10 +01:00
|
|
|
if (encoding === "utf8" || encoding === "utf-8") {
|
|
|
|
|
return typeof cached === "string"
|
|
|
|
|
? cached
|
|
|
|
|
: new TextDecoder().decode(cached);
|
2026-03-07 15:42:19 +01:00
|
|
|
}
|
|
|
|
|
return cached;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 20:49:10 +01:00
|
|
|
console.warn("[shim:fs] readFileSync cache miss, using sync XHR:", path);
|
2026-03-07 15:42:19 +01:00
|
|
|
const data = transport.readFileSync(path, encoding);
|
|
|
|
|
contentCache.set(path, data);
|
|
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
writeFileSync(path, data, encoding) {
|
2026-03-10 20:49:10 +01:00
|
|
|
if (typeof encoding === "object") encoding = encoding?.encoding;
|
2026-03-07 15:42:19 +01:00
|
|
|
|
|
|
|
|
contentCache.set(path, data);
|
2026-03-10 20:49:10 +01:00
|
|
|
const size =
|
|
|
|
|
typeof data === "string" ? data.length : data.byteLength || 0;
|
2026-03-07 15:42:19 +01:00
|
|
|
metadataCache.set(path, {
|
2026-03-10 20:49:10 +01:00
|
|
|
type: "file",
|
2026-03-07 15:42:19 +01:00
|
|
|
size,
|
|
|
|
|
mtime: Date.now(),
|
|
|
|
|
ctime: metadataCache.get(path)?.ctime || Date.now(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Fire-and-forget async send to server
|
|
|
|
|
transport.writeFile(path, data, encoding).catch((e) => {
|
2026-03-10 20:49:10 +01:00
|
|
|
console.error(
|
|
|
|
|
"[shim:fs] writeFileSync background save failed:",
|
|
|
|
|
path,
|
|
|
|
|
e,
|
|
|
|
|
);
|
2026-03-07 15:42:19 +01:00
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
unlinkSync(path) {
|
|
|
|
|
contentCache.delete(path);
|
|
|
|
|
metadataCache.delete(path);
|
|
|
|
|
|
2026-03-12 22:32:39 +01:00
|
|
|
// Fire-and-forget. suppress ENOENT (file already gone)
|
2026-03-07 15:42:19 +01:00
|
|
|
transport.unlink(path).catch((e) => {
|
2026-03-10 20:49:10 +01:00
|
|
|
if (e.code !== "ENOENT") {
|
|
|
|
|
console.error(
|
|
|
|
|
"[shim:fs] unlinkSync background delete failed:",
|
|
|
|
|
path,
|
|
|
|
|
e,
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-03-07 15:42:19 +01:00
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
readdirSync(path) {
|
|
|
|
|
const entries = metadataCache.readdir(path);
|
2026-03-10 20:49:10 +01:00
|
|
|
return entries.map((e) => e.name);
|
2026-03-07 15:42:19 +01:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|