// Adapts the runtime's host fs (async RPC to the kernel's pid-0 process) to bashkit's // `HostFileSystem`, so bash sees the same VFS the booted app's own `node` processes do. // readDir reports symlinks as themselves; stat follows them, so it only ever sees // "file" or "dir". const kind = (k) => (k === "dir" ? "directory" : k === "symlink" ? "symlink" : "file"); // The host's rejections already carry a Node `code` (ENOENT, EEXIST, ...), but they cross // a worker postMessage boundary on the way here; rewrap as a plain Error so bashkit's own // `code` check — and the bash message it prints for that code — sees what it expects. function wrapError(e) { const err = new Error(e.message); err.code = e.code ?? "EIO"; return err; } /** * @param {import("../../src/host/index").Kernel} runtime * @returns {import("@everruns/bashkit-wasm").HostFileSystem} */ export function hostFs(runtime) { const fs = runtime.fs; return { async read(path) { try { return await fs.readFile(path); } catch (e) { throw wrapError(e); } }, async write(path, content) { try { await fs.writeFile(path, content); } catch (e) { throw wrapError(e); } }, async mkdir(path, recursive) { try { await fs.mkdir(path, { recursive }); } catch (e) { throw wrapError(e); } }, // A directory rmdirs (recursively, when asked); a file unlinks. async remove(path, recursive) { try { const st = await fs.stat(path); if (st.kind === "dir") await fs.rm(path, { recursive }); else await fs.unlink(path); } catch (e) { throw wrapError(e); } }, async stat(path) { try { const st = await fs.stat(path); return { type: kind(st.kind), size: st.size, mode: st.mode, mtimeMs: st.mtimeMs }; } catch (e) { throw wrapError(e); } }, async readDir(path) { try { const entries = await fs.readdir(path); return Promise.all(entries.map(async (d) => { const full = `${path === "/" ? "" : path}/${d.name}`; const st = await fs.stat(full).catch(() => null); return { name: d.name, type: kind(d.kind), size: st?.size, mode: st?.mode, mtimeMs: st?.mtimeMs }; })); } catch (e) { throw wrapError(e); } }, // Must not throw for a plain miss. async exists(path) { return fs.stat(path).then(() => true, () => false); }, async rename(from, to) { try { await fs.rename(from, to); } catch (e) { throw wrapError(e); } }, async symlink(target, link) { try { await fs.symlink(target, link); } catch (e) { throw wrapError(e); } }, async readLink(path) { try { return await fs.readlink(path); } catch (e) { throw wrapError(e); } }, }; }