/** * Cordis-free local filesystem mechanics. This provider layer returns validated UTF-8 text, * streams large files, and rejects binary data; line windows belong to `dsh-tool-fs`. Writes * stage an exclusive owner-only file in a private sibling directory and atomically publish it. * @module @deepseek-ai/dsh-fs-local/fsio */ import type { BigIntStats } from 'node:fs'; import { FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs'; /** * Test hook: lets specs pin the atomic-write temp names (to prove exclusive-open behavior without * a name race), override native boundaries, and observe the staged temp file before publication. */ export interface FsIoInternals { /** Override the host platform for native-publication unit coverage. */ platform?: NodeJS.Platform; /** Override the generated private staging-dir name (relative to the target dir). */ tempDirName?: (writePath: string) => string; /** Override the generated temp-file name (relative to the private staging dir). */ tempName?: (writePath: string) => string; /** Override the Win32 DACL copy boundary. */ copyFileDacl?: (source: string, destination: string) => Promise; /** Override the Win32 security-preserving replacement boundary. */ replaceFile?: (replaced: string, replacement: string) => Promise; /** Override the hard-link no-replace publication boundary. */ linkFile?: (existingPath: string, newPath: string) => Promise; /** Override target inspection after guarded publication fails. */ inspectPublicationTarget?: (path: string) => Promise; /** Override staging-directory removal for commit-point failure coverage. */ removeStagingDir?: (stagingDir: string) => Promise; /** Test hook after the temp file is written/synced but before final chmod+publication. */ inspectTemp?: (paths: { stagingDir: string; tempPath: string; }) => void | Promise; } /** A resolved local path: the absolute path shown to callers and its realpath identity. */ export interface LocalTarget { /** Absolute path (symlinks not resolved) — used for display. */ displayPath: string; /** Realpath identity — used as the stable target key and the I/O path. */ targetKey: FsTargetKey; } /** Result of probing a path: null when it does not exist. */ export interface PathInfo { version: FsVersion; mode: number; type: 'file' | 'directory' | 'other'; size: number; } /** Result of probing a path without following the final symlink component. */ export interface PathLinkInfo { version: FsVersion; mode: number; type: 'file' | 'directory' | 'symlink' | 'other'; size: number; } /** One local directory child with a resolved target and cheap metadata. */ export interface LocalDirEntry { name: string; type: 'file' | 'directory' | 'other'; target: LocalTarget; version?: FsVersion; size?: number; } /** * Resolve a path to its absolute display path and realpath identity. For a missing target, * realpath the nearest existing ancestor and append the missing suffix, preserving identity * across symlinked ancestors before and after creation. * @param cwd - base directory a relative `path` resolves against. * @param path - absolute or relative path; empty/whitespace-only throws `FS_NOT_FOUND`. * @returns the absolute display path plus the realpath-derived stable target key. */ export declare function resolveLocalTarget(cwd: string, path: string): Promise; /** * Probe a path for its version, mode, type, and size. Null if absent. * @param absolutePath - the path to stat (typically a target key; symlinks are followed). * @returns the metadata, or null when the path — or a parent segment — does not exist. */ export declare function probe(absolutePath: string): Promise; /** * Probe a path without following the final symlink component. * @param absolutePath - the path entry to inspect with `lstat` semantics. * @returns path-entry metadata, or null when the entry is absent. */ export declare function probeNoFollow(absolutePath: string): Promise; /** * List direct children of a directory in stable name order. Each child includes * a resolved target plus stat metadata when still available; file contents are * never read. * @param target - the resolved directory to list; a missing or non-directory target throws. * @param signal - aborts the listing, checked between children (`FS_ABORTED`). * @returns one entry per direct child, sorted by name. */ export declare function listDirectory(target: LocalTarget, signal?: AbortSignal): Promise; /** * Read a whole regular UTF-8 text file into a single decoded string. Rejects * non-regular files, invalid UTF-8, and NUL-byte binary samples. * @param target - the resolved file to read. * @param signal - aborts the read (`FS_ABORTED`). * @returns the full decoded text, byte-for-byte (no normalization). */ export declare function readWholeText(target: LocalTarget, signal?: AbortSignal): Promise; /** * Stream a whole regular UTF-8 text file as decoded text chunks. Same text * semantics as {@link readWholeText} (regular-file check, binary/NUL rejection, * cross-chunk UTF-8 decoding), but never holds the whole file in memory. * @param target - the resolved file to stream. * @param signal - aborts the stream, including between chunks (`FS_ABORTED`). * @returns decoded text chunks in file order; chunk boundaries carry no meaning. */ export declare function streamWholeText(target: LocalTarget, signal?: AbortSignal): AsyncIterable; /** * Atomically replace a file through a private, synced staging file in the same directory. * POSIX protects the staging directory and file with `0o700` and `0o600`. A new Windows file * inherits the destination directory's DACL; a replacement copies the existing target's DACL * onto the empty temp before writing and preserves the target descriptor at publication. * @param absolutePath - destination; missing parent directories are created. * @param content - the full UTF-8 text to write. * @param mode - existing destination's POSIX mode to preserve, or `undefined` for a new file; * inert as a mode on Windows but identifies replacement security semantics. * @param signal - cancellation checked before final publication. * @param internals - Test hook for pinning temp names and observing the staged file. * @param createIfAbsent - when provided, publish with a hard-link no-replace * primitive; a concurrent creator's file is preserved and this write is * rejected with `FS_NOT_OBSERVED` using the supplied display path. */ export declare function writeFileAtomic(absolutePath: string, content: string, mode: number | undefined, signal: AbortSignal | undefined, internals?: FsIoInternals, createIfAbsent?: { displayPath: string; }): Promise; /** Line ending style detected before LF normalization. */ export type LineEndings = 'LF' | 'CRLF'; /** * Collapse CRLF to LF — the canonical in-memory form every edit/diff basis * uses. Lone `\r` bytes (not followed by `\n`) are left untouched. * @param content - decoded text in whatever line-ending style the file had. * @returns the text with every `\r\n` pair replaced by `\n`. */ declare function normalizeLineEndings(content: string): string; /** * Convert LF-normalized content back to the line-ending style detected at read * time, for write-back. `LF` returns the content unchanged; `CRLF` re-normalizes * first so an already-CRLF sequence is never doubled to `\r\r\n`. * @param content - the LF-normalized (edited) text. * @param lineEndings - the original file's style, as detected by {@link readForEdit}. * @returns the text in the original file's line-ending style. */ declare function restoreLineEndings(content: string, lineEndings: LineEndings): string; /** * Read and decode a file for editing: rejects binaries, returns LF-normalized * content plus the original line-ending style for write-back. * @param absolutePath - the file to read (typically a target key). * @param displayPath - the caller-facing path used in error messages. * @param signal - aborts the read (`FS_ABORTED`). * @returns the LF-normalized content and the detected style to restore on write-back. */ export declare function readForEdit(absolutePath: string, displayPath: string, signal?: AbortSignal): Promise<{ content: string; lineEndings: LineEndings; }>; /** * Best-effort overwrite diff basis. Binary, invalid UTF-8, a file at/above the byte limit, * or a file deleted/made unreadable after the caller's preflight returns `null` so the write * still succeeds and presentation falls back to a whole-file diff. The bound is enforced on * the opened descriptor rather than a prior path stat, so concurrent external replacement or * size changes cannot make this helper buffer more than `maxBytes`. * @param absolutePath - the file to read (typically a target key). * @param maxBytes - exclusive upper bound for bytes held as the contextual-diff basis. * @param signal - aborts the read (`FS_ABORTED`); cancellation propagates, unlike I/O failure. * @returns the LF-normalized text, or null for a non-regular, at/above-limit, binary, non-UTF-8, * descriptor-size-changed, or unreadable file. */ export declare function readTextForDiff(absolutePath: string, maxBytes: number, signal?: AbortSignal): Promise; /** * Apply a literal replacement to LF-normalized content. Empty or missing search text throws * `FS_EDIT_NOT_FOUND`; multiple matches throw `FS_AMBIGUOUS_EDIT` unless `replaceAll` is true. * @param content - the current file content, already LF-normalized. * @param oldString - literal text to find; CRLF inside it is normalized to LF before * matching. * @param newString - literal replacement text, normalized the same way. * @param replaceAll - replace every match instead of requiring exactly one. * @param displayPath - the caller-facing path used in error messages. * @returns the edited LF-normalized content plus how many occurrences were replaced. */ export declare function applyLiteralEdit(content: string, oldString: string, newString: string, replaceAll: boolean, displayPath: string): { content: string; replacements: number; }; export { normalizeLineEndings, restoreLineEndings }; //# sourceMappingURL=fsio.d.ts.map