diff --git a/src/daemon/exec-file.ts b/src/daemon/exec-file.ts new file mode 100644 index 00000000000..6067a160284 --- /dev/null +++ b/src/daemon/exec-file.ts @@ -0,0 +1,32 @@ +import { execFile, type ExecFileOptionsWithStringEncoding } from "node:child_process"; + +export type ExecResult = { stdout: string; stderr: string; code: number }; + +export async function execFileUtf8( + command: string, + args: string[], + options: Omit = {}, +): Promise { + return await new Promise((resolve) => { + execFile(command, args, { ...options, encoding: "utf8" }, (error, stdout, stderr) => { + if (!error) { + resolve({ + stdout: String(stdout ?? ""), + stderr: String(stderr ?? ""), + code: 0, + }); + return; + } + + const e = error as { code?: unknown; message?: unknown }; + const stderrText = String(stderr ?? ""); + resolve({ + stdout: String(stdout ?? ""), + stderr: + stderrText || + (typeof e.message === "string" ? e.message : typeof error === "string" ? error : ""), + code: typeof e.code === "number" ? e.code : 1, + }); + }); + }); +} diff --git a/src/daemon/launchd.ts b/src/daemon/launchd.ts index 7da2f78ebef..3d33af682dc 100644 --- a/src/daemon/launchd.ts +++ b/src/daemon/launchd.ts @@ -1,7 +1,5 @@ -import { execFile } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; -import { promisify } from "node:util"; import type { GatewayServiceRuntime } from "./service-runtime.js"; import { formatGatewayServiceDescription, @@ -9,6 +7,7 @@ import { resolveGatewayLaunchAgentLabel, resolveLegacyGatewayLaunchAgentLabels, } from "./constants.js"; +import { execFileUtf8 } from "./exec-file.js"; import { buildLaunchAgentPlist as buildLaunchAgentPlistImpl, readLaunchAgentProgramArgumentsFromFile, @@ -17,8 +16,6 @@ import { formatLine, toPosixPath } from "./output.js"; import { resolveGatewayStateDir, resolveHomeDir } from "./paths.js"; import { parseKeyValueOutput } from "./runtime-parse.js"; -const execFileAsync = promisify(execFile); - function resolveLaunchAgentLabel(args?: { env?: Record }): string { const envLabel = args?.env?.OPENCLAW_LAUNCHD_LABEL?.trim(); if (envLabel) { @@ -98,30 +95,10 @@ export function buildLaunchAgentPlist({ async function execLaunchctl( args: string[], ): Promise<{ stdout: string; stderr: string; code: number }> { - try { - const isWindows = process.platform === "win32"; - const file = isWindows ? (process.env.ComSpec ?? "cmd.exe") : "launchctl"; - const fileArgs = isWindows ? ["/d", "/s", "/c", "launchctl", ...args] : args; - const { stdout, stderr } = await execFileAsync(file, fileArgs, { encoding: "utf8" }); - return { - stdout: String(stdout ?? ""), - stderr: String(stderr ?? ""), - code: 0, - }; - } catch (error) { - const e = error as { - stdout?: unknown; - stderr?: unknown; - code?: unknown; - message?: unknown; - }; - return { - stdout: typeof e.stdout === "string" ? e.stdout : "", - stderr: - typeof e.stderr === "string" ? e.stderr : typeof e.message === "string" ? e.message : "", - code: typeof e.code === "number" ? e.code : 1, - }; - } + const isWindows = process.platform === "win32"; + const file = isWindows ? (process.env.ComSpec ?? "cmd.exe") : "launchctl"; + const fileArgs = isWindows ? ["/d", "/s", "/c", "launchctl", ...args] : args; + return await execFileUtf8(file, fileArgs, isWindows ? { windowsHide: true } : {}); } function resolveGuiDomain(): string { diff --git a/src/daemon/schtasks-exec.ts b/src/daemon/schtasks-exec.ts index c9c2628b44b..e4344d3cd5d 100644 --- a/src/daemon/schtasks-exec.ts +++ b/src/daemon/schtasks-exec.ts @@ -1,33 +1,7 @@ -import { execFile } from "node:child_process"; -import { promisify } from "node:util"; - -const execFileAsync = promisify(execFile); +import { execFileUtf8 } from "./exec-file.js"; export async function execSchtasks( args: string[], ): Promise<{ stdout: string; stderr: string; code: number }> { - try { - const { stdout, stderr } = await execFileAsync("schtasks", args, { - encoding: "utf8", - windowsHide: true, - }); - return { - stdout: String(stdout ?? ""), - stderr: String(stderr ?? ""), - code: 0, - }; - } catch (error) { - const e = error as { - stdout?: unknown; - stderr?: unknown; - code?: unknown; - message?: unknown; - }; - return { - stdout: typeof e.stdout === "string" ? e.stdout : "", - stderr: - typeof e.stderr === "string" ? e.stderr : typeof e.message === "string" ? e.message : "", - code: typeof e.code === "number" ? e.code : 1, - }; - } + return await execFileUtf8("schtasks", args, { windowsHide: true }); } diff --git a/src/daemon/systemd.ts b/src/daemon/systemd.ts index e4d45368d9b..6ef8af5765f 100644 --- a/src/daemon/systemd.ts +++ b/src/daemon/systemd.ts @@ -1,13 +1,12 @@ -import { execFile } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; -import { promisify } from "node:util"; import type { GatewayServiceRuntime } from "./service-runtime.js"; import { formatGatewayServiceDescription, LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES, resolveGatewaySystemdServiceName, } from "./constants.js"; +import { execFileUtf8 } from "./exec-file.js"; import { formatLine, toPosixPath } from "./output.js"; import { resolveHomeDir } from "./paths.js"; import { parseKeyValueOutput } from "./runtime-parse.js"; @@ -22,8 +21,6 @@ import { parseSystemdExecStart, } from "./systemd-unit.js"; -const execFileAsync = promisify(execFile); - function resolveSystemdUnitPathForName( env: Record, name: string, @@ -142,29 +139,7 @@ export function parseSystemdShow(output: string): SystemdServiceInfo { async function execSystemctl( args: string[], ): Promise<{ stdout: string; stderr: string; code: number }> { - try { - const { stdout, stderr } = await execFileAsync("systemctl", args, { - encoding: "utf8", - }); - return { - stdout: String(stdout ?? ""), - stderr: String(stderr ?? ""), - code: 0, - }; - } catch (error) { - const e = error as { - stdout?: unknown; - stderr?: unknown; - code?: unknown; - message?: unknown; - }; - return { - stdout: typeof e.stdout === "string" ? e.stdout : "", - stderr: - typeof e.stderr === "string" ? e.stderr : typeof e.message === "string" ? e.message : "", - code: typeof e.code === "number" ? e.code : 1, - }; - } + return await execFileUtf8("systemctl", args); } export async function isSystemdUserServiceAvailable(): Promise {