Extract CliError into src/errors.ts

CliError used to live in cli.ts and was imported by configFile.ts purely
to grab a one-line class — the dependency arrow said 'config-file loader
depends on the CLI parser' when really it just needed a shared error
type.

Moving CliError to its own errors.ts module flattens the graph:

  errors.ts  (no internal deps)
    |
    +-- cli.ts
    +-- configFile.ts
    +-- config.ts  (will use it in the next commit)

cli.ts re-exports CliError for any consumer that prefers to keep
importing it from there; the canonical home is now errors.ts.
This commit is contained in:
2026-06-17 16:22:33 -05:00
parent 7714a6ad1a
commit 8eac51cd45
5 changed files with 38 additions and 11 deletions
+1 -8
View File
@@ -27,14 +27,7 @@ import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";
import { DEFAULT_CONFIG, defaultConfigPath } from "./config.ts";
/**
* Thrown when CLI input is invalid (unknown option, missing value, bad
* number, malformed config file). Distinct from runtime errors so the
* entry point can exit with code 2 (user error) instead of code 1
* (runtime failure).
*/
export class CliError extends Error {}
import { CliError } from "./errors.ts";
/**
* Result of `parseCliArgs`. Numeric fields are `undefined` when the user
+13 -1
View File
@@ -21,6 +21,8 @@
import { join } from "node:path";
import { CliError } from "./errors.ts";
// Single source of truth for default values. The same file ships in the
// install tree and is copied to $XDG_CONFIG_HOME/move/config.json on a
// fresh install (only if no config exists there yet). Values use the CLI
@@ -141,7 +143,17 @@ export function defaultConfigPath(): string {
if (xdg && xdg.length > 0) {
return join(xdg, "move", "config.json");
}
const home = process.env.HOME ?? "";
const home = process.env.HOME;
if (!home || home.length === 0) {
// Neither var is set; we don't have a sensible fallback. Throwing
// CliError lets the entry-point's normal handler surface this as
// 'move: cannot resolve default config path: ...' + exit 2 instead
// of silently producing '/.config/move/config.json' and bewildering
// the user with a downstream 'file not found' message.
throw new CliError(
"cannot resolve default config path: neither $XDG_CONFIG_HOME nor $HOME is set",
);
}
return join(home, ".config", "move", "config.json");
}
+1 -1
View File
@@ -27,8 +27,8 @@
import { existsSync, readFileSync, statSync } from "node:fs";
import { CliError } from "./cli.ts";
import { defaultConfigPath, type ConfigOverrides } from "./config.ts";
import { CliError } from "./errors.ts";
const ALLOWED_KEYS: ReadonlySet<string> = new Set<string>([
"moveInterval",
+16
View File
@@ -0,0 +1,16 @@
/**
* errors.ts
* ---------
* Cross-cutting error types used by parsers, loaders, and config resolution.
* Kept in its own module so feature modules can import error types without
* pulling in unrelated implementation code (e.g., the JSON loader doesn't
* need to depend on the CLI parser just to throw a typed error).
*/
/**
* Thrown when user-supplied input is invalid: unknown CLI option, missing
* value, non-positive number, malformed config file, unresolvable default
* path, etc. Distinct from runtime errors so the top-level entry can exit
* with code 2 (user error) instead of code 1 (runtime failure).
*/
export class CliError extends Error {}
+7 -1
View File
@@ -49,7 +49,13 @@ try {
}
if (cliArgs.help) {
printHelp();
// printHelp() resolves defaultConfigPath(), which can throw CliError
// when neither $XDG_CONFIG_HOME nor $HOME is set.
try {
printHelp();
} catch (err: unknown) {
failUser(err);
}
process.exit(0);
}
if (cliArgs.version) {