import type { VirtualFileSystem, VirtualStat, VirtualDirEntry } from "secure-exec";
import {
NodeRuntime,
allowAllFs,
createNodeDriver,
createNodeRuntimeDriverFactory,
} from "secure-exec";
class ReadOnlyMapFS implements VirtualFileSystem {
private files: Map<string, string>;
constructor(files: Record<string, string>) {
this.files = new Map(Object.entries(files));
}
async readFile(path: string) {
const content = this.files.get(path);
if (content === undefined) throw new Error(`ENOENT: ${path}`);
return new TextEncoder().encode(content);
}
async readTextFile(path: string) {
const content = this.files.get(path);
if (content === undefined) throw new Error(`ENOENT: ${path}`);
return content;
}
async exists(path: string) {
return this.files.has(path) || this.#isDir(path);
}
async stat(path: string): Promise<VirtualStat> {
const now = Date.now();
if (this.files.has(path)) {
return {
mode: 0o444,
size: new TextEncoder().encode(this.files.get(path)!).byteLength,
isDirectory: false,
atimeMs: now, mtimeMs: now, ctimeMs: now, birthtimeMs: now,
};
}
if (this.#isDir(path)) {
return {
mode: 0o555,
size: 0,
isDirectory: true,
atimeMs: now, mtimeMs: now, ctimeMs: now, birthtimeMs: now,
};
}
throw new Error(`ENOENT: ${path}`);
}
async lstat(path: string) { return this.stat(path); }
async readDir(path: string) {
const prefix = path === "/" ? "/" : path + "/";
const entries = new Set<string>();
for (const key of this.files.keys()) {
if (key.startsWith(prefix)) {
const rest = key.slice(prefix.length);
entries.add(rest.split("/")[0]);
}
}
if (entries.size === 0) throw new Error(`ENOENT: ${path}`);
return [...entries];
}
async readDirWithTypes(path: string): Promise<VirtualDirEntry[]> {
const names = await this.readDir(path);
const prefix = path === "/" ? "/" : path + "/";
return names.map((name) => ({
name,
isDirectory: this.#isDir(prefix + name),
}));
}
// Write operations throw — this filesystem is read-only
async writeFile() { throw new Error("EROFS: read-only filesystem"); }
async createDir() { throw new Error("EROFS: read-only filesystem"); }
async mkdir() { throw new Error("EROFS: read-only filesystem"); }
async removeFile() { throw new Error("EROFS: read-only filesystem"); }
async removeDir() { throw new Error("EROFS: read-only filesystem"); }
async rename() { throw new Error("EROFS: read-only filesystem"); }
async symlink() { throw new Error("EROFS: read-only filesystem"); }
async readlink() { throw new Error("ENOSYS: no symlinks"); }
async link() { throw new Error("EROFS: read-only filesystem"); }
async chmod() { throw new Error("EROFS: read-only filesystem"); }
async chown() { throw new Error("EROFS: read-only filesystem"); }
async utimes() { throw new Error("EROFS: read-only filesystem"); }
async truncate() { throw new Error("EROFS: read-only filesystem"); }
#isDir(path: string) {
const prefix = path === "/" ? "/" : path + "/";
for (const key of this.files.keys()) {
if (key.startsWith(prefix)) return true;
}
return false;
}
}