diff --git a/src/cli/config-cli.test.ts b/src/cli/config-cli.test.ts index 148dcfa84d5..cf176b44015 100644 --- a/src/cli/config-cli.test.ts +++ b/src/cli/config-cli.test.ts @@ -42,10 +42,10 @@ vi.mock("../runtime.js", async (importOriginal) => { ...actual.defaultRuntime, log: (...args: unknown[]) => mockLog(...args), error: (...args: unknown[]) => mockError(...args), - exit: (code: number) => mockExit(code), writeStdout: (value: string) => mockLog(value.endsWith("\n") ? value.slice(0, -1) : value), writeJson: (value: unknown, space = 2) => mockLog(JSON.stringify(value, null, space > 0 ? space : undefined)), + exit: (code: number) => mockExit(code), }, }; }); diff --git a/src/cli/cron-cli.test.ts b/src/cli/cron-cli.test.ts index 4988af09a1a..9ecc104f5e2 100644 --- a/src/cli/cron-cli.test.ts +++ b/src/cli/cron-cli.test.ts @@ -25,17 +25,24 @@ vi.mock("./gateway-rpc.js", async () => { }; }); -vi.mock("../runtime.js", () => ({ - defaultRuntime: { - log: vi.fn(), - error: vi.fn(), - writeStdout: vi.fn(), - writeJson: vi.fn(), - exit: (code: number) => { - throw new Error(`__exit__:${code}`); +vi.mock("../runtime.js", async (importOriginal) => { + const actual = await importOriginal(); + const log = vi.fn(); + return { + ...actual, + defaultRuntime: { + ...actual.defaultRuntime, + log, + error: vi.fn(), + writeStdout: (value: string) => log(value.endsWith("\n") ? value.slice(0, -1) : value), + writeJson: (value: unknown, space = 2) => + log(JSON.stringify(value, null, space > 0 ? space : undefined)), + exit: (code: number) => { + throw new Error(`__exit__:${code}`); + }, }, - }, -})); + }; +}); const { registerCronCli } = await import("./cron-cli.js"); diff --git a/src/cli/daemon-cli.coverage.test.ts b/src/cli/daemon-cli.coverage.test.ts index 8faf44cdde3..765c920d624 100644 --- a/src/cli/daemon-cli.coverage.test.ts +++ b/src/cli/daemon-cli.coverage.test.ts @@ -81,7 +81,8 @@ vi.mock("../infra/ports.js", () => ({ formatPortDiagnostics: () => ["Port 18789 is already in use."], })); -vi.mock("../runtime.js", () => ({ +vi.mock("../runtime.js", async (importOriginal) => ({ + ...(await importOriginal()), defaultRuntime, })); diff --git a/src/cli/daemon-cli/install.integration.test.ts b/src/cli/daemon-cli/install.integration.test.ts index 52272b61f55..92e9aff2cad 100644 --- a/src/cli/daemon-cli/install.integration.test.ts +++ b/src/cli/daemon-cli/install.integration.test.ts @@ -24,21 +24,24 @@ vi.mock("../../daemon/service.js", () => ({ resolveGatewayService: () => serviceMock, })); -vi.mock("../../runtime.js", () => ({ - defaultRuntime: { - log: (message: string) => runtimeLogs.push(message), - error: (message: string) => runtimeErrors.push(message), - writeStdout: (value: string) => { - runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value); +vi.mock("../../runtime.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + defaultRuntime: { + ...actual.defaultRuntime, + log: (message: string) => runtimeLogs.push(message), + error: (message: string) => runtimeErrors.push(message), + writeStdout: (value: string) => + runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value), + writeJson: (value: unknown, space = 2) => + runtimeLogs.push(JSON.stringify(value, null, space > 0 ? space : undefined)), + exit: (code: number) => { + throw new Error(`__exit__:${code}`); + }, }, - writeJson: (value: unknown, space = 2) => { - runtimeLogs.push(JSON.stringify(value, null, space > 0 ? space : undefined)); - }, - exit: (code: number) => { - throw new Error(`__exit__:${code}`); - }, - }, -})); + }; +}); const { runDaemonInstall } = await import("./install.js"); const { clearConfigCache } = await import("../../config/config.js"); diff --git a/src/cli/daemon-cli/test-helpers/lifecycle-core-harness.ts b/src/cli/daemon-cli/test-helpers/lifecycle-core-harness.ts index 7ecf4aac9ab..e79df93173a 100644 --- a/src/cli/daemon-cli/test-helpers/lifecycle-core-harness.ts +++ b/src/cli/daemon-cli/test-helpers/lifecycle-core-harness.ts @@ -8,8 +8,6 @@ export const runtimeLogs: string[] = []; type LifecycleRuntimeHarness = OutputRuntimeEnv & { error: MockFn; exit: MockFn; - writeStdout: MockFn<(value: string) => void>; - writeJson: MockFn<(value: unknown, space?: number) => void>; }; type LifecycleServiceHarness = GatewayService & { @@ -26,16 +24,16 @@ export const defaultRuntime: LifecycleRuntimeHarness = { log: (...args: unknown[]) => { runtimeLogs.push(args.map((arg) => String(arg)).join(" ")); }, + writeStdout: (value: string) => { + runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value); + }, + writeJson: (value: unknown, space = 2) => { + runtimeLogs.push(JSON.stringify(value, null, space > 0 ? space : undefined)); + }, error: vi.fn(), exit: vi.fn((code: number) => { throw new Error(`__exit__:${code}`); }), - writeStdout: vi.fn((value: string) => { - runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value); - }), - writeJson: vi.fn((value: unknown, space = 2) => { - runtimeLogs.push(JSON.stringify(value, null, space > 0 ? space : undefined)); - }), }; export const service: LifecycleServiceHarness = { diff --git a/src/cli/devices-cli.test.ts b/src/cli/devices-cli.test.ts index bf3627fc72e..e3c58cfcee8 100644 --- a/src/cli/devices-cli.test.ts +++ b/src/cli/devices-cli.test.ts @@ -38,7 +38,8 @@ vi.mock("../infra/device-pairing.js", () => ({ summarizeDeviceTokens, })); -vi.mock("../runtime.js", () => ({ +vi.mock("../runtime.js", async (importOriginal) => ({ + ...(await importOriginal()), defaultRuntime: runtime, })); diff --git a/src/cli/directory-cli.test.ts b/src/cli/directory-cli.test.ts index f2e2208bf54..d1f32aa8207 100644 --- a/src/cli/directory-cli.test.ts +++ b/src/cli/directory-cli.test.ts @@ -35,16 +35,21 @@ vi.mock("../channels/plugins/helpers.js", () => ({ resolveChannelDefaultAccountId: mocks.resolveChannelDefaultAccountId, })); -vi.mock("../runtime.js", () => ({ - defaultRuntime: { - log: (...args: unknown[]) => mocks.log(...args), - error: (...args: unknown[]) => mocks.error(...args), - writeStdout: (value: string) => mocks.log(value.endsWith("\n") ? value.slice(0, -1) : value), - writeJson: (value: unknown, space = 2) => - mocks.log(JSON.stringify(value, null, space > 0 ? space : undefined)), - exit: (...args: unknown[]) => mocks.exit(...args), - }, -})); +vi.mock("../runtime.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + defaultRuntime: { + ...actual.defaultRuntime, + log: (...args: unknown[]) => mocks.log(...args), + error: (...args: unknown[]) => mocks.error(...args), + writeStdout: (value: string) => mocks.log(value.endsWith("\n") ? value.slice(0, -1) : value), + writeJson: (value: unknown, space = 2) => + mocks.log(JSON.stringify(value, null, space > 0 ? space : undefined)), + exit: (...args: unknown[]) => mocks.exit(...args), + }, + }; +}); describe("registerDirectoryCli", () => { beforeEach(() => { diff --git a/src/cli/gateway-cli.coverage.test.ts b/src/cli/gateway-cli.coverage.test.ts index 394a5d680d6..3beac2f67fa 100644 --- a/src/cli/gateway-cli.coverage.test.ts +++ b/src/cli/gateway-cli.coverage.test.ts @@ -51,7 +51,8 @@ vi.mock("../globals.js", () => ({ setVerbose: (enabled: boolean) => setVerbose(enabled), })); -vi.mock("../runtime.js", () => ({ +vi.mock("../runtime.js", async (importOriginal) => ({ + ...(await importOriginal()), defaultRuntime, })); diff --git a/src/cli/gateway-cli/register.option-collisions.test.ts b/src/cli/gateway-cli/register.option-collisions.test.ts index 665886c76eb..d8ffe706b10 100644 --- a/src/cli/gateway-cli/register.option-collisions.test.ts +++ b/src/cli/gateway-cli/register.option-collisions.test.ts @@ -23,7 +23,8 @@ vi.mock("../cli-utils.js", () => ({ }, })); -vi.mock("../../runtime.js", () => ({ +vi.mock("../../runtime.js", async (importOriginal) => ({ + ...(await importOriginal()), defaultRuntime, })); diff --git a/src/cli/mcp-cli.test.ts b/src/cli/mcp-cli.test.ts index eb2f0fbdb73..a7defd5c5ba 100644 --- a/src/cli/mcp-cli.test.ts +++ b/src/cli/mcp-cli.test.ts @@ -11,16 +11,21 @@ const mockExit = vi.fn((code: number) => { throw new Error(`__exit__:${code}`); }); -vi.mock("../runtime.js", () => ({ - defaultRuntime: { - log: (...args: unknown[]) => mockLog(...args), - error: (...args: unknown[]) => mockError(...args), - writeStdout: (value: string) => mockLog(value.endsWith("\n") ? value.slice(0, -1) : value), - writeJson: (value: unknown, space = 2) => - mockLog(JSON.stringify(value, null, space > 0 ? space : undefined)), - exit: (code: number) => mockExit(code), - }, -})); +vi.mock("../runtime.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + defaultRuntime: { + ...actual.defaultRuntime, + log: (...args: unknown[]) => mockLog(...args), + error: (...args: unknown[]) => mockError(...args), + writeStdout: (value: string) => mockLog(value.endsWith("\n") ? value.slice(0, -1) : value), + writeJson: (value: unknown, space = 2) => + mockLog(JSON.stringify(value, null, space > 0 ? space : undefined)), + exit: (code: number) => mockExit(code), + }, + }; +}); const tempDirs: string[] = []; diff --git a/src/cli/memory-cli.test.ts b/src/cli/memory-cli.test.ts index ac55bd07cce..1bf9874854c 100644 --- a/src/cli/memory-cli.test.ts +++ b/src/cli/memory-cli.test.ts @@ -61,7 +61,7 @@ describe("memory cli", () => { function spyRuntimeLogs() { const logSpy = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); vi.spyOn(defaultRuntime, "writeJson").mockImplementation((value: unknown, space = 2) => { - logSpy(JSON.stringify(value, null, space)); + logSpy(JSON.stringify(value, null, space > 0 ? space : undefined)); }); return logSpy; } diff --git a/src/cli/nodes-cli.coverage.test.ts b/src/cli/nodes-cli.coverage.test.ts index 81d0f17c07c..99a21580100 100644 --- a/src/cli/nodes-cli.coverage.test.ts +++ b/src/cli/nodes-cli.coverage.test.ts @@ -88,7 +88,8 @@ vi.mock("../gateway/call.js", () => ({ randomIdempotencyKey: () => randomIdempotencyKey(), })); -vi.mock("../runtime.js", () => ({ +vi.mock("../runtime.js", async (importOriginal) => ({ + ...(await importOriginal()), defaultRuntime, })); diff --git a/src/cli/qr-cli.test.ts b/src/cli/qr-cli.test.ts index c7d54a6247a..ef0361fe242 100644 --- a/src/cli/qr-cli.test.ts +++ b/src/cli/qr-cli.test.ts @@ -27,7 +27,10 @@ const mocks = vi.hoisted(() => ({ }), })); -vi.mock("../runtime.js", () => ({ defaultRuntime: mocks.runtime })); +vi.mock("../runtime.js", async (importOriginal) => ({ + ...(await importOriginal()), + defaultRuntime: mocks.runtime, +})); vi.mock("../config/config.js", () => ({ loadConfig: mocks.loadConfig })); vi.mock("../process/exec.js", () => ({ runCommandWithTimeout: mocks.runCommandWithTimeout })); vi.mock("./command-secret-gateway.js", () => ({ diff --git a/src/cli/system-cli.test.ts b/src/cli/system-cli.test.ts index 3b0cfeb84a0..7d86b503440 100644 --- a/src/cli/system-cli.test.ts +++ b/src/cli/system-cli.test.ts @@ -13,7 +13,8 @@ vi.mock("./gateway-rpc.js", () => ({ callGatewayFromCli, })); -vi.mock("../runtime.js", () => ({ +vi.mock("../runtime.js", async (importOriginal) => ({ + ...(await importOriginal()), defaultRuntime, })); diff --git a/src/cli/test-runtime-capture.ts b/src/cli/test-runtime-capture.ts index 6ff5df0b0de..02fbb63fdb1 100644 --- a/src/cli/test-runtime-capture.ts +++ b/src/cli/test-runtime-capture.ts @@ -3,7 +3,7 @@ import type { OutputRuntimeEnv } from "../runtime.js"; export type CliRuntimeCapture = { runtimeLogs: string[]; runtimeErrors: string[]; - defaultRuntime: Pick; + defaultRuntime: Pick; resetRuntimeCapture: () => void; }; @@ -11,6 +11,9 @@ export function createCliRuntimeCapture(): CliRuntimeCapture { const runtimeLogs: string[] = []; const runtimeErrors: string[] = []; const stringifyArgs = (args: unknown[]) => args.map((value) => String(value)).join(" "); + const writeLine = (value: string) => { + runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value); + }; return { runtimeLogs, runtimeErrors, @@ -22,10 +25,10 @@ export function createCliRuntimeCapture(): CliRuntimeCapture { runtimeErrors.push(stringifyArgs(args)); }, writeStdout: (value: string) => { - runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value); + writeLine(value); }, writeJson: (value: unknown, space = 2) => { - runtimeLogs.push(JSON.stringify(value, null, space > 0 ? space : undefined)); + writeLine(JSON.stringify(value, null, space > 0 ? space : undefined)); }, exit: (code: number) => { throw new Error(`__exit__:${code}`); diff --git a/src/cli/update-cli.test.ts b/src/cli/update-cli.test.ts index 1433c25e5ee..c57082f1b39 100644 --- a/src/cli/update-cli.test.ts +++ b/src/cli/update-cli.test.ts @@ -25,12 +25,6 @@ const formatPortDiagnostics = vi.fn(); const pathExists = vi.fn(); const syncPluginsForUpdateChannel = vi.fn(); const updateNpmInstalledPlugins = vi.fn(); -const runtimeLog = vi.fn(); -const runtimeError = vi.fn(); -const runtimeExit = vi.fn(); -const runtimeWriteJson = vi.fn((value: unknown, space = 2) => - runtimeLog(JSON.stringify(value, null, space > 0 ? space : undefined)), -); vi.mock("@clack/prompts", () => ({ confirm, @@ -135,17 +129,22 @@ vi.mock("./daemon-cli.js", () => ({ })); // Mock the runtime -vi.mock("../runtime.js", () => ({ - defaultRuntime: { - log: runtimeLog, - error: runtimeError, - exit: runtimeExit, - writeStdout: vi.fn((value: string) => - runtimeLog(value.endsWith("\n") ? value.slice(0, -1) : value), - ), - writeJson: runtimeWriteJson, - }, -})); +vi.mock("../runtime.js", async (importOriginal) => { + const actual = await importOriginal(); + const log = vi.fn(); + return { + ...actual, + defaultRuntime: { + ...actual.defaultRuntime, + log, + error: vi.fn(), + writeStdout: (value: string) => log(value.endsWith("\n") ? value.slice(0, -1) : value), + writeJson: (value: unknown, space = 2) => + log(JSON.stringify(value, null, space > 0 ? space : undefined)), + exit: vi.fn(), + }, + }; +}); const { runGatewayUpdate } = await import("../infra/update-runner.js"); const { resolveOpenClawPackageRoot } = await import("../infra/openclaw-root.js");