51 lines
1.5 KiB
TypeScript
51 lines
1.5 KiB
TypeScript
import type { Logger } from "./logger.js";
|
|
|
|
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
|
|
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
|
|
/** Identify caller-visible CLI outcomes that mean the requested operation failed. */
|
|
export const isCliOperationFailure = (value: unknown): boolean => {
|
|
if (!isRecord(value)) return false;
|
|
if (typeof value.error === "string") return true;
|
|
if (value.healthy === false) return true;
|
|
return (
|
|
value.status === "failed" ||
|
|
value.status === "needs_confirmation" ||
|
|
value.status === "needs_human" ||
|
|
value.status === "planned"
|
|
);
|
|
};
|
|
|
|
/** Log one CLI command with duration and a stable success or failure status. */
|
|
export const logCliCommand = async <Value>(
|
|
logger: Logger,
|
|
command: string,
|
|
execute: () => Promise<Value>,
|
|
): Promise<Value> => {
|
|
const startedAt = performance.now();
|
|
try {
|
|
const value = await execute();
|
|
const failed = isCliOperationFailure(value);
|
|
logger[failed ? "error" : "info"](
|
|
{
|
|
command,
|
|
durationMs: Math.round((performance.now() - startedAt) * 100) / 100,
|
|
status: failed ? "error" : "ok",
|
|
},
|
|
failed ? "CLI command failed" : "CLI command completed",
|
|
);
|
|
if (failed) process.exitCode = 1;
|
|
return value;
|
|
} catch (cause: unknown) {
|
|
logger.error(
|
|
{
|
|
command,
|
|
durationMs: Math.round((performance.now() - startedAt) * 100) / 100,
|
|
status: "error",
|
|
},
|
|
"CLI command failed",
|
|
);
|
|
throw cause;
|
|
}
|
|
};
|