Add JSON config file support (XDG-respecting)
End users can now set defaults in a config file at:
${XDG_CONFIG_HOME:-$HOME/.config}/move/config.json
CLI flags continue to win when both are set:
CLI flags > config file > built-in defaults
Schema mirrors the CLI flag names and units. Loader is strict: unknown
keys, wrong types, and non-positive numerics are rejected with a clear
message naming the file and key, and the process exits 2.
Changes:
- New src/configFile.ts: existence-aware loader + strict schema
validation. Throws CliError; the entry point converts those to exit 2.
- src/config.ts: ConfigOverrides gains 'verbose'; resolveConfig takes
both file and CLI override layers; new defaultConfigPath() honors
XDG_CONFIG_HOME; new resolveVerbose() layers verbose with the
presence-only-CLI semantics documented.
- src/cli.ts: -C/--config <path> flag. printHelp prints the default
config path and the precedence rule.
- src/move.ts: loads the config file (default XDG path or --config),
passes both override layers into resolveConfig, resolves verbose
separately, exits 2 on any validation failure.
- README: new Configuration section with path, precedence, example,
validation rules, and the verbose CLI-can't-turn-off limitation.
Files table gains src/configFile.ts.
This commit is contained in:
@@ -95,17 +95,78 @@ out-of-bounds events.
|
||||
Invalid input (unknown flag, missing value, non-positive number) prints an
|
||||
error to `stderr` and exits with code `2`.
|
||||
|
||||
## Configuration
|
||||
|
||||
`move` reads an optional JSON config file at:
|
||||
|
||||
```
|
||||
${XDG_CONFIG_HOME:-$HOME/.config}/move/config.json
|
||||
```
|
||||
|
||||
If the file is missing, defaults are used (no config file is required).
|
||||
Pass `-C` / `--config <path>` to point at a different file; in that mode
|
||||
the file must exist.
|
||||
|
||||
### Precedence
|
||||
|
||||
```
|
||||
CLI flags > config file > built-in defaults
|
||||
```
|
||||
|
||||
CLI flags always win. The config file fills in any flag the user didn't
|
||||
pass on the command line. Built-in defaults fill in anything the file
|
||||
doesn't set.
|
||||
|
||||
### Example
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"moveInterval": 240,
|
||||
"checkInterval": 10,
|
||||
"stepDelay": 50,
|
||||
"stepCount": 250,
|
||||
"verbose": false
|
||||
}
|
||||
```
|
||||
|
||||
All keys are optional; supply only the ones you want to override. Keys
|
||||
and units mirror the CLI flags exactly: `moveInterval` and
|
||||
`checkInterval` are seconds, `stepDelay` is milliseconds, `stepCount` is
|
||||
pixels, `verbose` is a boolean.
|
||||
|
||||
### Validation
|
||||
|
||||
The loader is strict:
|
||||
|
||||
- Root must be a JSON object.
|
||||
- Unknown keys are rejected (catches typos like `"movInterval"`).
|
||||
- Numeric values must be finite and strictly positive.
|
||||
- `verbose` must be a boolean.
|
||||
|
||||
Any validation failure prints a message naming the file and the offending
|
||||
key to `stderr` and exits `2`.
|
||||
|
||||
### Known limitation: `verbose` can be turned on but not off from the CLI
|
||||
|
||||
`--verbose` is a presence-only flag (there is no `--no-verbose`). If the
|
||||
config file sets `"verbose": true`, the CLI cannot force quiet mode in
|
||||
that invocation. Workarounds: edit the file, or point at a different
|
||||
file with `--config`.
|
||||
|
||||
## How it works
|
||||
|
||||
The source lives under `src/`, split into an entry point plus three logic
|
||||
The source lives under `src/`, split into an entry point plus four logic
|
||||
modules:
|
||||
|
||||
- `src/move.ts` is a thin entry point: parses args, dispatches `--help` /
|
||||
`--version`, resolves the runtime config, and calls
|
||||
`runKeeper(config, verbose)`.
|
||||
`--version`, loads the config file, resolves the layered runtime
|
||||
config, and calls `runKeeper(config, verbose)`.
|
||||
- `src/cli.ts` owns argument parsing, validation, and help/version output.
|
||||
- `src/config.ts` exports the `Config` type, `DEFAULT_CONFIG`, and the
|
||||
`resolveConfig` overlay function.
|
||||
- `src/configFile.ts` owns optional JSON config-file loading + strict
|
||||
schema validation.
|
||||
- `src/config.ts` exports the `Config` type, `DEFAULT_CONFIG`,
|
||||
`defaultConfigPath`, and the layered `resolveConfig` / `resolveVerbose`
|
||||
overlay functions.
|
||||
- `src/keeper.ts` owns the synthetic-activity sweep and the idle-watch loop.
|
||||
|
||||
Defaults live in `src/config.ts` as `DEFAULT_CONFIG`:
|
||||
@@ -190,7 +251,8 @@ move --help
|
||||
| `scripts/dev-setup.sh` | Contributor bootstrap (verify Bun + `bun install`). |
|
||||
| `src/move.ts` | CLI entry point: parses args, dispatches help/version, starts the loop. |
|
||||
| `src/cli.ts` | Argument parsing, validation, and help/version output. |
|
||||
| `src/config.ts` | `Config` type, `DEFAULT_CONFIG`, and `resolveConfig` overlay. |
|
||||
| `src/config.ts` | `Config` type, `DEFAULT_CONFIG`, `defaultConfigPath`, and the layered `resolveConfig` / `resolveVerbose` overlay. |
|
||||
| `src/configFile.ts` | Optional JSON config-file loader with strict schema validation. |
|
||||
| `src/keeper.ts` | Synthetic-activity sweep and idle-watch loop. |
|
||||
| `package.json` | Bun project manifest. Single runtime dep: `@nut-tree-fork/nut-js`. |
|
||||
| `tsconfig.json` | Strict TypeScript config tuned for Bun (ESNext, bundler resolution). |
|
||||
|
||||
Reference in New Issue
Block a user