type FST = { existsSync(path: string): boolean; readFileSync(path: string, options?: any): any; writeFileSync(path: string, data: any, options?: any): void; mkdirSync(path: string, options?: any): any; statSync(path: string, options?: any): any; readdirSync(path: string, options?: any): any; renameSync?: (from: string, to: string) => void; chmodSync?: (path: string, mode: any) => void; unlinkSync?: (path: string) => void; realpathSync?: (path: string) => any; }; type JostracaResult = { when: number; files: { preserved: string[]; written: string[]; presented: string[]; diffed: string[]; merged: string[]; conflicted: string[]; unchanged: string[]; }; audit: () => Audit[]; vol?: () => any; fs?: () => FST; }; /** * One way a committed file differs from what the generators produce. * * `kind` says which question failed, and the bytes are carried rather * than rendered: a CLI wants a diff, a test wants an assertion, and a * report wants a count. Rendering is the caller's. */ type CheckDrift = { /** Path relative to the checked folder, forward slashes. */ path: string; /** * `missing`: the generators claim it and the folder does not hold it. * `content`: the bytes differ. * `mode`: the bytes match and the permission bits do not, where the * tree stated them with `File.mode`. */ kind: 'missing' | 'content' | 'mode'; /** * What the generators produce. Absent for no kind -- a missing file * still has generated bytes, which is what would be written. */ generated?: Buffer; /** What the folder holds. Absent when `kind` is `missing`. */ existing?: Buffer; /** * The mode the tree declared, and the bits on disk. Both present only * when `kind` is `mode`. */ mode?: number; existingMode?: number; }; /** * What `check()` answers: the tree the generators produce, held against * the tree on disk. */ type CheckResult = { /** The folder that was checked, as given. */ folder: string; /** * Every path compared, relative and sorted. A file on disk that the * generators do not claim is not here: see the `check` reference for * why an unclaimed file is left alone. */ checked: string[]; /** * Sorted by path. Empty means the folder is what the generators * produce. */ drift: CheckDrift[]; /** * The run report of the generate that produced the comparison, for a * caller that wants to say what ran. */ files: JostracaResult["files"]; }; type Node = { kind: string; meta: any; content: any; children?: Node[]; name?: string; path: string[]; from?: string; folder?: string; after?: any; exclude?: boolean | string | RegExp | (string | RegExp)[]; mode?: number; indent?: string | number; filter?: (props: any, children: any, component: any) => boolean; fullpath?: string; replace?: Record; }; type OpStep = (node: Node, ctx$: any, buildctx: any) => Promise | void; type OpDef = { before: OpStep; after: OpStep; }; /** * The ambient context a component runs in. * * Written into every props object by `cmp()` as `ctx$`, and never by an * author: a component reads it, a caller does not supply it. That is why * no props type carries it -- a props type says what a caller writes, * and `cmp()` adds this on the way in. */ type CmpContext = { /** The node this component is defining. Filling it in is the whole job. */ node: Node; /** * Where this component's own children accumulate, and the root of the * tree the define phase is building. */ children: Node[]; root: Node; /** The generate model, in scope for every template rendered. */ model: any; /** Options as the run was started with, and the caller's `meta`. */ opts: any; meta: any; /** The run's output folder, and the filesystem in force for it. */ folder: string; fs: () => FST; log: Log; debug: boolean; now: () => number; content: any; }; /** * Props as a component body sees them: what the caller wrote, plus the * ambient context `cmp()` writes in under `ctx$`. */ type CmpProps

= P & { ctx$: CmpContext; }; /** * One child of a component: the callback that defines it. * * The callback receives the args its parent bound for this invocation -- * `ListItems` binds `{item, indent, replace}` (see `ListItemProps`), and * `Project` binds its own props -- which is why the parameter is here * rather than the callback being nullary. * * Literal text is not here. Three components take text as a child and * seven do not, so it rides on `Component`'s `Child` parameter instead * of being admitted for all ten. */ type CmpChild = (args: any) => any; /** One child, or a list of them. */ type CmpChildren = CmpChild | CmpChild[]; /** * A component, as a caller sees it. * * `P` is the props the component reads; each component exports its own * (`ContentProps`, `FileProps`, ...). * * `Arg` is the positional form: `Content('text')`, which `cmp()` reads * as `Content({arg: 'text'})`. `Child` is literal text in the children * position: `Content({indent: 2}, 'text')`. Two components take the * first, three take the second, and both stay at `never` everywhere * else -- which makes `File('text')` a type error rather than the * silent no-op it is at run time, since `File` never reads `arg`. * * The first signature is the props-less call, `File(() => {...})`: * `cmp()` reads a lone function or array as the children. * * `P` defaults to `any`, so `cmp(function My(props: any) {...})` still * types as it always did -- a component written outside this package * declares whatever it likes, and declares nothing if it likes that. * * PROPS LAST, and the order is load-bearing. When no overload matches, * TypeScript reports the LAST one's error, so `File({nosuchprop: 1})` * has to end at the props signature to be told about `FileProps` -- * with the signatures the other way round it was told that * `nosuchprop` does not exist on `CmpChild[] | ((args: any) => any)`, * which is an answer to a question nobody asked. */ type Component

= { (children: CmpChildren | Arg): void; (props: P, children?: CmpChildren | Child): void; }; type Log = { trace: (...args: any[]) => any; debug: (...args: any[]) => any; info: (...args: any[]) => any; warn: (...args: any[]) => any; error: (...args: any[]) => any; fatal: (...args: any[]) => any; }; type FileEntry = { path: string; action: 'write' | 'preserve' | 'present' | 'diff'; copy?: string; }; type Audit = [string, any][]; export type { JostracaResult, CheckDrift, CheckResult, Node, OpStep, OpDef, Component, CmpContext, CmpProps, CmpChild, CmpChildren, Log, FileEntry, FST, Audit, };