2.5 KiB
installer-fs
Purpose
Re-exports the read-only InstallerFs interface from the types module and
declares its mutating extension MutableInstallerFs. Plugins receive a
read-only InstallerFs through the context — they can describe writes via
FileOp[] but cannot perform them directly. The host uses
MutableInstallerFs internally to materialise ops with rollback. Keeping
the read/write split at the interface level is what makes --dry-run
trivially correct: a dry-run never resolves the mutating subset.
Features
- Single source of truth for filesystem capability boundaries inside the installer.
MutableInstallerFsextendsInstallerFswithwriteFile,deleteFile,mkdir,rename, andwriteJson.- Both
MemoryFsandDiskFsimplementMutableInstallerFs, but only the read-only view is ever handed to plugin code.
Basic Example
import { MemoryFs, type InstallerFs } from "@suaveplan/installer";
async function describe(fs: InstallerFs): Promise<number> {
const entries = await fs.listDir("/");
return entries.length;
}
const fs = new MemoryFs({ seed: { "/a.txt": "hi" } });
await describe(fs);
Advanced Example
import {
type MutableInstallerFs,
MemoryFs,
} from "@suaveplan/installer";
async function backupThenWrite(
fs: MutableInstallerFs,
path: string,
next: Uint8Array
): Promise<void> {
if (await fs.exists(path)) {
const prior = await fs.readFile(path);
await fs.writeFile(`${path}.bak`, prior);
}
await fs.writeFile(path, next);
}
await backupThenWrite(new MemoryFs(), "/cfg.json", new Uint8Array([123, 125]));
API Reference
interface InstallerFs(re-export fromtypes/installer.types.ts):exists,readFile,readJson,stat,listDir,pathOf.interface MutableInstallerFs extends InstallerFs:writeFile(path, contents, mode?),deleteFile(path),mkdir(path),rename(from, to),writeJson(path, value).
Implementation Notes
MutableInstallerFs is exposed publicly through ./fs so authors who want
to write a custom backing store (e.g. an S3-backed installer fs) have a
single contract to satisfy. Implementations must honour any AbortSignal
they were constructed with — both in-tree implementations call a shared
ensureNotAborted(signal) helper at the start of every async operation.
The interface deliberately has no chmod or chown operations: per-file
mode is carried on the WriteOp itself, and ownership is left to the
host filesystem.