faf-cli 7.13.0 → 7.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/cli.js +279 -279
- package/dist/cli.js.map +28 -28
- package/dist/core/safe-write.d.ts +97 -0
- package/dist/core/slots.d.ts +2 -2
- package/dist/core/typed-none.d.ts +1 -1
- package/dist/detect/assemble.d.ts +1 -1
- package/dist/index.js +156 -156
- package/dist/index.js.map +24 -24
- package/package.json +1 -1
- package/project.faf +3 -3
|
@@ -1,3 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe file access — the one primitive every faf writer, and every read of
|
|
3
|
+
* project context, goes through. It is also the only module in faf that calls
|
|
4
|
+
* a filesystem write, rename, delete, mkdir or chmod API (tests/write-guard
|
|
5
|
+
* enforces that for all of src/).
|
|
6
|
+
*
|
|
7
|
+
* Rule 1 — stay inside the project. Before faf touches a path it resolves it on
|
|
8
|
+
* disk (realpath: every link followed). If that leads outside the project
|
|
9
|
+
* folder, faf refuses. A dangling link is refused — faf never creates a file at
|
|
10
|
+
* the end of a link. Anything whose real path is inside a `.git` folder is
|
|
11
|
+
* refused, always — with two narrow exceptions: faf's own git hook
|
|
12
|
+
* (`allowGitHooks`: one file directly inside the repo's hooks folder), and
|
|
13
|
+
* the repo's own git config file (`allowGitConfig`: the file `config`
|
|
14
|
+
* directly in the folder git names for it), where `faf diff
|
|
15
|
+
* --uninstall-driver` removes the section faf's install wrote.
|
|
16
|
+
*
|
|
17
|
+
* Rule 2 — a link leads to the same kind of file. faf follows a link only to a
|
|
18
|
+
* file with the same name (CLAUDE.md → docs/CLAUDE.md), or from one AI context
|
|
19
|
+
* file to another (CLAUDE.md → AGENTS.md; the set is FAF_CONTEXT_FILES). The
|
|
20
|
+
* link itself survives: faf writes the file it points at. `CLAUDE.md →
|
|
21
|
+
* README.md` or `project.html → package.json` is refused — faf would be
|
|
22
|
+
* writing a file it was never asked to write. A read of project context
|
|
23
|
+
* through a link must land on a .faf or .fafm file instead, so `project.faf →
|
|
24
|
+
* .env` is refused even though .env is in the project — or, for a read of an
|
|
25
|
+
* AI context file, on another AI context file (`faf recover` reads CLAUDE.md →
|
|
26
|
+
* AGENTS.md), the rule the writers use.
|
|
27
|
+
*
|
|
28
|
+
* Rule 3 — never leave a half-written file. A write goes to a temp file in the
|
|
29
|
+
* same folder, is flushed to disk (fsync), then renamed over the original in
|
|
30
|
+
* one step, keeping the original's permissions. If any step fails the temp
|
|
31
|
+
* file is removed and the original is exactly as it was: "not written;
|
|
32
|
+
* original kept". A plain writeFileSync truncates first, so a full disk, a
|
|
33
|
+
* quota or a killed process used to leave the user's file cut short. With
|
|
34
|
+
* `expect` (the bytes the caller read), the file is read again just before the
|
|
35
|
+
* rename and the write is refused if it changed in the meantime. Its mode is
|
|
36
|
+
* checked too: a file made read-only, or given other permissions, while faf
|
|
37
|
+
* was writing is not replaced.
|
|
38
|
+
*
|
|
39
|
+
* Rule 4 — text faf edits is UTF-8. readUtf8 decodes strictly: a UTF-16 file or
|
|
40
|
+
* any bytes that are not UTF-8 are refused, never turned into U+FFFD and
|
|
41
|
+
* written back.
|
|
42
|
+
*
|
|
43
|
+
* Rule 5 — a whole file faf renders replaces a file already there only when
|
|
44
|
+
* faf can prove it wrote it: project.html, the cards, `faf server-card --out`,
|
|
45
|
+
* a `faf taf --output` snapshot and a `faf decompile --output` file must be
|
|
46
|
+
* byte for byte what faf last wrote (their render hash, render-hash.ts); a
|
|
47
|
+
* `.fafb` must carry the FAFB header (see {@link safeReplaceOwned}). Anything
|
|
48
|
+
* else is refused unless the caller passes `force` (the CLI's `--force`).
|
|
49
|
+
*
|
|
50
|
+
* Rule 6 — detection reads stay inside the project. The files faf reads to fill
|
|
51
|
+
* slots and render context (README.md, package.json, pyproject.toml,
|
|
52
|
+
* Cargo.toml, go.mod, …) go through {@link repoFile}: resolved on disk, every
|
|
53
|
+
* link followed. A file whose real path, or the folder it sits in, leaves the
|
|
54
|
+
* project folder, runs through `.git`, or is a dangling link is absent to
|
|
55
|
+
* detection: it does not exist, and reading it gives nothing. A link that
|
|
56
|
+
* stays inside the project is followed (README.md → docs/README.md).
|
|
57
|
+
*/
|
|
58
|
+
import { type Dirent, type Stats } from 'fs';
|
|
1
59
|
/** Why a path was refused. */
|
|
2
60
|
export type SafePathReason = 'outside' | 'dangling' | 'not-a-file' | 'not-faf' | 'other-file' | 'git' | 'not-utf8' | 'changed' | 'not-owned' | 'not-yaml' | 'unplaceable';
|
|
3
61
|
/**
|
|
@@ -133,6 +191,45 @@ export declare function resolveInside(dir: string, name: string, opts?: ResolveI
|
|
|
133
191
|
export declare function readUtf8(path: string): string;
|
|
134
192
|
/** The bytes at `path`, or null when nothing is there (only ENOENT). */
|
|
135
193
|
export declare function readBytesIfPresent(path: string): Buffer | null;
|
|
194
|
+
/**
|
|
195
|
+
* Where detection may read `rel` in the project folder `dir` — its real path —
|
|
196
|
+
* or null when detection treats it as absent (Rule 6). `rel` names a file or a
|
|
197
|
+
* folder, relative to `dir` (as `join(dir, rel)`). Every link on the way is
|
|
198
|
+
* followed, the folders included; the result is null when:
|
|
199
|
+
* - nothing is there, or a link on the way dangles or loops
|
|
200
|
+
* - the real path leaves `dir` (README.md → ~/.aws/credentials, or a folder
|
|
201
|
+
* on the way that is a link out)
|
|
202
|
+
* - the real path runs through `.git` (README.md → .git/config)
|
|
203
|
+
* A link that stays inside `dir` is followed. `dir` is the folder detection
|
|
204
|
+
* was handed: the project, or a folder below it that detection reads on its
|
|
205
|
+
* own (a subfolder's manifest), so a link may not leave that folder either.
|
|
206
|
+
*/
|
|
207
|
+
export declare function repoFile(dir: string, rel: string): string | null;
|
|
208
|
+
/**
|
|
209
|
+
* Run `scan` with `root` as the project folder for every detection read under
|
|
210
|
+
* it: a read in a subfolder (web/package.json) may then follow a link to
|
|
211
|
+
* anywhere inside `root` (../shared/web-package.json), not only inside that
|
|
212
|
+
* subfolder. A link out of `root` is still absent. Subfolder scans use this.
|
|
213
|
+
*/
|
|
214
|
+
export declare function withRepoRoot<T>(root: string, scan: () => T): T;
|
|
215
|
+
/** True when `rel` (a file or a folder) is in the project folder `dir` for
|
|
216
|
+
* detection: see {@link repoFile}. */
|
|
217
|
+
export declare function repoExists(dir: string, rel: string): boolean;
|
|
218
|
+
/** The text of the file `rel` in the project folder `dir` (UTF-8, read the way
|
|
219
|
+
* detection reads it), or null when detection treats it as absent (see
|
|
220
|
+
* {@link repoFile}), it is not a regular file, or it cannot be read. */
|
|
221
|
+
export declare function readRepoFile(dir: string, rel: string): string | null;
|
|
222
|
+
/** What `rel` is (its real path's stats) in the project folder `dir`, or null
|
|
223
|
+
* when detection treats it as absent (see {@link repoFile}). */
|
|
224
|
+
export declare function statRepoFile(dir: string, rel: string): Stats | null;
|
|
225
|
+
/**
|
|
226
|
+
* The entries of the folder `rel` (default: `dir` itself) in the project
|
|
227
|
+
* folder `dir`, or null when detection treats the folder as absent (see
|
|
228
|
+
* {@link repoFile}) or it cannot be read. A link among them that leaves `dir`,
|
|
229
|
+
* dangles, or leads into `.git` is left out: detection does not see it. Each
|
|
230
|
+
* entry says what it is itself (a link is a link, as readdirSync gives it).
|
|
231
|
+
*/
|
|
232
|
+
export declare function readRepoDir(dir: string, rel?: string): Dirent[] | null;
|
|
136
233
|
/**
|
|
137
234
|
* A write that failed partway — a full disk, a quota, a read-only file, a
|
|
138
235
|
* killed rename — with the file on disk exactly as it was: "<file>: not
|
package/dist/core/slots.d.ts
CHANGED
|
@@ -32,8 +32,8 @@ export declare const PLACEHOLDERS: Set<string>;
|
|
|
32
32
|
* Only in a tech slot the app-type leaves out does faf auto write
|
|
33
33
|
* `slotignored` over them — the app-type's decision, never the words'. */
|
|
34
34
|
export declare const EXPLICIT_NONE: ReadonlySet<string>;
|
|
35
|
-
/** The value that marks a slot as not
|
|
36
|
-
* only from the app-type
|
|
35
|
+
/** The value that marks a slot as not required and not scored (the What-Not).
|
|
36
|
+
* It comes only from the app-type; a typed word never means it. */
|
|
37
37
|
export declare const SLOTIGNORED = "slotignored";
|
|
38
38
|
/** True for a typed none — counts as an empty slot: `None`, `N/A`,
|
|
39
39
|
* `not applicable`, `none` (any case, any padding). */
|
|
@@ -15,7 +15,7 @@ export interface TypedNoneHintOptions {
|
|
|
15
15
|
* - a slot the app-type needs:
|
|
16
16
|
* `<slot> says '<words>' — this app-type needs it, so it counts as empty until filled.`
|
|
17
17
|
* - a slot the app-type leaves out (before `faf auto` has run):
|
|
18
|
-
* `<slot> says '<words>' — this app-type doesn't use it; faf auto marks it slotignored
|
|
18
|
+
* `<slot> says '<words>' — this app-type doesn't use it; faf auto marks it slotignored.`
|
|
19
19
|
* A file whose type faf does not know, or whose type is the `library`
|
|
20
20
|
* detection fell back to (see {@link typeIsFallback}), gives the first line
|
|
21
21
|
* for the slots a library needs (as faf's detection reads such a file) and no
|
|
@@ -29,7 +29,7 @@ export declare function assembleFreshFaf(dir: string): Record<string, unknown>;
|
|
|
29
29
|
* has them, comment included; faf never writes `''` over them.
|
|
30
30
|
* - In a tech slot the file's app-type (`project.type`) leaves out, the
|
|
31
31
|
* app-type's decision is the fact: with no repo fact, the slot becomes
|
|
32
|
-
* `slotignored`
|
|
32
|
+
* `slotignored` in place of the words, an empty value or a
|
|
33
33
|
* placeholder. A real value there is kept. `slotignored` comes only from
|
|
34
34
|
* the app-type — in a slot the app-type uses, it is never written, even
|
|
35
35
|
* when detection reads the repo as another type; such a slot gets the
|