mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-24 16:32:29 +00:00
125 lines
3.2 KiB
TypeScript
125 lines
3.2 KiB
TypeScript
import { clearActiveProgressLine } from "./terminal/progress-line.js";
|
|
import { restoreTerminalState } from "./terminal/restore.js";
|
|
|
|
export type RuntimeEnv = {
|
|
log: (...args: unknown[]) => void;
|
|
error: (...args: unknown[]) => void;
|
|
exit: (code: number) => void;
|
|
};
|
|
|
|
export type OutputRuntimeEnv = RuntimeEnv & {
|
|
writeStdout: (value: string) => void;
|
|
writeJson: (value: unknown, space?: number) => void;
|
|
};
|
|
|
|
function shouldEmitRuntimeLog(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
if (env.VITEST !== "true") {
|
|
return true;
|
|
}
|
|
if (env.OPENCLAW_TEST_RUNTIME_LOG === "1") {
|
|
return true;
|
|
}
|
|
const maybeMockedLog = console.log as unknown as { mock?: unknown };
|
|
return typeof maybeMockedLog.mock === "object";
|
|
}
|
|
|
|
function shouldEmitRuntimeStdout(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
if (env.VITEST !== "true") {
|
|
return true;
|
|
}
|
|
if (env.OPENCLAW_TEST_RUNTIME_LOG === "1") {
|
|
return true;
|
|
}
|
|
const stdout = process.stdout as NodeJS.WriteStream & {
|
|
write: {
|
|
mock?: unknown;
|
|
};
|
|
};
|
|
return typeof stdout.write.mock === "object";
|
|
}
|
|
|
|
function isPipeClosedError(err: unknown): boolean {
|
|
const code = (err as { code?: string })?.code;
|
|
return code === "EPIPE" || code === "EIO";
|
|
}
|
|
|
|
function hasRuntimeOutputWriter(
|
|
runtime: RuntimeEnv | OutputRuntimeEnv,
|
|
): runtime is OutputRuntimeEnv {
|
|
return typeof (runtime as Partial<OutputRuntimeEnv>).writeStdout === "function";
|
|
}
|
|
|
|
function writeStdout(value: string): void {
|
|
if (!shouldEmitRuntimeStdout()) {
|
|
return;
|
|
}
|
|
clearActiveProgressLine();
|
|
const line = value.endsWith("\n") ? value : `${value}\n`;
|
|
try {
|
|
process.stdout.write(line);
|
|
} catch (err) {
|
|
if (isPipeClosedError(err)) {
|
|
return;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
function createRuntimeIo(): Pick<OutputRuntimeEnv, "log" | "error" | "writeStdout" | "writeJson"> {
|
|
return {
|
|
log: (...args: Parameters<typeof console.log>) => {
|
|
if (!shouldEmitRuntimeLog()) {
|
|
return;
|
|
}
|
|
clearActiveProgressLine();
|
|
console.log(...args);
|
|
},
|
|
error: (...args: Parameters<typeof console.error>) => {
|
|
clearActiveProgressLine();
|
|
console.error(...args);
|
|
},
|
|
writeStdout,
|
|
writeJson: (value: unknown, space = 2) => {
|
|
writeStdout(JSON.stringify(value, null, space > 0 ? space : undefined));
|
|
},
|
|
};
|
|
}
|
|
|
|
export const defaultRuntime: OutputRuntimeEnv = {
|
|
...createRuntimeIo(),
|
|
exit: (code) => {
|
|
restoreTerminalState("runtime exit", { resumeStdinIfPaused: false });
|
|
process.exit(code);
|
|
throw new Error("unreachable"); // satisfies tests when mocked
|
|
},
|
|
};
|
|
|
|
export function createNonExitingRuntime(): OutputRuntimeEnv {
|
|
return {
|
|
...createRuntimeIo(),
|
|
exit: (code: number) => {
|
|
throw new Error(`exit ${code}`);
|
|
},
|
|
};
|
|
}
|
|
|
|
export function writeRuntimeStdout(runtime: RuntimeEnv | OutputRuntimeEnv, value: string): void {
|
|
if (hasRuntimeOutputWriter(runtime)) {
|
|
runtime.writeStdout(value);
|
|
return;
|
|
}
|
|
runtime.log(value);
|
|
}
|
|
|
|
export function writeRuntimeJson(
|
|
runtime: RuntimeEnv | OutputRuntimeEnv,
|
|
value: unknown,
|
|
space = 2,
|
|
): void {
|
|
if (hasRuntimeOutputWriter(runtime)) {
|
|
runtime.writeJson(value, space);
|
|
return;
|
|
}
|
|
runtime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
|
|
}
|