Fold the view command module (viewFile, the error type, the read adapter) into index.ts so the command functions sit beside the guarded CLI, and generalise formatViewError into formatError matching over a CliError union that new fallible commands extend. Align the test files with ADR 0009's tiers by name: - index_test.ts now holds the in-process integration tests of the command functions (formerly view_test.ts) - end_to_end_test.ts holds the black-box subprocess e2e (formerly index_test.ts)
55 lines
1.6 KiB
TypeScript
55 lines
1.6 KiB
TypeScript
#!/usr/bin/env bun
|
|
import { readFileSync } from "node:fs";
|
|
import { parse } from "@kitchen-md/core";
|
|
import { Command } from "commander";
|
|
import { err, ok, type Result } from "neverthrow";
|
|
import { render } from "./render.ts";
|
|
|
|
export type ViewError = { tag: "read-failed"; path: string; cause: string };
|
|
|
|
// The union of every error a command can surface to the boundary; new fallible commands add their variants here.
|
|
export type CliError = ViewError;
|
|
|
|
export function viewFile(path: string): Result<string, ViewError> {
|
|
return readFile(path).map((content) => render(parse(content)));
|
|
}
|
|
|
|
export function formatError(error: CliError): 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 });
|
|
}
|
|
}
|
|
|
|
const program = new Command();
|
|
|
|
program.name("kitchen").description("Read and view KitchenMD Recipe Files");
|
|
|
|
program
|
|
.command("view")
|
|
.description("Render a Recipe File to the terminal")
|
|
.argument("<file>", "path to a Recipe File")
|
|
.action((file: string) => {
|
|
viewFile(file).match(
|
|
(output) => process.stdout.write(output),
|
|
(error) => {
|
|
process.stderr.write(`kitchen: ${formatError(error)}\n`);
|
|
process.exit(1);
|
|
},
|
|
);
|
|
});
|
|
|
|
if (import.meta.main) {
|
|
program.parse();
|
|
}
|