Files
hyperframes/packages/cli/src/utils/commandResult.ts
T

123 lines
3.4 KiB
TypeScript

export type CommandResultKind = "success" | "usage_error" | "runtime_error";
export interface CommandResult {
exitCode: number;
kind: CommandResultKind;
/** The command already emitted its structured/human output. */
presented?: boolean;
}
const SUCCESS_RESULT: CommandResult = { exitCode: 0, kind: "success" };
let pendingResult: CommandResult = SUCCESS_RESULT;
let rootExitRequester: ((exitCode: number) => void) | undefined;
let rootExitCodeSanitizer: (() => void) | undefined;
export class CliUsageError extends Error {
readonly result: CommandResult;
constructor(
message = "Invalid command usage",
options: { exitCode?: number; presented?: boolean } = {},
) {
super(message);
this.name = "CliUsageError";
this.result = {
exitCode: options.exitCode ?? 1,
kind: "usage_error",
presented: options.presented,
};
}
}
export class CliRuntimeError extends Error {
readonly result: CommandResult;
constructor(
message = "Command failed",
options: { exitCode?: number; presented?: boolean } = {},
) {
super(message);
this.name = "CliRuntimeError";
this.result = {
exitCode: options.exitCode ?? 1,
kind: "runtime_error",
presented: options.presented,
};
}
}
/** Internal control-flow signal for an early, already-presented result. */
export class CliResultSignal extends Error {
readonly result: CommandResult;
constructor(result: CommandResult) {
super(`CLI result ${result.exitCode}`);
this.name = "CliResultSignal";
this.result = result;
}
}
export function failCommand(exitCode = 1): never {
throw new CliRuntimeError("Command failed", { exitCode, presented: true });
}
export function failUsage(exitCode = 1): never {
throw new CliUsageError("Invalid command usage", { exitCode, presented: true });
}
export function finishCommand(exitCode = 0): never {
throw new CliResultSignal({
exitCode,
kind: exitCode === 0 ? "success" : "runtime_error",
presented: true,
});
}
/** Record a non-fatal result while allowing output/finalizers to complete. */
export function setCommandResult(result: CommandResult): void {
if (pendingResult.exitCode !== 0 && result.exitCode === 0) return;
pendingResult = result;
}
export function setCommandExitCode(exitCode: number): void {
setCommandResult({
exitCode,
kind: exitCode === 0 ? "success" : "runtime_error",
presented: true,
});
}
export function consumeCommandResult(): CommandResult {
const result = pendingResult;
pendingResult = SUCCESS_RESULT;
return result;
}
/** Called only by cli.ts to retain ownership of forced process termination. */
export function registerRootExitRequester(requester: (exitCode: number) => void): void {
rootExitRequester = requester;
}
/** Called only by cli.ts to retain ownership of process exit-code mutation. */
export function registerRootExitCodeSanitizer(sanitizer: () => void): void {
rootExitCodeSanitizer = sanitizer;
}
/** Ask cli.ts to clear stray process exit state after a successful render. */
export function sanitizeSuccessfulExitCode(): void {
rootExitCodeSanitizer?.();
}
/** Ask cli.ts to finalize telemetry/output and then terminate the process. */
export function requestCliExit(exitCode = 0): void {
if (!rootExitRequester) {
setCommandResult({
exitCode,
kind: exitCode === 0 ? "success" : "runtime_error",
presented: true,
});
return;
}
rootExitRequester(exitCode);
}