From c35f529fac4bc0f09076cb500aa376c7aaa8f100 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 7 Mar 2026 21:08:43 +0000 Subject: [PATCH] refactor: share daemon install plan runtime scaffolding --- src/commands/daemon-install-helpers.ts | 33 ++++++-------- .../daemon-install-plan.shared.test.ts | 31 +++++++++++++ src/commands/daemon-install-plan.shared.ts | 44 +++++++++++++++++++ src/commands/node-daemon-install-helpers.ts | 26 +++++------ 4 files changed, 101 insertions(+), 33 deletions(-) create mode 100644 src/commands/daemon-install-plan.shared.test.ts create mode 100644 src/commands/daemon-install-plan.shared.ts diff --git a/src/commands/daemon-install-helpers.ts b/src/commands/daemon-install-helpers.ts index 8bcd717c3df..0a548acf799 100644 --- a/src/commands/daemon-install-helpers.ts +++ b/src/commands/daemon-install-helpers.ts @@ -3,26 +3,22 @@ import { collectConfigServiceEnvVars } from "../config/env-vars.js"; import type { OpenClawConfig } from "../config/types.js"; import { resolveGatewayLaunchAgentLabel } from "../daemon/constants.js"; import { resolveGatewayProgramArguments } from "../daemon/program-args.js"; -import { resolvePreferredNodePath } from "../daemon/runtime-paths.js"; import { buildServiceEnvironment } from "../daemon/service-env.js"; import { - emitNodeRuntimeWarning, - type DaemonInstallWarnFn, -} from "./daemon-install-runtime-warning.js"; + emitDaemonInstallRuntimeWarning, + resolveDaemonInstallRuntimeInputs, +} from "./daemon-install-plan.shared.js"; +import type { DaemonInstallWarnFn } from "./daemon-install-runtime-warning.js"; import type { GatewayDaemonRuntime } from "./daemon-runtime.js"; +export { resolveGatewayDevMode } from "./daemon-install-plan.shared.js"; + export type GatewayInstallPlan = { programArguments: string[]; workingDirectory?: string; environment: Record; }; -export function resolveGatewayDevMode(argv: string[] = process.argv): boolean { - const entry = argv[1]; - const normalizedEntry = entry?.replaceAll("\\", "/"); - return Boolean(normalizedEntry?.includes("/src/") && normalizedEntry.endsWith(".ts")); -} - export async function buildGatewayInstallPlan(params: { env: Record; port: number; @@ -34,23 +30,22 @@ export async function buildGatewayInstallPlan(params: { /** Full config to extract env vars from (env vars + inline env keys). */ config?: OpenClawConfig; }): Promise { - const devMode = params.devMode ?? resolveGatewayDevMode(); - const nodePath = - params.nodePath ?? - (await resolvePreferredNodePath({ - env: params.env, - runtime: params.runtime, - })); + const { devMode, nodePath } = await resolveDaemonInstallRuntimeInputs({ + env: params.env, + runtime: params.runtime, + devMode: params.devMode, + nodePath: params.nodePath, + }); const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({ port: params.port, dev: devMode, runtime: params.runtime, nodePath, }); - await emitNodeRuntimeWarning({ + await emitDaemonInstallRuntimeWarning({ env: params.env, runtime: params.runtime, - nodeProgram: programArguments[0], + programArguments, warn: params.warn, title: "Gateway runtime", }); diff --git a/src/commands/daemon-install-plan.shared.test.ts b/src/commands/daemon-install-plan.shared.test.ts new file mode 100644 index 00000000000..399b521a5d5 --- /dev/null +++ b/src/commands/daemon-install-plan.shared.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "vitest"; +import { + resolveDaemonInstallRuntimeInputs, + resolveGatewayDevMode, +} from "./daemon-install-plan.shared.js"; + +describe("resolveGatewayDevMode", () => { + it("detects src ts entrypoints", () => { + expect(resolveGatewayDevMode(["node", "/Users/me/openclaw/src/cli/index.ts"])).toBe(true); + expect(resolveGatewayDevMode(["node", "C:\\Users\\me\\openclaw\\src\\cli\\index.ts"])).toBe( + true, + ); + expect(resolveGatewayDevMode(["node", "/Users/me/openclaw/dist/cli/index.js"])).toBe(false); + }); +}); + +describe("resolveDaemonInstallRuntimeInputs", () => { + it("keeps explicit devMode and nodePath overrides", async () => { + await expect( + resolveDaemonInstallRuntimeInputs({ + env: {}, + runtime: "node", + devMode: false, + nodePath: "/custom/node", + }), + ).resolves.toEqual({ + devMode: false, + nodePath: "/custom/node", + }); + }); +}); diff --git a/src/commands/daemon-install-plan.shared.ts b/src/commands/daemon-install-plan.shared.ts new file mode 100644 index 00000000000..b3a970d05f4 --- /dev/null +++ b/src/commands/daemon-install-plan.shared.ts @@ -0,0 +1,44 @@ +import { resolvePreferredNodePath } from "../daemon/runtime-paths.js"; +import { + emitNodeRuntimeWarning, + type DaemonInstallWarnFn, +} from "./daemon-install-runtime-warning.js"; +import type { GatewayDaemonRuntime } from "./daemon-runtime.js"; + +export function resolveGatewayDevMode(argv: string[] = process.argv): boolean { + const entry = argv[1]; + const normalizedEntry = entry?.replaceAll("\\", "/"); + return Boolean(normalizedEntry?.includes("/src/") && normalizedEntry.endsWith(".ts")); +} + +export async function resolveDaemonInstallRuntimeInputs(params: { + env: Record; + runtime: GatewayDaemonRuntime; + devMode?: boolean; + nodePath?: string; +}): Promise<{ devMode: boolean; nodePath?: string }> { + const devMode = params.devMode ?? resolveGatewayDevMode(); + const nodePath = + params.nodePath ?? + (await resolvePreferredNodePath({ + env: params.env, + runtime: params.runtime, + })); + return { devMode, nodePath }; +} + +export async function emitDaemonInstallRuntimeWarning(params: { + env: Record; + runtime: GatewayDaemonRuntime; + programArguments: string[]; + warn?: DaemonInstallWarnFn; + title: string; +}): Promise { + await emitNodeRuntimeWarning({ + env: params.env, + runtime: params.runtime, + nodeProgram: params.programArguments[0], + warn: params.warn, + title: params.title, + }); +} diff --git a/src/commands/node-daemon-install-helpers.ts b/src/commands/node-daemon-install-helpers.ts index c2bab673e4f..2f86d1c3b5e 100644 --- a/src/commands/node-daemon-install-helpers.ts +++ b/src/commands/node-daemon-install-helpers.ts @@ -1,12 +1,11 @@ import { formatNodeServiceDescription } from "../daemon/constants.js"; import { resolveNodeProgramArguments } from "../daemon/program-args.js"; -import { resolvePreferredNodePath } from "../daemon/runtime-paths.js"; import { buildNodeServiceEnvironment } from "../daemon/service-env.js"; -import { resolveGatewayDevMode } from "./daemon-install-helpers.js"; import { - emitNodeRuntimeWarning, - type DaemonInstallWarnFn, -} from "./daemon-install-runtime-warning.js"; + emitDaemonInstallRuntimeWarning, + resolveDaemonInstallRuntimeInputs, +} from "./daemon-install-plan.shared.js"; +import type { DaemonInstallWarnFn } from "./daemon-install-runtime-warning.js"; import type { NodeDaemonRuntime } from "./node-daemon-runtime.js"; export type NodeInstallPlan = { @@ -29,13 +28,12 @@ export async function buildNodeInstallPlan(params: { nodePath?: string; warn?: DaemonInstallWarnFn; }): Promise { - const devMode = params.devMode ?? resolveGatewayDevMode(); - const nodePath = - params.nodePath ?? - (await resolvePreferredNodePath({ - env: params.env, - runtime: params.runtime, - })); + const { devMode, nodePath } = await resolveDaemonInstallRuntimeInputs({ + env: params.env, + runtime: params.runtime, + devMode: params.devMode, + nodePath: params.nodePath, + }); const { programArguments, workingDirectory } = await resolveNodeProgramArguments({ host: params.host, port: params.port, @@ -48,10 +46,10 @@ export async function buildNodeInstallPlan(params: { nodePath, }); - await emitNodeRuntimeWarning({ + await emitDaemonInstallRuntimeWarning({ env: params.env, runtime: params.runtime, - nodeProgram: programArguments[0], + programArguments, warn: params.warn, title: "Node daemon runtime", });