Files
skills/docs/devtools/tooling/installer/src/fs/disk-fs.md
2026-05-29 08:20:56 +01:00

2.9 KiB

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

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

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<boolean> — never throws; missing path returns false.
  • stat(path): Promise<{ size, isFile, isDir }>
  • readFile(path): Promise<Uint8Array>
  • readJson<T>(path): Promise<T>
  • listDir(path): Promise<string[]> — sorted ascending.
  • writeFile(path, contents, mode?): Promise<void>
  • deleteFile(path): Promise<void> — no-op when absent (force: true).
  • mkdir(path): Promise<void> — recursive.
  • rename(from, to): Promise<void>
  • writeJson(path, value): Promise<void>

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 <cwd>/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.