# `disk-fs` ## Purpose Disk-backed implementation of `MutableInstallerFs` used by the `suaveplan-install` bin and any host-side code that wants to write to a real project directory. All reads and writes resolve relative to a single configurable `cwd`, so an installer run cannot accidentally escape the target project's root via absolute paths supplied by a third-party plugin. ## Features - Constructor accepts an explicit `cwd` (defaults to `process.cwd()`) and an optional `AbortSignal` — every async method short-circuits with an `AbortError` once the signal aborts. - `writeFile` and `rename` automatically create intermediate directories via `mkdir({ recursive: true })`. - Absolute paths supplied by callers (e.g. `/foo`) are resolved as *relative* to the configured `cwd`, never escaping it. This is the same policy `MemoryFs` uses, keeping the two backends interchangeable for tests vs. production. - `readFile` returns a true `Uint8Array` view (not a Node `Buffer`), so consumers that target browsers and servers behave identically. ## Basic Example ```ts import { DiskFs } from "@suaveplan/installer"; const fs = new DiskFs({ cwd: process.cwd() }); const bytes = await fs.readFile("package.json"); const pkg = JSON.parse(new TextDecoder().decode(bytes)) as { name: string; }; console.log(pkg.name); ``` ## Advanced Example ```ts import { DiskFs } from "@suaveplan/installer"; const ac = new AbortController(); process.once("SIGINT", () => ac.abort()); const fs = new DiskFs({ cwd: process.cwd(), signal: ac.signal }); await fs.mkdir(".suaveplan/skills"); await fs.writeJson(".suaveplan/skills/index.json", { schemaVersion: 1, skills: [], }); await fs.writeFile( ".suaveplan/skills/echo/SKILL.md", new TextEncoder().encode("# echo\n"), 0o644 ); ``` ## API Reference - `new DiskFs(options?: { cwd?, signal? })` - `pathOf(...segments): string` — resolves relative to `cwd`. - `exists(path): Promise` — never throws; missing path returns `false`. - `stat(path): Promise<{ size, isFile, isDir }>` - `readFile(path): Promise` - `readJson(path): Promise` - `listDir(path): Promise` — sorted ascending. - `writeFile(path, contents, mode?): Promise` - `deleteFile(path): Promise` — no-op when absent (`force: true`). - `mkdir(path): Promise` — recursive. - `rename(from, to): Promise` - `writeJson(path, value): Promise` ## Implementation Notes Internally, paths starting with `/` are stripped of the leading slash and re-resolved against `cwd` so they cannot escape. This is critical: a third party plugin that planned `{ kind: "write", path: "/etc/passwd" }` would otherwise overwrite a system file. With the strip-and-resolve policy, the write lands at `/etc/passwd` instead. Consumers who really want to write outside `cwd` must use `node:fs/promises` directly — the installer does not provide an escape hatch on purpose.