Move verbose into Config; drop resolveVerbose
Verbose was awkwardly a separate runKeeper argument with a separate resolveVerbose helper, even though it's just another tunable on the same precedence ladder as the numeric fields. This commit collapses it. Changes: - Config gains 'readonly verbose: boolean'. DEFAULT_CONFIG sets it to false. - resolveConfig now returns the full Config including verbose. Verbose layers with the same 'first defined value wins' precedence as the numeric fields. - resolveVerbose is gone. - ParsedCliArgs.verbose becomes boolean | undefined: undefined when -V was not passed, true when it was. parseCliArgs maps accordingly. This lets the layered resolver treat verbose uniformly. - runKeeper takes a single Config arg. The internal logger reads from config.verbose at the top of runKeeper. - move.ts no longer plumbs verbose separately; the single resolved Config drives everything. - README How-it-works and Files-table entries updated to match. Behavior verified for all five verbose-precedence cases (no file/no flag, no file/-V, file:true/no flag, file:false/-V, file:false/no flag) and config-error scenarios remain intact.
This commit is contained in:
+29
-36
@@ -4,16 +4,16 @@
|
||||
* Runtime configuration types, defaults, the layered resolver, and the
|
||||
* default config-file path.
|
||||
*
|
||||
* The keeper is driven by a single `Config` object that carries the four
|
||||
* tunables it cares about. Defaults live in `DEFAULT_CONFIG`; CLI and
|
||||
* config-file overrides are layered on top by `resolveConfig` rather than
|
||||
* mutating the defaults, so the defaults stay genuinely constant and the
|
||||
* resolved config stays structurally typed.
|
||||
* The keeper is driven by a single `Config` object that carries every
|
||||
* tunable it cares about, including the `verbose` flag. Defaults live in
|
||||
* `DEFAULT_CONFIG`; CLI and config-file overrides are layered on top by
|
||||
* `resolveConfig` rather than mutating the defaults, so the defaults stay
|
||||
* genuinely constant and the resolved config stays structurally typed.
|
||||
*
|
||||
* All `Config` fields are in their internal units (ms, pixels). The CLI and
|
||||
* config file expose the time-valued fields in seconds for ergonomics;
|
||||
* `resolveConfig` performs the seconds->ms conversion at the boundary so
|
||||
* downstream code never has to think about it.
|
||||
* All numeric `Config` fields are in their internal units (ms, pixels).
|
||||
* The CLI and config file expose the time-valued fields in seconds for
|
||||
* ergonomics; `resolveConfig` performs the seconds->ms conversion at the
|
||||
* boundary so downstream code never has to think about it.
|
||||
*
|
||||
* Layering precedence (highest wins):
|
||||
* CLI overrides > file overrides > DEFAULT_CONFIG
|
||||
@@ -33,12 +33,15 @@ import { join } from "node:path";
|
||||
* a sweep. Also the window in which the user can
|
||||
* "interrupt" by moving the cursor. Milliseconds.
|
||||
* - `stepCount` — number of pixel-steps in a single sweep. Pixels.
|
||||
* - `verbose` — whether per-sweep / interrupt / bounds events are
|
||||
* logged. The startup banner is always printed.
|
||||
*/
|
||||
export interface Config {
|
||||
readonly moveInterval: number;
|
||||
readonly checkInterval: number;
|
||||
readonly stepDelay: number;
|
||||
readonly stepCount: number;
|
||||
readonly verbose: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,6 +53,7 @@ export const DEFAULT_CONFIG: Config = {
|
||||
checkInterval: 10 * 1000, // Check every 10 seconds
|
||||
stepDelay: 50, // ms between mouse steps
|
||||
stepCount: 250, // pixels to move per sweep
|
||||
verbose: false, // quiet by default; startup banner still prints
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -63,9 +67,14 @@ export const DEFAULT_CONFIG: Config = {
|
||||
* stepDelay — milliseconds
|
||||
* stepCount — pixels
|
||||
*
|
||||
* `verbose` is included so both layers can share a single shape, even
|
||||
* though `verbose` isn't part of `Config` itself (it's plumbed through to
|
||||
* `runKeeper` as a separate parameter).
|
||||
* `verbose` is `boolean | undefined` like the numeric fields, so all five
|
||||
* fields share the same "first defined value wins" precedence logic.
|
||||
*
|
||||
* For the CLI specifically, `verbose` is `undefined` when `-V/--verbose`
|
||||
* was not passed and `true` when it was. There is no CLI off-switch
|
||||
* today, so CLI `false` doesn't occur — a file-set `verbose: true` cannot
|
||||
* be overridden back to false from the command line (see the Configuration
|
||||
* section of the README).
|
||||
*/
|
||||
export interface ConfigOverrides {
|
||||
readonly moveInterval: number | undefined;
|
||||
@@ -96,8 +105,8 @@ export function defaultConfigPath(): string {
|
||||
/**
|
||||
* Overlay file overrides (lowest priority) and CLI overrides (highest)
|
||||
* on top of `DEFAULT_CONFIG` and return a resolved `Config`. Time-valued
|
||||
* inputs are in seconds; this is where they're converted to milliseconds
|
||||
* for internal use.
|
||||
* numeric inputs are in seconds; this is where they're converted to
|
||||
* milliseconds for internal use.
|
||||
*
|
||||
* For each field, the first layer that supplies a defined value wins:
|
||||
* CLI -> file -> DEFAULT_CONFIG
|
||||
@@ -113,11 +122,11 @@ export function resolveConfig(file: ConfigOverrides | null, cli: ConfigOverrides
|
||||
return fallbackMs;
|
||||
};
|
||||
|
||||
const pickRaw = (
|
||||
cliVal: number | undefined,
|
||||
fileVal: number | undefined,
|
||||
fallback: number,
|
||||
): number => {
|
||||
const pickRaw = <T>(
|
||||
cliVal: T | undefined,
|
||||
fileVal: T | undefined,
|
||||
fallback: T,
|
||||
): T => {
|
||||
if (cliVal !== undefined) return cliVal;
|
||||
if (fileVal !== undefined) return fileVal;
|
||||
return fallback;
|
||||
@@ -128,22 +137,6 @@ export function resolveConfig(file: ConfigOverrides | null, cli: ConfigOverrides
|
||||
checkInterval: pickSeconds(cli.checkInterval, file?.checkInterval, DEFAULT_CONFIG.checkInterval),
|
||||
stepDelay: pickRaw(cli.stepDelay, file?.stepDelay, DEFAULT_CONFIG.stepDelay),
|
||||
stepCount: pickRaw(cli.stepCount, file?.stepCount, DEFAULT_CONFIG.stepCount),
|
||||
verbose: pickRaw(cli.verbose, file?.verbose, DEFAULT_CONFIG.verbose),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the effective `verbose` flag from the same layered overrides.
|
||||
*
|
||||
* `--verbose` on the CLI is a presence-only switch (no `--no-verbose`),
|
||||
* so this is strictly an "or-up" merge: CLI true wins outright, otherwise
|
||||
* the file's value (or false if absent) is used.
|
||||
*
|
||||
* Known limitation: a user with `"verbose": true` in their config file
|
||||
* cannot force quiet mode from the CLI. They must edit the file (or use
|
||||
* `--config` to point at a different one).
|
||||
*/
|
||||
export function resolveVerbose(file: ConfigOverrides | null, cli: ConfigOverrides): boolean {
|
||||
if (cli.verbose === true) return true;
|
||||
if (file?.verbose === true) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user