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.
28 lines
920 B
TypeScript
28 lines
920 B
TypeScript
import { readFileSync } from "node:fs";
|
|
import { parse } from "@kitchen-md/core";
|
|
import { err, ok, type Result } from "neverthrow";
|
|
import { render } from "./render.ts";
|
|
|
|
export type ViewError = { tag: "read-failed"; path: string; cause: string };
|
|
|
|
export function viewFile(path: string): Result<string, ViewError> {
|
|
return readFile(path).map((content) => render(parse(content)));
|
|
}
|
|
|
|
export function formatViewError(error: ViewError): string {
|
|
switch (error.tag) {
|
|
case "read-failed":
|
|
return `cannot read '${error.path}': ${error.cause}`;
|
|
}
|
|
}
|
|
|
|
// The one place a throwing API is turned into a Result; nothing above this leaks exceptions.
|
|
function readFile(path: string): Result<string, ViewError> {
|
|
try {
|
|
return ok(readFileSync(path, "utf8"));
|
|
} catch (error) {
|
|
const cause = error instanceof Error ? error.message : String(error);
|
|
return err({ tag: "read-failed", path, cause });
|
|
}
|
|
}
|