import { GlobPattern, GlobPatternError, GlobPatternOptions } from "@effected/glob"; import { Effect, FileSystem, Option, Path, Schema } from "effect"; //#region src/Descend.d.ts /** * Options for {@link descend}. * * @public */ interface DescendOptions { /** Absolute directory the pattern is resolved against. Required — walker never reads `process.cwd()`. */ readonly cwd: string; /** Hard cap on directory depth below the pattern's literal prefix. Defaults to 256. */ readonly maxDepth?: number; /** Directory names never descended into. Defaults to `["node_modules", ".git"]`; a custom list replaces the default. */ readonly prune?: ReadonlyArray; /** * What an unreadable directory mid-walk does. `"fail"` (the default) fails * typed — downward enumeration must not silently swallow a subtree, or the * answer is silently missing membership dressed as an empty one. `"skip"` * absorbs the failure and continues. */ readonly onUnreadable?: "fail" | "skip"; } declare const DescendError_base: Schema.Class; /** The offending directory, relative to `cwd` (`""` is the walk's base). */ readonly path: Schema.String; /** The depth cap, present when `reason` is `"depthExceeded"`. */ readonly limit: Schema.optionalKey; }>, import("effect/Cause").YieldableError>; /** * Typed failure raised by {@link descend}: a directory mid-walk was unreadable * (under `onUnreadable: "fail"`), or the walk descended past `maxDepth`. Depth * exhaustion is a typed failure, never a truncation — silent truncation * silently changes match semantics. * * @public */ declare class DescendError extends DescendError_base { get message(): string; } /** * Expand a compiled glob pattern against the filesystem, returning matching * FILE paths relative to `cwd` (POSIX separators), sorted by relative path. * * @remarks * A literal pattern (no magic, not negated) fast-paths to a single stat: the * result is `[source]` when it resolves to a file, `[]` otherwise — a missing * path is zero matches, not an error. A magic pattern walks from its literal * directory prefix (`GlobPattern.enumerationPrefix`); a NEGATED pattern can * match paths outside that prefix, so it walks from `cwd` itself. A missing * base directory is likewise an empty result, because zero matches is a * normal glob answer — as is any pattern that lexically climbs above `cwd` * via `..` segments (walked paths never contain `..`, and the walk never * reads outside its documented root). Only an unreadable directory mid-walk * (under the default `onUnreadable: "fail"`) or a walk past `maxDepth` fails, * typed as {@link DescendError}. * * Only files match. A symlink counts when it stat-resolves to a file * (`FileSystem.stat` follows links, as node's does); a symlinked directory is * never descended into (cycle safety — detected by a `readLink` probe); a * dangling symlink is not a match. A directory that vanishes between its * parent's listing and its own read is a benign race and reads as empty. A * pattern that cannot match below one level (no globstar, no mid-pattern * magic segment) reads a single level and never descends. * * The descent is a worklist, not a recursion — it cannot overflow the stack — * dequeued by head index, never `Array.shift()`. Like `ascend`, `maxDepth` * must be a positive integer: anything else is a defect, never a * silently-empty result. * * @public */ declare const descend: (pattern: GlobPattern, options: DescendOptions) => Effect.Effect, DescendError, FileSystem.FileSystem | Path.Path>; //#endregion //#region src/Expand.d.ts /** * Options for {@link compileAndExpand}: every {@link DescendOptions} field, * plus the glob options the pattern compiles under. * * @public */ interface CompileAndExpandOptions extends DescendOptions { /** * The options the pattern compiles under — **required, deliberately**. * * @remarks * Matching semantics (`dot` above all) are the thing two call sites most * easily disagree about, and an optional field invites exactly that: one * site passes `{ dot: true }`, another omits it, and the same package now * has two glob dialects that nothing makes visible. Required means every * call site states its dialect in its own source, so a divergence is a * visible difference between two spellings rather than the absence of one. * Pass `GlobPatternOptions.make({})` to mean "the defaults" — that is a * deliberate choice being written down, not boilerplate. */ readonly glob: GlobPatternOptions; } declare const GlobExpansionError_base: Schema.Class; }>, import("effect/Cause").YieldableError>; /** * Typed failure raised by {@link compileAndExpand}: the single error the * compile+expand recipe fails with, so a caller catches one tag rather than * folding two error channels by hand. * * @remarks * One tag, two genuinely different causes — "your pattern is malformed" and * "that directory is unreadable" are different problems with different fixes, * so `cause` keeps the underlying typed error intact * rather than flattening it into a string. Discriminate on `cause._tag` * (`"GlobPatternError"` vs `"DescendError"`), or read * {@link GlobExpansionError.stage} when only the phase matters; either way the * original payload — a guard's `limit`/`actual`, a descent's `path` — is still * there. `cause` is also the native `Error` cause, so error chaining and * stack-printing work without extra wiring. * * @public */ declare class GlobExpansionError extends GlobExpansionError_base { /** * Which phase failed — `"compile"` when the pattern itself was rejected, * `"descend"` when the filesystem walk failed. A convenience over * `cause._tag` for callers that only need the phase. */ get stage(): "compile" | "descend"; get message(): string; } /** * Compile a glob pattern and expand it against the filesystem in one call: * matching FILE paths relative to `options.cwd`, POSIX separators, sorted. * * @remarks * The recipe form of {@link descend}. Everything `descend` documents about * traversal holds unchanged — the literal fast-path, the negated-pattern walk * from `cwd`, files-only matching, symlink and prune handling, `maxDepth`, * `onUnreadable` — because this delegates to it. What this adds is the seam: * the pattern arrives as a string, and both failure modes arrive as one * {@link GlobExpansionError} with the underlying error preserved in `cause`. * * A missing base directory, a pattern that climbs above `cwd`, and a pattern * that simply matches nothing are all an EMPTY result, not a failure — zero * matches is a normal glob answer. Only a rejected pattern or a failed walk * produces an error. * * `FileSystem` and `Path` stay in the `R` channel and are **deliberately not * provided here**, even though hand-providing them is the friction this * recipe otherwise removes. `FileSystem` cannot be provided — a library that * picks its own filesystem cannot be tested against a fixture tree. Given * that, providing `Path` internally would not save the caller a layer (they * still supply `FileSystem`), and it would actively break win32: the walk * would join paths POSIX-style against a caller's win32 filesystem. The * consumer's platform layer stays the single place that choice is made. * Provide both once at the application boundary, not per call site. * * @example * ```ts * import { compileAndExpand } from "@effected/walker" * import { GlobPatternOptions } from "@effected/glob" * * const files = yield* compileAndExpand("packages/*​/src/**​/*.ts", { * cwd: "/repo", * glob: GlobPatternOptions.make({ dot: true }) * }) * ``` * * @public */ declare const compileAndExpand: (pattern: string, options: CompileAndExpandOptions) => Effect.Effect, GlobExpansionError, FileSystem.FileSystem | Path.Path>; //#endregion //#region src/Walker.d.ts /** * Options for {@link Walker.ascend}. * * @public */ interface AscendOptions { /** * Stop after this directory, inclusive. * * **Must be absolute**, and is **normalized before comparison**. The ceiling * is matched against each directory's `Path.resolve` form rather than by raw * string equality, so a trailing separator, a `.` or `..` segment, or a * duplicated separator all stop at the ancestor they name. Normalization is * idempotent — an already-resolved absolute path is unchanged — so callers * that resolve at the call site are unaffected. * * A **relative** ceiling is a **defect**, not a typed failure, and is never * resolved against the process working directory. Two rules meet here: * * - Malformed *input* fails typed; bad *wiring* dies. A relative ceiling is * a caller-supplied option that is statically wrong at the call site — the * same category as a `NaN` `maxDepth` just below, not a recoverable * environmental condition — so it dies exactly as that does. * - **Do not "upgrade" this to a typed error.** A typed error is only loud * if somebody handles it, and `@effected/config-file`'s resolver contract * absorbs every failure into `Option.none()`. A typed ceiling rejection * would be swallowed there and re-emerge as a clean-looking "no config * found" — the silent-wrong-answer failure this whole guard exists to * close, reappearing through a third door. `Effect.catch` does not catch * defects, so dying is what survives that absorption. * * Resolving a relative ceiling would be just as bad: the same `stopAt` would * name different directories in a lint-staged hook, a CLI run from a package * directory, and a test runner, with no way for the caller to see which they * got. Rejecting costs one `path.resolve` at the site that knows the answer. * It is also why `ascend` reads `process.cwd()` nowhere. * * Only the CEILING is constrained — a relative `start` is fine and ascends * to the relative root. Absoluteness is judged by the injected `Path` * service, so the win32 layer accepts `C:\repo` and posix does not. * * Normalization governs the comparison only: the chain `ascend` returns is * still the lexical one derived from `start`, unrewritten. A ceiling naming * no ancestor of `start` never matches and the ascent runs to the * filesystem root. */ readonly stopAt?: string; /** Hard cap on chain length. Defaults to 256. */ readonly maxDepth?: number; } /** * Upward path traversal primitives. * * @public */ declare class Walker { private constructor(); /** * Ascend from `start` toward the filesystem root, yielding each directory, * nearest first. * * @remarks * Lexical, not physical: `Path.dirname` does not resolve symlinks, so ascending * out of a symlinked directory follows the path you were given rather than the * real filesystem parent. That is deliberate — config discovery wants the file * nearest the path the user named. * * Bounded twice over: `dirname` is a fixpoint at the root, and `maxDepth` guards * a pathological `Path` implementation that never reaches one. * * `stopAt` is compared in normalized form — see {@link AscendOptions.stopAt}. * Raw string equality made the ceiling fail OPEN: an unnormalized ceiling * matched nothing and the ascent ran silently to the filesystem root, which is * the unbounded walk the option exists to prevent, with no error to notice it * by. A relative ceiling **dies** for the same reason — resolving one against * `process.cwd()` would reintroduce the silent-wrong-walk failure through a * different door. See {@link AscendOptions.stopAt} for why that is a defect * rather than a typed error; the error channel stays `never`. */ static readonly ascend: (start: string, options?: AscendOptions) => Effect.Effect, never, Path.Path>; /** * The first candidate whose `predicate` reports true, or `Option.none()`. * * @remarks * Each predicate is absorbed **individually**: a failure on one candidate is * treated as "this candidate did not match" and the scan continues. One * unreadable ancestor must never abort the walk, or a permission error deep in * the tree would hide a valid root above it. Not-found and cannot-look are * therefore indistinguishable to the caller; discovery is best-effort. * * `Effect.catch` catches failures, **not defects**. A predicate that throws is * programmer error and surfaces as a defect. Do not change this to * `Effect.catchCause` — the distinction is load-bearing. */ static readonly firstMatch: (candidates: ReadonlyArray, predicate: (candidate: string) => Effect.Effect) => Effect.Effect, never, R>; /** * The first existing path among the candidates `candidatesFor` produces for each * directory in `dirs`, scanned in order. Nearer directories win. * * @remarks * Candidates materialize up front, bounded by `dirs.length × candidatesFor`'s * output — a few hundred strings under the default `maxDepth`. */ static readonly findUpward: (dirs: ReadonlyArray, candidatesFor: (dir: string) => ReadonlyArray) => Effect.Effect, never, FileSystem.FileSystem>; /** * The first directory in `dirs` that `isRoot` accepts. * * @remarks * `firstMatch` over the directories themselves — the candidate expansion is the * identity. `isRoot` is a caller-supplied marker test (a `.git` entry, a * `pnpm-workspace.yaml`), and its failures are absorbed per directory. */ static readonly findRoot: (dirs: ReadonlyArray, isRoot: (dir: string) => Effect.Effect) => Effect.Effect, never, R>; } //#endregion export { type AscendOptions, type CompileAndExpandOptions, DescendError, type DescendOptions, GlobExpansionError, Walker, compileAndExpand, descend }; //# sourceMappingURL=index.d.ts.map