From 1b9c1c648ddf5d780709c2c1a3d02143e78219cf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 14 Feb 2026 14:00:34 +0000 Subject: [PATCH] refactor(daemon): share service lifecycle runner --- src/cli/daemon-cli/lifecycle-core.ts | 265 ++++++++++++++++++++++ src/cli/daemon-cli/lifecycle.ts | 323 +++------------------------ src/cli/node-cli/daemon.ts | 301 +++---------------------- 3 files changed, 318 insertions(+), 571 deletions(-) create mode 100644 src/cli/daemon-cli/lifecycle-core.ts diff --git a/src/cli/daemon-cli/lifecycle-core.ts b/src/cli/daemon-cli/lifecycle-core.ts new file mode 100644 index 000000000..6edd0a8c4 --- /dev/null +++ b/src/cli/daemon-cli/lifecycle-core.ts @@ -0,0 +1,265 @@ +import type { GatewayService } from "../../daemon/service.js"; +import { resolveIsNixMode } from "../../config/paths.js"; +import { renderSystemdUnavailableHints } from "../../daemon/systemd-hints.js"; +import { isSystemdUserServiceAvailable } from "../../daemon/systemd.js"; +import { isWSL } from "../../infra/wsl.js"; +import { defaultRuntime } from "../../runtime.js"; +import { + buildDaemonServiceSnapshot, + createNullWriter, + type DaemonAction, + emitDaemonActionJson, +} from "./response.js"; + +type DaemonLifecycleOptions = { + json?: boolean; +}; + +async function maybeAugmentSystemdHints(hints: string[]): Promise { + if (process.platform !== "linux") { + return hints; + } + const systemdAvailable = await isSystemdUserServiceAvailable().catch(() => false); + if (systemdAvailable) { + return hints; + } + return [...hints, ...renderSystemdUnavailableHints({ wsl: await isWSL() })]; +} + +function createActionIO(params: { action: DaemonAction; json: boolean }) { + const stdout = params.json ? createNullWriter() : process.stdout; + const emit = (payload: { + ok: boolean; + result?: string; + message?: string; + error?: string; + hints?: string[]; + service?: { + label: string; + loaded: boolean; + loadedText: string; + notLoadedText: string; + }; + }) => { + if (!params.json) { + return; + } + emitDaemonActionJson({ action: params.action, ...payload }); + }; + const fail = (message: string, hints?: string[]) => { + if (params.json) { + emit({ ok: false, error: message, hints }); + } else { + defaultRuntime.error(message); + } + defaultRuntime.exit(1); + }; + return { stdout, emit, fail }; +} + +export async function runServiceUninstall(params: { + serviceNoun: string; + service: GatewayService; + opts?: DaemonLifecycleOptions; + stopBeforeUninstall: boolean; + assertNotLoadedAfterUninstall: boolean; +}) { + const json = Boolean(params.opts?.json); + const { stdout, emit, fail } = createActionIO({ action: "uninstall", json }); + + if (resolveIsNixMode(process.env)) { + fail("Nix mode detected; service uninstall is disabled."); + return; + } + + let loaded = false; + try { + loaded = await params.service.isLoaded({ env: process.env }); + } catch { + loaded = false; + } + if (loaded && params.stopBeforeUninstall) { + try { + await params.service.stop({ env: process.env, stdout }); + } catch { + // Best-effort stop; final loaded check gates success when enabled. + } + } + try { + await params.service.uninstall({ env: process.env, stdout }); + } catch (err) { + fail(`${params.serviceNoun} uninstall failed: ${String(err)}`); + return; + } + + loaded = false; + try { + loaded = await params.service.isLoaded({ env: process.env }); + } catch { + loaded = false; + } + if (loaded && params.assertNotLoadedAfterUninstall) { + fail(`${params.serviceNoun} service still loaded after uninstall.`); + return; + } + emit({ + ok: true, + result: "uninstalled", + service: buildDaemonServiceSnapshot(params.service, loaded), + }); +} + +export async function runServiceStart(params: { + serviceNoun: string; + service: GatewayService; + renderStartHints: () => string[]; + opts?: DaemonLifecycleOptions; +}) { + const json = Boolean(params.opts?.json); + const { stdout, emit, fail } = createActionIO({ action: "start", json }); + + let loaded = false; + try { + loaded = await params.service.isLoaded({ env: process.env }); + } catch (err) { + fail(`${params.serviceNoun} service check failed: ${String(err)}`); + return; + } + if (!loaded) { + const hints = await maybeAugmentSystemdHints(params.renderStartHints()); + emit({ + ok: true, + result: "not-loaded", + message: `${params.serviceNoun} service ${params.service.notLoadedText}.`, + hints, + service: buildDaemonServiceSnapshot(params.service, loaded), + }); + if (!json) { + defaultRuntime.log(`${params.serviceNoun} service ${params.service.notLoadedText}.`); + for (const hint of hints) { + defaultRuntime.log(`Start with: ${hint}`); + } + } + return; + } + try { + await params.service.restart({ env: process.env, stdout }); + } catch (err) { + const hints = params.renderStartHints(); + fail(`${params.serviceNoun} start failed: ${String(err)}`, hints); + return; + } + + let started = true; + try { + started = await params.service.isLoaded({ env: process.env }); + } catch { + started = true; + } + emit({ + ok: true, + result: "started", + service: buildDaemonServiceSnapshot(params.service, started), + }); +} + +export async function runServiceStop(params: { + serviceNoun: string; + service: GatewayService; + opts?: DaemonLifecycleOptions; +}) { + const json = Boolean(params.opts?.json); + const { stdout, emit, fail } = createActionIO({ action: "stop", json }); + + let loaded = false; + try { + loaded = await params.service.isLoaded({ env: process.env }); + } catch (err) { + fail(`${params.serviceNoun} service check failed: ${String(err)}`); + return; + } + if (!loaded) { + emit({ + ok: true, + result: "not-loaded", + message: `${params.serviceNoun} service ${params.service.notLoadedText}.`, + service: buildDaemonServiceSnapshot(params.service, loaded), + }); + if (!json) { + defaultRuntime.log(`${params.serviceNoun} service ${params.service.notLoadedText}.`); + } + return; + } + try { + await params.service.stop({ env: process.env, stdout }); + } catch (err) { + fail(`${params.serviceNoun} stop failed: ${String(err)}`); + return; + } + + let stopped = false; + try { + stopped = await params.service.isLoaded({ env: process.env }); + } catch { + stopped = false; + } + emit({ + ok: true, + result: "stopped", + service: buildDaemonServiceSnapshot(params.service, stopped), + }); +} + +export async function runServiceRestart(params: { + serviceNoun: string; + service: GatewayService; + renderStartHints: () => string[]; + opts?: DaemonLifecycleOptions; +}): Promise { + const json = Boolean(params.opts?.json); + const { stdout, emit, fail } = createActionIO({ action: "restart", json }); + + let loaded = false; + try { + loaded = await params.service.isLoaded({ env: process.env }); + } catch (err) { + fail(`${params.serviceNoun} service check failed: ${String(err)}`); + return false; + } + if (!loaded) { + const hints = await maybeAugmentSystemdHints(params.renderStartHints()); + emit({ + ok: true, + result: "not-loaded", + message: `${params.serviceNoun} service ${params.service.notLoadedText}.`, + hints, + service: buildDaemonServiceSnapshot(params.service, loaded), + }); + if (!json) { + defaultRuntime.log(`${params.serviceNoun} service ${params.service.notLoadedText}.`); + for (const hint of hints) { + defaultRuntime.log(`Start with: ${hint}`); + } + } + return false; + } + try { + await params.service.restart({ env: process.env, stdout }); + let restarted = true; + try { + restarted = await params.service.isLoaded({ env: process.env }); + } catch { + restarted = true; + } + emit({ + ok: true, + result: "restarted", + service: buildDaemonServiceSnapshot(params.service, restarted), + }); + return true; + } catch (err) { + const hints = params.renderStartHints(); + fail(`${params.serviceNoun} restart failed: ${String(err)}`, hints); + return false; + } +} diff --git a/src/cli/daemon-cli/lifecycle.ts b/src/cli/daemon-cli/lifecycle.ts index ef3574b53..641848374 100644 --- a/src/cli/daemon-cli/lifecycle.ts +++ b/src/cli/daemon-cli/lifecycle.ts @@ -1,233 +1,37 @@ import type { DaemonLifecycleOptions } from "./types.js"; -import { resolveIsNixMode } from "../../config/paths.js"; import { resolveGatewayService } from "../../daemon/service.js"; -import { renderSystemdUnavailableHints } from "../../daemon/systemd-hints.js"; -import { isSystemdUserServiceAvailable } from "../../daemon/systemd.js"; -import { isWSL } from "../../infra/wsl.js"; -import { defaultRuntime } from "../../runtime.js"; -import { buildDaemonServiceSnapshot, createNullWriter, emitDaemonActionJson } from "./response.js"; +import { + runServiceRestart, + runServiceStart, + runServiceStop, + runServiceUninstall, +} from "./lifecycle-core.js"; import { renderGatewayServiceStartHints } from "./shared.js"; export async function runDaemonUninstall(opts: DaemonLifecycleOptions = {}) { - const json = Boolean(opts.json); - const stdout = json ? createNullWriter() : process.stdout; - const emit = (payload: { - ok: boolean; - result?: string; - message?: string; - error?: string; - service?: { - label: string; - loaded: boolean; - loadedText: string; - notLoadedText: string; - }; - }) => { - if (!json) { - return; - } - emitDaemonActionJson({ action: "uninstall", ...payload }); - }; - const fail = (message: string) => { - if (json) { - emit({ ok: false, error: message }); - } else { - defaultRuntime.error(message); - } - defaultRuntime.exit(1); - }; - - if (resolveIsNixMode(process.env)) { - fail("Nix mode detected; service uninstall is disabled."); - return; - } - - const service = resolveGatewayService(); - let loaded = false; - try { - loaded = await service.isLoaded({ env: process.env }); - } catch { - loaded = false; - } - if (loaded) { - try { - await service.stop({ env: process.env, stdout }); - } catch { - // Best-effort stop; final loaded check gates success. - } - } - try { - await service.uninstall({ env: process.env, stdout }); - } catch (err) { - fail(`Gateway uninstall failed: ${String(err)}`); - return; - } - - loaded = false; - try { - loaded = await service.isLoaded({ env: process.env }); - } catch { - loaded = false; - } - if (loaded) { - fail("Gateway service still loaded after uninstall."); - return; - } - emit({ - ok: true, - result: "uninstalled", - service: buildDaemonServiceSnapshot(service, loaded), + return await runServiceUninstall({ + serviceNoun: "Gateway", + service: resolveGatewayService(), + opts, + stopBeforeUninstall: true, + assertNotLoadedAfterUninstall: true, }); } export async function runDaemonStart(opts: DaemonLifecycleOptions = {}) { - const json = Boolean(opts.json); - const stdout = json ? createNullWriter() : process.stdout; - const emit = (payload: { - ok: boolean; - result?: string; - message?: string; - error?: string; - hints?: string[]; - service?: { - label: string; - loaded: boolean; - loadedText: string; - notLoadedText: string; - }; - }) => { - if (!json) { - return; - } - emitDaemonActionJson({ action: "start", ...payload }); - }; - const fail = (message: string, hints?: string[]) => { - if (json) { - emit({ ok: false, error: message, hints }); - } else { - defaultRuntime.error(message); - } - defaultRuntime.exit(1); - }; - - const service = resolveGatewayService(); - let loaded = false; - try { - loaded = await service.isLoaded({ env: process.env }); - } catch (err) { - fail(`Gateway service check failed: ${String(err)}`); - return; - } - if (!loaded) { - let hints = renderGatewayServiceStartHints(); - if (process.platform === "linux") { - const systemdAvailable = await isSystemdUserServiceAvailable().catch(() => false); - if (!systemdAvailable) { - hints = [...hints, ...renderSystemdUnavailableHints({ wsl: await isWSL() })]; - } - } - emit({ - ok: true, - result: "not-loaded", - message: `Gateway service ${service.notLoadedText}.`, - hints, - service: buildDaemonServiceSnapshot(service, loaded), - }); - if (!json) { - defaultRuntime.log(`Gateway service ${service.notLoadedText}.`); - for (const hint of hints) { - defaultRuntime.log(`Start with: ${hint}`); - } - } - return; - } - try { - await service.restart({ env: process.env, stdout }); - } catch (err) { - const hints = renderGatewayServiceStartHints(); - fail(`Gateway start failed: ${String(err)}`, hints); - return; - } - - let started = true; - try { - started = await service.isLoaded({ env: process.env }); - } catch { - started = true; - } - emit({ - ok: true, - result: "started", - service: buildDaemonServiceSnapshot(service, started), + return await runServiceStart({ + serviceNoun: "Gateway", + service: resolveGatewayService(), + renderStartHints: renderGatewayServiceStartHints, + opts, }); } export async function runDaemonStop(opts: DaemonLifecycleOptions = {}) { - const json = Boolean(opts.json); - const stdout = json ? createNullWriter() : process.stdout; - const emit = (payload: { - ok: boolean; - result?: string; - message?: string; - error?: string; - service?: { - label: string; - loaded: boolean; - loadedText: string; - notLoadedText: string; - }; - }) => { - if (!json) { - return; - } - emitDaemonActionJson({ action: "stop", ...payload }); - }; - const fail = (message: string) => { - if (json) { - emit({ ok: false, error: message }); - } else { - defaultRuntime.error(message); - } - defaultRuntime.exit(1); - }; - - const service = resolveGatewayService(); - let loaded = false; - try { - loaded = await service.isLoaded({ env: process.env }); - } catch (err) { - fail(`Gateway service check failed: ${String(err)}`); - return; - } - if (!loaded) { - emit({ - ok: true, - result: "not-loaded", - message: `Gateway service ${service.notLoadedText}.`, - service: buildDaemonServiceSnapshot(service, loaded), - }); - if (!json) { - defaultRuntime.log(`Gateway service ${service.notLoadedText}.`); - } - return; - } - try { - await service.stop({ env: process.env, stdout }); - } catch (err) { - fail(`Gateway stop failed: ${String(err)}`); - return; - } - - let stopped = false; - try { - stopped = await service.isLoaded({ env: process.env }); - } catch { - stopped = false; - } - emit({ - ok: true, - result: "stopped", - service: buildDaemonServiceSnapshot(service, stopped), + return await runServiceStop({ + serviceNoun: "Gateway", + service: resolveGatewayService(), + opts, }); } @@ -237,83 +41,10 @@ export async function runDaemonStop(opts: DaemonLifecycleOptions = {}) { * Throws/exits on check or restart failures. */ export async function runDaemonRestart(opts: DaemonLifecycleOptions = {}): Promise { - const json = Boolean(opts.json); - const stdout = json ? createNullWriter() : process.stdout; - const emit = (payload: { - ok: boolean; - result?: string; - message?: string; - error?: string; - hints?: string[]; - service?: { - label: string; - loaded: boolean; - loadedText: string; - notLoadedText: string; - }; - }) => { - if (!json) { - return; - } - emitDaemonActionJson({ action: "restart", ...payload }); - }; - const fail = (message: string, hints?: string[]) => { - if (json) { - emit({ ok: false, error: message, hints }); - } else { - defaultRuntime.error(message); - } - defaultRuntime.exit(1); - }; - - const service = resolveGatewayService(); - let loaded = false; - try { - loaded = await service.isLoaded({ env: process.env }); - } catch (err) { - fail(`Gateway service check failed: ${String(err)}`); - return false; - } - if (!loaded) { - let hints = renderGatewayServiceStartHints(); - if (process.platform === "linux") { - const systemdAvailable = await isSystemdUserServiceAvailable().catch(() => false); - if (!systemdAvailable) { - hints = [...hints, ...renderSystemdUnavailableHints({ wsl: await isWSL() })]; - } - } - emit({ - ok: true, - result: "not-loaded", - message: `Gateway service ${service.notLoadedText}.`, - hints, - service: buildDaemonServiceSnapshot(service, loaded), - }); - if (!json) { - defaultRuntime.log(`Gateway service ${service.notLoadedText}.`); - for (const hint of hints) { - defaultRuntime.log(`Start with: ${hint}`); - } - } - return false; - } - try { - await service.restart({ env: process.env, stdout }); - let restarted = true; - try { - restarted = await service.isLoaded({ env: process.env }); - } catch { - restarted = true; - } - emit({ - ok: true, - result: "restarted", - service: buildDaemonServiceSnapshot(service, restarted), - }); - return true; - } catch (err) { - const hints = renderGatewayServiceStartHints(); - fail(`Gateway restart failed: ${String(err)}`, hints); - return false; - } + return await runServiceRestart({ + serviceNoun: "Gateway", + service: resolveGatewayService(), + renderStartHints: renderGatewayServiceStartHints, + opts, + }); } diff --git a/src/cli/node-cli/daemon.ts b/src/cli/node-cli/daemon.ts index 271ea318f..c82a317e8 100644 --- a/src/cli/node-cli/daemon.ts +++ b/src/cli/node-cli/daemon.ts @@ -12,13 +12,16 @@ import { } from "../../daemon/constants.js"; import { resolveGatewayLogPaths } from "../../daemon/launchd.js"; import { resolveNodeService } from "../../daemon/node-service.js"; -import { renderSystemdUnavailableHints } from "../../daemon/systemd-hints.js"; -import { isSystemdUserServiceAvailable } from "../../daemon/systemd.js"; -import { isWSL } from "../../infra/wsl.js"; import { loadNodeHostConfig } from "../../node-host/config.js"; import { defaultRuntime } from "../../runtime.js"; import { colorize, isRich, theme } from "../../terminal/theme.js"; import { formatCliCommand } from "../command-format.js"; +import { + runServiceRestart, + runServiceStart, + runServiceStop, + runServiceUninstall, +} from "../daemon-cli/lifecycle-core.js"; import { buildDaemonServiceSnapshot, createNullWriter, @@ -228,290 +231,38 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) { } export async function runNodeDaemonUninstall(opts: NodeDaemonLifecycleOptions = {}) { - const json = Boolean(opts.json); - const stdout = json ? createNullWriter() : process.stdout; - const emit = (payload: { - ok: boolean; - result?: string; - message?: string; - error?: string; - service?: { - label: string; - loaded: boolean; - loadedText: string; - notLoadedText: string; - }; - }) => { - if (!json) { - return; - } - emitDaemonActionJson({ action: "uninstall", ...payload }); - }; - const fail = (message: string) => { - if (json) { - emit({ ok: false, error: message }); - } else { - defaultRuntime.error(message); - } - defaultRuntime.exit(1); - }; - - if (resolveIsNixMode(process.env)) { - fail("Nix mode detected; service uninstall is disabled."); - return; - } - - const service = resolveNodeService(); - try { - await service.uninstall({ env: process.env, stdout }); - } catch (err) { - fail(`Node uninstall failed: ${String(err)}`); - return; - } - - let loaded = false; - try { - loaded = await service.isLoaded({ env: process.env }); - } catch { - loaded = false; - } - emit({ - ok: true, - result: "uninstalled", - service: buildDaemonServiceSnapshot(service, loaded), + return await runServiceUninstall({ + serviceNoun: "Node", + service: resolveNodeService(), + opts, + stopBeforeUninstall: false, + assertNotLoadedAfterUninstall: false, }); } export async function runNodeDaemonStart(opts: NodeDaemonLifecycleOptions = {}) { - const json = Boolean(opts.json); - const stdout = json ? createNullWriter() : process.stdout; - const emit = (payload: { - ok: boolean; - result?: string; - message?: string; - error?: string; - hints?: string[]; - service?: { - label: string; - loaded: boolean; - loadedText: string; - notLoadedText: string; - }; - }) => { - if (!json) { - return; - } - emitDaemonActionJson({ action: "start", ...payload }); - }; - const fail = (message: string, hints?: string[]) => { - if (json) { - emit({ ok: false, error: message, hints }); - } else { - defaultRuntime.error(message); - } - defaultRuntime.exit(1); - }; - - const service = resolveNodeService(); - let loaded = false; - try { - loaded = await service.isLoaded({ env: process.env }); - } catch (err) { - fail(`Node service check failed: ${String(err)}`); - return; - } - if (!loaded) { - let hints = renderNodeServiceStartHints(); - if (process.platform === "linux") { - const systemdAvailable = await isSystemdUserServiceAvailable().catch(() => false); - if (!systemdAvailable) { - hints = [...hints, ...renderSystemdUnavailableHints({ wsl: await isWSL() })]; - } - } - emit({ - ok: true, - result: "not-loaded", - message: `Node service ${service.notLoadedText}.`, - hints, - service: buildDaemonServiceSnapshot(service, loaded), - }); - if (!json) { - defaultRuntime.log(`Node service ${service.notLoadedText}.`); - for (const hint of hints) { - defaultRuntime.log(`Start with: ${hint}`); - } - } - return; - } - try { - await service.restart({ env: process.env, stdout }); - } catch (err) { - const hints = renderNodeServiceStartHints(); - fail(`Node start failed: ${String(err)}`, hints); - return; - } - - let started = true; - try { - started = await service.isLoaded({ env: process.env }); - } catch { - started = true; - } - emit({ - ok: true, - result: "started", - service: buildDaemonServiceSnapshot(service, started), + return await runServiceStart({ + serviceNoun: "Node", + service: resolveNodeService(), + renderStartHints: renderNodeServiceStartHints, + opts, }); } export async function runNodeDaemonRestart(opts: NodeDaemonLifecycleOptions = {}) { - const json = Boolean(opts.json); - const stdout = json ? createNullWriter() : process.stdout; - const emit = (payload: { - ok: boolean; - result?: string; - message?: string; - error?: string; - hints?: string[]; - service?: { - label: string; - loaded: boolean; - loadedText: string; - notLoadedText: string; - }; - }) => { - if (!json) { - return; - } - emitDaemonActionJson({ action: "restart", ...payload }); - }; - const fail = (message: string, hints?: string[]) => { - if (json) { - emit({ ok: false, error: message, hints }); - } else { - defaultRuntime.error(message); - } - defaultRuntime.exit(1); - }; - - const service = resolveNodeService(); - let loaded = false; - try { - loaded = await service.isLoaded({ env: process.env }); - } catch (err) { - fail(`Node service check failed: ${String(err)}`); - return; - } - if (!loaded) { - let hints = renderNodeServiceStartHints(); - if (process.platform === "linux") { - const systemdAvailable = await isSystemdUserServiceAvailable().catch(() => false); - if (!systemdAvailable) { - hints = [...hints, ...renderSystemdUnavailableHints({ wsl: await isWSL() })]; - } - } - emit({ - ok: true, - result: "not-loaded", - message: `Node service ${service.notLoadedText}.`, - hints, - service: buildDaemonServiceSnapshot(service, loaded), - }); - if (!json) { - defaultRuntime.log(`Node service ${service.notLoadedText}.`); - for (const hint of hints) { - defaultRuntime.log(`Start with: ${hint}`); - } - } - return; - } - try { - await service.restart({ env: process.env, stdout }); - } catch (err) { - const hints = renderNodeServiceStartHints(); - fail(`Node restart failed: ${String(err)}`, hints); - return; - } - - let restarted = true; - try { - restarted = await service.isLoaded({ env: process.env }); - } catch { - restarted = true; - } - emit({ - ok: true, - result: "restarted", - service: buildDaemonServiceSnapshot(service, restarted), + await runServiceRestart({ + serviceNoun: "Node", + service: resolveNodeService(), + renderStartHints: renderNodeServiceStartHints, + opts, }); } export async function runNodeDaemonStop(opts: NodeDaemonLifecycleOptions = {}) { - const json = Boolean(opts.json); - const stdout = json ? createNullWriter() : process.stdout; - const emit = (payload: { - ok: boolean; - result?: string; - message?: string; - error?: string; - service?: { - label: string; - loaded: boolean; - loadedText: string; - notLoadedText: string; - }; - }) => { - if (!json) { - return; - } - emitDaemonActionJson({ action: "stop", ...payload }); - }; - const fail = (message: string) => { - if (json) { - emit({ ok: false, error: message }); - } else { - defaultRuntime.error(message); - } - defaultRuntime.exit(1); - }; - - const service = resolveNodeService(); - let loaded = false; - try { - loaded = await service.isLoaded({ env: process.env }); - } catch (err) { - fail(`Node service check failed: ${String(err)}`); - return; - } - if (!loaded) { - emit({ - ok: true, - result: "not-loaded", - message: `Node service ${service.notLoadedText}.`, - service: buildDaemonServiceSnapshot(service, loaded), - }); - if (!json) { - defaultRuntime.log(`Node service ${service.notLoadedText}.`); - } - return; - } - try { - await service.stop({ env: process.env, stdout }); - } catch (err) { - fail(`Node stop failed: ${String(err)}`); - return; - } - - let stopped = false; - try { - stopped = await service.isLoaded({ env: process.env }); - } catch { - stopped = false; - } - emit({ - ok: true, - result: "stopped", - service: buildDaemonServiceSnapshot(service, stopped), + return await runServiceStop({ + serviceNoun: "Node", + service: resolveNodeService(), + opts, }); }