67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
import {
|
|
analysisCliErrorEnvelopeSchema,
|
|
analysisErrorProjectionSchema,
|
|
} from "./contracts/errorSchemas.js";
|
|
import { AnalysisError } from "./domain/analysisErrorBase.js";
|
|
import {
|
|
projectAnalysisError,
|
|
type AnalysisErrorProjection,
|
|
} from "./domain/analysisErrorProjection.js";
|
|
import type { Logger } from "pino";
|
|
|
|
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 (
|
|
isRecord(value.error) &&
|
|
analysisErrorProjectionSchema.safeParse(value.error).success
|
|
)
|
|
return true;
|
|
if (analysisCliErrorEnvelopeSchema.safeParse(value).success) return true;
|
|
if (analysisErrorProjectionSchema.safeParse(value).success) return true;
|
|
if (value.healthy === false) return true;
|
|
return false;
|
|
};
|
|
|
|
/** 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>,
|
|
isCommandFailure?: (value: Value) => boolean,
|
|
): Promise<Value | AnalysisErrorProjection> => {
|
|
const startedAt = performance.now();
|
|
try {
|
|
const value = await execute();
|
|
const failed = isCommandFailure?.(value) ?? 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) {
|
|
process.exitCode = 1;
|
|
logger.error(
|
|
{
|
|
command,
|
|
durationMs: Math.round((performance.now() - startedAt) * 100) / 100,
|
|
status: "error",
|
|
},
|
|
"CLI command failed",
|
|
);
|
|
if (cause instanceof AnalysisError) {
|
|
process.exitCode = 1;
|
|
return projectAnalysisError(cause);
|
|
}
|
|
throw cause;
|
|
}
|
|
};
|