refactor: model bin errors as neverthrow Result values (task 0003)

Split the view command out of the entry point and make its failure a
value rather than an exception. index.ts is now scaffolding plus
dispatch; view.ts owns the behaviour, returning Result<string, ViewError>
(a plain-data tagged union) via neverthrow, with try/catch confined to a
readFile adapter. The entry point matches the Result at the boundary.

Because viewFile is a pure in-process function it is now unit-tested
directly (and counted by coverage), while the subprocess tests stay as
the end-to-end check. ADR 0008 records the errors-as-values convention.
This commit is contained in:
2026-07-28 21:21:23 -04:00
parent 313a5b60b7
commit ae542893da
7 changed files with 120 additions and 12 deletions

View File

@@ -1,8 +1,6 @@
#!/usr/bin/env bun
import { readFileSync } from "node:fs";
import { parse } from "@kitchen-md/core";
import { Command } from "commander";
import { render } from "./render.ts";
import { formatViewError, viewFile } from "./view.ts";
const program = new Command();
@@ -13,15 +11,13 @@ program
.description("Render a Recipe File to the terminal")
.argument("<file>", "path to a Recipe File")
.action((file: string) => {
let content: string;
try {
content = readFileSync(file, "utf8");
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
process.stderr.write(`kitchen: cannot read '${file}': ${reason}\n`);
process.exit(1);
}
process.stdout.write(render(parse(content)));
viewFile(file).match(
(output) => process.stdout.write(output),
(error) => {
process.stderr.write(`kitchen: ${formatViewError(error)}\n`);
process.exit(1);
},
);
});
program.parse();