refactor(cli): separate json payload output from logging

This commit is contained in:
Peter Steinberger
2026-03-22 23:19:14 +00:00
parent 274af0486a
commit 4ee41cc6f3
89 changed files with 710 additions and 693 deletions

View File

@@ -32,8 +32,12 @@ describe("resolveRuntimeEnv", () => {
const resolved = resolveRuntimeEnv({ logger });
resolved.log?.("hello %s", "world");
resolved.error?.("bad %d", 7);
resolved.writeStdout("plain");
resolved.writeJson({ ok: true });
expect(logger.info).toHaveBeenCalledWith("hello world");
expect(logger.error).toHaveBeenCalledWith("bad 7");
expect(logger.info).toHaveBeenCalledWith("plain");
expect(logger.info).toHaveBeenCalledWith('{\n "ok": true\n}');
});
});

View File

@@ -1,6 +1,6 @@
import { format } from "node:util";
import type { RuntimeEnv } from "../runtime.js";
export type { RuntimeEnv } from "../runtime.js";
import type { OutputRuntimeEnv, RuntimeEnv } from "../runtime.js";
export type { OutputRuntimeEnv, RuntimeEnv } from "../runtime.js";
export { createNonExitingRuntime, defaultRuntime } from "../runtime.js";
export {
danger,
@@ -29,7 +29,7 @@ type LoggerLike = {
export function createLoggerBackedRuntime(params: {
logger: LoggerLike;
exitError?: (code: number) => Error;
}): RuntimeEnv {
}): OutputRuntimeEnv {
return {
log: (...args) => {
params.logger.info(format(...args));
@@ -37,6 +37,12 @@ export function createLoggerBackedRuntime(params: {
error: (...args) => {
params.logger.error(format(...args));
},
writeStdout: (value) => {
params.logger.info(value);
},
writeJson: (value, space = 2) => {
params.logger.info(JSON.stringify(value, null, space > 0 ? space : undefined));
},
exit: (code: number): never => {
throw params.exitError?.(code) ?? new Error(`exit ${code}`);
},
@@ -44,22 +50,48 @@ export function createLoggerBackedRuntime(params: {
}
/** Reuse an existing runtime when present, otherwise synthesize one from the provided logger. */
export function resolveRuntimeEnv(params: {
runtime: RuntimeEnv;
logger: LoggerLike;
exitError?: (code: number) => Error;
}): RuntimeEnv;
export function resolveRuntimeEnv(params: {
runtime?: undefined;
logger: LoggerLike;
exitError?: (code: number) => Error;
}): OutputRuntimeEnv;
export function resolveRuntimeEnv(params: {
runtime?: RuntimeEnv;
logger: LoggerLike;
exitError?: (code: number) => Error;
}): RuntimeEnv {
}): RuntimeEnv | OutputRuntimeEnv {
return params.runtime ?? createLoggerBackedRuntime(params);
}
/** Resolve a runtime that treats exit requests as unsupported errors instead of process termination. */
export function resolveRuntimeEnvWithUnavailableExit(params: {
runtime: RuntimeEnv;
logger: LoggerLike;
unavailableMessage?: string;
}): RuntimeEnv;
export function resolveRuntimeEnvWithUnavailableExit(params: {
runtime?: undefined;
logger: LoggerLike;
unavailableMessage?: string;
}): OutputRuntimeEnv;
export function resolveRuntimeEnvWithUnavailableExit(params: {
runtime?: RuntimeEnv;
logger: LoggerLike;
unavailableMessage?: string;
}): RuntimeEnv {
}): RuntimeEnv | OutputRuntimeEnv {
if (params.runtime) {
return resolveRuntimeEnv({
runtime: params.runtime,
logger: params.logger,
exitError: () => new Error(params.unavailableMessage ?? "Runtime exit not available"),
});
}
return resolveRuntimeEnv({
runtime: params.runtime,
logger: params.logger,
exitError: () => new Error(params.unavailableMessage ?? "Runtime exit not available"),
});

View File

@@ -9,7 +9,7 @@ export { removeAckReactionAfterReply, shouldAckReaction } from "../channels/ack-
export type { ChannelAccountSnapshot, ChannelGatewayContext } from "../channels/plugins/types.js";
export type { OpenClawConfig } from "../config/config.js";
export type { PluginRuntime } from "../plugins/runtime/types.js";
export type { RuntimeEnv } from "../runtime.js";
export type { OutputRuntimeEnv, RuntimeEnv } from "../runtime.js";
export type { MockFn } from "../test-utils/vitest-mock-fn.js";
/** Create a tiny Windows `.cmd` shim fixture for plugin tests that spawn CLIs. */