import { RuntimeAdapter } from "../../runtime/adapter.mjs"; import { ManifestDiscoveryOptions, PackageJsonAdapter, PackageJsonData, PackageRepository, PackageRepositoryUrlOptions, discoverManifest, discoverPackageJson, inferCliName, packageRepositoryUrl } from "./package-json.mjs"; //#region src/core/config/index.d.ts /** * Format loader — parses file content into a config object. * * Register custom config formats by providing file extensions and a parser. * Parsers may return any parsed value; {@link discoverConfig} validates that * the result is a plain object before feeding it into the resolution chain. * * Implementations should throw on syntax or shape errors; the caller wraps * those failures as {@link CLIError} with code `CONFIG_PARSE_ERROR`. */ interface FormatLoader { /** File extensions this loader handles (without leading dot, e.g. `'toml'`). */ readonly extensions: readonly string[]; /** * Parse file content into a config value. * * Arrays, primitives, and `null` are allowed at this boundary so generic * parsers like `Bun.YAML.parse` can be passed directly. Those values are * still rejected by {@link discoverConfig}, which requires a plain object. */ readonly parse: (content: string) => unknown; } /** Options for {@link discoverConfig}. */ interface ConfigDiscoveryOptions { /** * Explicit config file path (`--config` override). * When provided, skips search — loads only this path. */ readonly configPath?: string; /** * Additional format loaders (JSON is built-in). * Later loaders for the same extension win (allows override). */ readonly loaders?: readonly FormatLoader[]; /** * Custom search paths (absolute). * Replaces the default search paths when provided. * Probed in order; first found wins. */ readonly searchPaths?: readonly string[]; /** * Directory the project-scope ancestor walk starts from. * Anchors discovery to a location other than the process working * directory (e.g. the directory of a file an editor integration is * operating on). * @defaultValue `adapter.cwd` */ readonly baseDir?: string; } /** Successful config discovery — file found and parsed. */ interface ConfigFound { /** Discriminant — `true` indicates a config file was found and parsed successfully. */ readonly found: true; /** Absolute path to the config file that was loaded. */ readonly path: string; /** Parsed config data. */ readonly data: Readonly>; /** File extension that determined the loader (e.g. `'json'`). */ readonly format: string; } /** No config file found at any candidate path (not an error). */ interface ConfigNotFound { /** Discriminant — `false` indicates no config file exists at any candidate path. */ readonly found: false; } /** Discriminated result of config discovery. */ type ConfigDiscoveryResult = ConfigFound | ConfigNotFound; /** Directory scopes feeding {@link buildConfigSearchPaths}. */ interface ConfigSearchPathOptions { /** Directory the project-scope ancestor walk starts from. */ readonly baseDir: string; /** * User-scope config roots, highest priority first * (XDG / AppData, plus `~/Library/Application Support` on macOS). */ readonly userConfigDirs: readonly string[]; /** * System-scope config roots (`/etc` on Linux and macOS). * @defaultValue `[]` */ readonly systemConfigDirs?: readonly string[]; /** Custom {@link FormatLoader}s whose extensions expand the search set. */ readonly loaders?: readonly FormatLoader[]; } /** * Build the default config search paths for an app. * * Advanced helper used by DreamCLI's config discovery. Most apps should call * `.config()` or {@link discoverConfig} instead of constructing search paths * manually. Exported for debugging, custom discovery flows, and help text. * * Search order (first match wins): * 1. Project scope — for `baseDir` and each ancestor directory up to the * filesystem root, nearest first: * 1. `{dir}/.{appName}.json` — dotfile * 2. `{dir}/{appName}.config.json` — explicit config * 3. `{dir}/.config/{appName}.json` — project `.config/` convention * 2. User scope — `{userConfigDir}/{appName}/config.json` for each entry of * `userConfigDirs`, in order. * 3. System scope — `{systemConfigDir}/{appName}/config.json` for each entry * of `systemConfigDirs`, in order. * * When custom {@link ConfigDiscoveryOptions.loaders | loaders} are registered, * each path pattern is repeated per supported extension (JSON always first). * * @param appName - CLI application name used to derive config filenames. * @param options - Directory scopes and loaders (see {@link ConfigSearchPathOptions}). * @returns Ordered list of candidate config file paths (first match wins). * * @example * ```ts * const paths = buildConfigSearchPaths('mycli', { * baseDir: '/repo/packages/app', * userConfigDirs: ['/home/me/.config'], * systemConfigDirs: ['/etc'], * }); * ``` */ declare function buildConfigSearchPaths(appName: string, options: ConfigSearchPathOptions): readonly string[]; /** * The subset of {@link RuntimeAdapter} needed for config discovery. * * Exported so custom hosts and tests can type the minimal adapter required by * {@link discoverConfig} without depending on the full runtime adapter shape. */ type ConfigAdapter = Pick & Partial>; /** * Discover and load a config file. * * Low-level discovery helper behind `CLIBuilder.config()`. Most apps should * let the CLI runtime call this automatically; call it directly when testing * config behavior or building custom bootstrapping around {@linkcode RuntimeAdapter}. * * Pure function — all filesystem I/O flows through `adapter.readFile`. * Returns a discriminated union: `{ found: true, ... }` when a config * file was found and parsed, `{ found: false }` when no file exists. * * @throws {CLIError} code `CONFIG_NOT_FOUND` — explicit `configPath` doesn't exist * @throws {CLIError} code `CONFIG_PARSE_ERROR` — file exists but fails to parse * @throws {CLIError} code `CONFIG_UNKNOWN_FORMAT` — no loader for the file extension * * @example * ```ts * const result = await discoverConfig('mycli', adapter, { * loaders: [ * configFormat(['yaml', 'yml'], Bun.YAML.parse), * configFormat(['toml'], Bun.TOML.parse), * ], * }); * ``` */ declare function discoverConfig(appName: string, adapter: ConfigAdapter, options?: ConfigDiscoveryOptions): Promise; /** * Create a {@link FormatLoader} from extensions and a parse function. * * Convenience factory for config loading. It avoids manually constructing the * `{ extensions, parse }` object and makes intent clearer at call sites. * * Later loaders registered for the same extension override earlier ones. Any * error thrown by `parse` is wrapped by {@link discoverConfig} as * `CONFIG_PARSE_ERROR`. * * @param extensions - File extensions this loader handles (without dot, e.g. `'yaml'`). * @param parse - Parse function: takes file content string and returns a parsed config value. * @returns A {@link FormatLoader} ready to pass to {@link ConfigDiscoveryOptions.loaders}. * * @example * ```ts * import { cli, configFormat } from '@kjanat/dreamcli'; * import { parse as parseYaml } from 'yaml'; * import { parse as parseTOML } from '@iarna/toml'; * * const yamlPackageLoader = configFormat(['yaml', 'yml'], parseYaml); * const tomlPackageLoader = configFormat(['toml'], parseTOML); * // Or with Bun's built-in parsers: * // const yamlLoader = configFormat(['yaml', 'yml'], Bun.YAML.parse); * // const tomlLoader = configFormat(['toml'], Bun.TOML.parse); * * cli('myapp') * .config('myapp') * .configLoader(yamlLoader) * .configLoader(tomlLoader) * .run(); * ``` */ declare function configFormat(extensions: readonly string[], parse: (content: string) => unknown): FormatLoader; //#endregion export { type ConfigAdapter, type ConfigDiscoveryOptions, type ConfigDiscoveryResult, type ConfigFound, type ConfigNotFound, type ConfigSearchPathOptions, type FormatLoader, type ManifestDiscoveryOptions, type PackageJsonAdapter, type PackageJsonData, type PackageRepository, type PackageRepositoryUrlOptions, buildConfigSearchPaths, configFormat, discoverConfig, discoverManifest, discoverPackageJson, inferCliName, packageRepositoryUrl };