import { type Document } from 'yaml'; import type { FafData } from '../core/types.js'; import { type KeptAlias } from '../core/yaml-edit.js'; /** Run `call` — a call into faf's scoring kernel with the text of the .faf * at `path` (or the bytes of the .fafb there) — and turn a rejection by the * kernel into the one-line refusal. The kernel throws a bare string (or an * Error) for text it cannot read, some of which yaml reads (a 30-digit * integer, nesting past the kernel's depth limit); that becomes a * SafePathError (`not-yaml`): ": faf's scoring kernel could not read * it () — faf left it unchanged". A SafePathError from `call` passes * through as it is. Every command that hands project.faf (or a .fafb) to * the kernel calls it through here, after it has read the file as a .faf * ({@link readFaf}, or {@link readFafFromString} with the path); one that * edits project.faf (`faf auto`, `faf go`, `faf sync --direction pull`) * asks the kernel before it writes, so the line is true. `faf diff` and * `faf log` score a version the kernel cannot read as 0 instead. */ export declare function withKernel(path: string, call: () => T): T; /** Read and parse a .faf file. Always a mapping: an empty file reads as `{}`; * a file that parses to a scalar or a list throws a SafePathError * (`not-yaml`, one line) instead of handing callers a value they would * spread into character keys. A file * that is not valid YAML throws a SafePathError (`not-yaml`): " is not * valid YAML (, line N) — faf left it unchanged". A link that * leaves the folder, or does not end at a .faf/.fafm file, is refused, and so * is a file that is not UTF-8. The data remembers the text it was read from: * writeFaf refuses to write it back over a file that changed since. */ export declare function readFaf(path: string): FafData; /** Serialize .faf data to YAML text — the exact bytes writeFaf writes for a * new file (an existing file is updated in place instead). * * If `data._meta.found` is present, it's stripped before serialization and * rendered as a `# found: ` YAML comment next to the `type:` field — * Glass Hood doctrine: the user sees WHY the cli classified the project as * it did. `_meta` is a runtime hint, never a serialized .faf field. Used by * writeFaf and by `faf git --stdout`. */ export declare function serializeFaf(data: FafData): string; /** What {@link updateFafFile} did. */ export interface UpdateFafResult { /** True when the file changed on disk; false when the change was a no-op and * nothing was written. */ written: boolean; /** The file's text after the update (its original text when nothing changed). */ text: string; /** Aliases the change would have replaced, left as written: `path` is where * (`stack`), `alias` what the file says there (`*base`). faf never replaces * or expands an alias; the change at that path is not written. */ keptAliases?: KeptAlias[]; } export type { KeptAlias }; /** * Update an existing .faf in place, keeping every byte the change does not * touch. The file is parsed with yaml's `parseDocument`; `mutate` changes the * Document through its node APIs (`doc.setIn(['stack', 'database'], 'Postgres')`, * `doc.deleteIn([...])`, `doc.getIn([...], true)`); only the text of the nodes * that changed is rewritten. Comments, blank lines, key order, quoting, scalar * source text (`version: 1.10`, `0x1F90`, a 20-digit integer), anchors, unknown * keys (a user's own `_meta` included), CRLF, a BOM and the `%YAML` / `---` / * `...` framing survive — nothing folds at 80 columns. When the change leaves * the data as it was, nothing is written (`written: false`), even if faf's own * layout of the file would differ. * * An alias the change replaces (`doc.set('stack', …)` over `stack: *base`) * is put back: faf never replaces or expands an alias, so that path stays as * written and is listed in `keptAliases`; the rest of the change is written. * * The file must exist. The same link rules as {@link readFaf} (a link must * stay in the folder and end at a .faf/.fafm file) and the atomic write of * `safeWriteFile` apply; the write itself goes only through a link to a file * of the same name. A file that is not valid YAML (SafePathError `not-yaml`, * one line), or not UTF-8, is refused (nothing written); so is a file that * parses to a scalar or a list — the same `not-yaml` refusal readFaf gives, * before `mutate` is called — anything `mutate` throws, and a file that * changed on disk while faf was writing. */ export declare function updateFafFile(path: string, mutate: (doc: Document) => void): UpdateFafResult; /** Options for {@link writeFaf}. */ export interface WriteFafOptions { /** Write a fresh file from `data` even when one exists — the explicit * overwrite (`faf init --force`, `faf git --force`). Default: an existing * file is updated in place with {@link updateFafFile}. */ replace?: boolean; /** Called for each alias of an existing file that `data` would change * (`stack: *base` while `data.stack` has more keys): faf leaves it as * written — it never replaces or expands an alias — and that path is not * written. For a fill (data from updateExistingFaf) it is also called for * each node with an anchor an alias reads that the fill would change * (`kind: 'anchor'`). `faf auto` prints one line for each. */ onAliasKept?: (kept: KeptAlias) => void; } /** The one line faf prints for an alias (or an anchor an alias reads) it left as written. */ export declare function aliasKeptNote(kept: KeptAlias): string; /** Write a .faf file from data — atomically (temp file, fsync, rename; a * failure leaves the original as it was), and never through a link that * leaves the folder or dangles (SafePathError). An in-project link is written * through and stays a link. * * An existing file is updated in place ({@link updateFafFile}): `data` is * compared with what the file holds, and only the values that changed are * rewritten — so comments, formatting, source text and key order survive, a * value `data` merely repeats (the text an alias `*g` read as, say) never * replaces the node the file has, and a write that changes nothing writes * nothing. A key the file has and `data` leaves out is kept (faf removes * nothing it did not write). Pass `{ replace: true }` to overwrite the file * with a fresh render instead. Returns false when nothing was written. * * An alias in the file (`stack: *base`) is never replaced or expanded: it * stays as written, and a change `data` makes under it is not written — * `opts.onAliasKept` hears of each such path. When `data` is a fill (from * updateExistingFaf), a node with an anchor that an alias reads is left as * written as well (filling it would change every alias), and reported the * same way with `kind: 'anchor'`. * * When `data` came from readFaf of this file (directly, or through * updateExistingFaf), the write is refused if the file changed on disk since * that read (SafePathError `changed`: "not written; original kept"), so an * edit made meanwhile is never written over. A new file is not written over * one that appeared meanwhile. */ export declare function writeFaf(path: string, data: FafData, opts?: WriteFafOptions): boolean; /** Read raw YAML text from a .faf file (the same link rules as readFaf; a * file that is not UTF-8 is refused). */ export declare function readFafRaw(path: string): string; /** Parse .faf data from a YAML string — e.g. the output of `git show :project.faf`. * The readers above are path-only; `faf diff` needs to parse a version that * lives in git history, never on disk. With `path` (the file the text was * read from) the text is read as {@link readFaf} reads a file: text that is * not valid YAML throws a SafePathError (`not-yaml`) naming it, and so does * text that parses to a scalar or a list (faf's shape check, * `asFafMapping`); empty text reads as `{}`. Without `path`, the parsed * value as it is, or yaml's own error. */ export declare function readFafFromString(text: string, path?: string): FafData; /** Find the .faf file in a directory (walks up one level). The path is * returned as spelled; a symlinked candidate must pass the readFaf link rules * or this throws a SafePathError. */ export declare function findFafFile(dir?: string): string | null; /** Repo-root-relative path of a .faf, computed BY git — runtime- and OS-independent. * Replaces `path.relative(top, fafPath)`, which breaks on Windows when git's * long-form `--show-toplevel` and an 8.3 short-name cwd (e.g. `RUNNER~1`) disagree. * git resolves both representations internally and emits forward slashes; works for * tracked AND untracked .faf (it's the cwd's location under the root, not file status). * Caller must already be inside a git repo. */ export declare function gitRepoRel(fafPath: string, cwd?: string): string;