import { n as RuntimeAdapter } from "./adapter-D0hbBNVB.mjs"; //#region src/core/config/package-json.d.ts /** * Object form of a manifest's `repository` field * (e.g. `{"type":"git","url":"git+https://github.com/u/r.git"}`). */ interface PackageRepository { /** Version control system type (usually `'git'`). */ readonly type?: string; /** Repository URL or locator. */ readonly url?: string; /** Subdirectory within a monorepo where the package lives. */ readonly directory?: string; } /** * Subset of manifest fields relevant to CLI metadata (`package.json`, * `deno.json`, `jsr.json`, or preloaded data). * * All fields are optional — a valid manifest may omit any of them. */ interface PackageJsonData { /** Package name (e.g. `@scope/mycli`). */ readonly name?: string; /** Semver version string from manifest metadata. */ readonly version?: string; /** One-line package description. */ readonly description?: string; /** Binary entry point(s) — string for single-bin, object for multi-bin. */ readonly bin?: string | Readonly>; /** Project homepage URL. */ readonly homepage?: string; /** Repository locator — URL/shorthand string or `{type, url, directory}` object. */ readonly repository?: string | PackageRepository; } /** * The subset of {@link RuntimeAdapter} needed for manifest discovery. * * Using a narrow pick keeps the function easy to test and makes the * dependency explicit. */ type PackageJsonAdapter = Pick; /** Options for {@link discoverManifest}. */ interface ManifestDiscoveryOptions { /** * Explicit directory or file path to walk up from. Defaults to * `adapter.cwd` when omitted. Pass an absolute path inside your own package * (e.g. `fileURLToPath(import.meta.url)`) for installable CLIs whose version * should reflect the CLI's own package, not the consumer's working directory. * * This low-level helper takes a resolved path **string** only. The builder * sugar `CLIBuilder.manifest({ from })` exposes the same anchor under the name * `from` and additionally accepts a `file:` URL string or `URL` instance, * normalizing it to a path first. */ readonly startDir?: string; /** * Candidate manifest filenames, in per-directory priority order * (e.g. `['deno.json', 'jsr.json']`). At each directory the first existing * file that carries CLI metadata wins, so the nearest manifest directory * always takes precedence over file order. A file that parses but holds no * recognised metadata (e.g. a config-only `deno.json` with just * `tasks`/`imports`) is skipped, so a sibling `jsr.json` — or a manifest * higher up — can still be found. Defaults to `['package.json']`. * * An empty list is a deliberate no-op: with no candidates to probe, the * walk-up reads nothing and resolves to `null` (consistent with the * "returns `null` when no manifest is found" contract). */ readonly files?: readonly string[]; } /** * Discover the nearest manifest (`package.json`, `deno.json`, `jsr.json`, …) * by walking up from `startDir` (or `adapter.cwd` when omitted). * * Convenience helper behind `CLIBuilder.manifest()`. Most apps should let the * CLI runtime discover metadata automatically; call this directly when testing * metadata inference or embedding the behavior in custom tooling. * * Candidate files are parsed as JSON with a JSONC fallback — `package.json`, * `deno.json`, `jsr.json`, and `deno.jsonc` all qualify, including files that * carry `//` / block comments or trailing commas (common in `deno.json`). A * file that fails both parses is skipped, so discovery keeps probing. * * Returns the parsed metadata on success, `null` when no manifest is found * (not an error). Malformed JSON, non-object roots, and config-only manifests * that carry no recognised metadata (e.g. a `deno.json` with only `tasks` / * `imports`) are all skipped, so discovery keeps probing the remaining * candidate files and parent directories — the feature is a convenience, not a * hard requirement. * * @param adapter - Adapter providing `readFile` + `cwd`. * @param options - Optional anchor (`startDir`) and candidate filenames (`files`). * * @example * ```ts * import { discoverManifest } from '@kjanat/dreamcli/config'; * * const meta = await discoverManifest(adapter, { files: ['deno.json', 'jsr.json'] }); * if (meta !== null) { * console.log(meta.version); // '1.2.3' * } * ``` */ declare function discoverManifest(adapter: PackageJsonAdapter, options?: ManifestDiscoveryOptions): Promise; /** * Infer the CLI binary name from manifest data. * * Resolution order: * 1. First key of `bin` object (e.g. `{"mycli": "./dist/cli.js"}` → `"mycli"`) * 2. Package `name`, scope stripped by default (e.g. `"@scope/mycli"` → `"mycli"`); * pass `{ stripScope: false }` to keep the full scoped name * 3. `undefined` if neither exists * * Note: `bin` keys are never scoped, so `stripScope` only affects the `name` * fallback (relevant for `deno.json` / `jsr.json`, which have no `bin` field). * * @param pkg - Parsed manifest metadata. * @param options - `stripScope` (default `true`): strip a leading `@scope/` * from the `name` fallback. * * @example * ```ts * import { inferCliName } from '@kjanat/dreamcli/config'; * * inferCliName({ bin: { mycli: './dist/cli.js' } }); // 'mycli' * inferCliName({ name: '@scope/mycli' }); // 'mycli' * inferCliName({ name: '@scope/mycli' }, { stripScope: false }); // '@scope/mycli' * inferCliName({ name: 'mycli' }); // 'mycli' * inferCliName({}); // undefined * ``` */ declare function inferCliName(pkg: PackageJsonData, options?: { readonly stripScope?: boolean; }): string | undefined; //#endregion //#region src/core/config/repository-url.d.ts /** Options for {@link packageRepositoryUrl}. */ interface PackageRepositoryUrlOptions { /** * Throw a `CLIError` (code `INVALID_REPOSITORY`) instead of returning * `undefined` when the `repository` field is absent or not a recognisable * locator. With `require: true` the return type narrows to `string`, so * callers that know their manifest carries a valid repository need no * assertion — and a bad manifest fails fast with a real message instead * of propagating `undefined`. * * @defaultValue `false` */ readonly require?: boolean; } /** * Resolve a manifest's `repository` field to a browsable `https://` URL. * * Handles the locator formats npm accepts: * - object form: `{ "type": "git", "url": "git+https://github.com/u/r.git" }` * - `https`/`git`/`ssh` URLs (`git+` prefix and `.git` suffix stripped) * - scp-style locators: `git@github.com:u/r.git` * - shorthands: `github:u/r`, `gitlab:u/r`, `bitbucket:u/r`, and bare `u/r` * (GitHub, per npm convention) * * Returns `undefined` when the field is absent or unrecognised — or, with * `{ require: true }`, throws instead and the return type is `string`. * (The narrowing cannot key off the input type alone: a present `repository` * may still be an empty string, a url-less object, or an unparseable * locator, so the `string` promise is backed by the runtime check.) * * @example * ```ts * packageRepositoryUrl({ repository: 'git+https://github.com/u/r.git' }); * // 'https://github.com/u/r' * * const url: string = packageRepositoryUrl(pkg, { require: true }); * // throws CLIError when pkg.repository is missing or unrecognised * ``` */ declare function packageRepositoryUrl(pkg: PackageJsonData, options: PackageRepositoryUrlOptions & { readonly require: true; }): string; /** Resolve a manifest repository URL, returning `undefined` for absent or invalid locators. */ declare function packageRepositoryUrl(pkg: PackageJsonData, options?: PackageRepositoryUrlOptions): string | undefined; //#endregion //#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 { discoverManifest as _, ConfigNotFound as a, buildConfigSearchPaths as c, PackageRepositoryUrlOptions as d, packageRepositoryUrl as f, PackageRepository as g, PackageJsonData as h, ConfigFound as i, configFormat as l, PackageJsonAdapter as m, ConfigDiscoveryOptions as n, ConfigSearchPathOptions as o, ManifestDiscoveryOptions as p, ConfigDiscoveryResult as r, FormatLoader as s, ConfigAdapter as t, discoverConfig as u, inferCliName as v };