/* oxlint-disable executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: Electron main process shells out to the bundled CLI and surfaces failures to boot/settings IPC callers */import { execFile } from "node:child_process";import { existsSync } from "node:fs";import { join } from "node:path";import { promisify } from "node:util";import { app } from "electron";import log from "electron-log/main.js";import { sidecarCrashReportingEnv } from "./diagnostics"; const serviceLog = log.scope("service");const execFileAsync = promisify(execFile); export const SERVICE_LABEL = "sh.executor.daemon"; interface CommandResult { readonly code: number; readonly stdout: string; readonly stderr: string;} export interface SupervisedServiceStatus { readonly supported: boolean; readonly registered: boolean; readonly running: boolean;} export interface InstallOptions { readonly port: number; readonly dataDir: string;} export const bundledExecutorPath = (): string => join( process.resourcesPath, "executor", process.platform === "win32" ? "executor.exe" : "executor", ); const executorAvailable = (): boolean => app.isPackaged && existsSync(bundledExecutorPath()); let userPathCapture: Promise<string | undefined> | null = null; const captureUserPath = async (): Promise<string | undefined> => { const shell = process.env.SHELL; if (!shell) return process.env.PATH; try { const { stdout } = await execFileAsync(shell, ["-ilc", 'printf "%s" "$PATH"'], { encoding: "utf8", timeout: 5000, }); const path = stdout.trim(); return path.length > 0 ? path : process.env.PATH; } catch { return process.env.PATH; }}; const memoizedUserPath = (): Promise<string | undefined> => { userPathCapture ??= captureUserPath(); return userPathCapture;}; const serviceEnv = async ( dataDir: string, options: { readonly captureUserPath: boolean },): Promise<NodeJS.ProcessEnv> => { const path = options.captureUserPath ? await memoizedUserPath() : process.env.PATH; return { ...process.env, ...(path ? { PATH: path } : {}), EXECUTOR_DATA_DIR: dataDir, EXECUTOR_SCOPE_DIR: dataDir, EXECUTOR_CLIENT: "desktop", ...sidecarCrashReportingEnv(), };}; const runExecutor = async ( args: ReadonlyArray<string>, options: { readonly dataDir: string; readonly captureUserPath?: boolean },): Promise<CommandResult> => { const bin = bundledExecutorPath(); try { const { stdout, stderr } = await execFileAsync(bin, [...args], { encoding: "utf8", env: await serviceEnv(options.dataDir, { captureUserPath: options.captureUserPath ?? true, }), }); return { code: 0, stdout, stderr }; } catch (error) { const err = error as { code?: number | string; stdout?: string; stderr?: string }; if (typeof err.code === "string") { throw new Error(`Failed to run \`${bin}\`: ${err.code}`); } return { code: typeof err.code === "number" ? err.code : 1, stdout: err.stdout ?? "", stderr: err.stderr ?? "", }; }}; const statusValue = (stdout: string, key: "Registered" | "Running"): boolean => new RegExp(`^${key}:\\s+yes(?:\\s|$)`, "im").test(stdout); export const supervisedServiceStatus = async (): Promise<SupervisedServiceStatus> => { if (!executorAvailable()) return { supported: false, registered: false, running: false }; const dataDir = join(app.getPath("home"), ".executor"); const result = await runExecutor(["service", "status"], { dataDir, captureUserPath: false, }); if (result.code !== 0) { serviceLog.warn(`service status failed: ${result.stderr || result.stdout}`); return { supported: true, registered: false, running: false }; } const supported = !/^Platform:\s+unsupported$/im.test(result.stdout); return { supported, registered: supported && statusValue(result.stdout, "Registered"), running: supported && statusValue(result.stdout, "Running"), };}; export const installSupervisedService = async (opts: InstallOptions): Promise<void> => { if (!executorAvailable()) { throw new Error("Bundled executor binary is not available."); } const result = await runExecutor(["install", "--port", String(opts.port)], { dataDir: opts.dataDir, }); if (result.code !== 0) { throw new Error((result.stderr || result.stdout).trim() || "`executor install` failed."); } serviceLog.info(`installed supervised service via bundled executor on port ${opts.port}`);}; export const uninstallSupervisedService = async (dataDir: string): Promise<void> => { if (!executorAvailable()) return; const result = await runExecutor(["service", "uninstall"], { dataDir }); if (result.code !== 0) { throw new Error( (result.stderr || result.stdout).trim() || "`executor service uninstall` failed.", ); } serviceLog.info("uninstalled supervised service via bundled executor");}; export const restartSupervisedService = async (): Promise<void> => { if (!executorAvailable()) { throw new Error("Bundled executor binary is not available."); } const dataDir = join(app.getPath("home"), ".executor"); const result = await runExecutor(["service", "restart"], { dataDir }); if (result.code !== 0) { throw new Error( (result.stderr || result.stdout).trim() || "`executor service restart` failed.", ); }};
/* oxlint-disable executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: Electron main process shells out to the bundled CLI and surfaces failures to boot/settings IPC callers */
import { execFile } from "node:child_process";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { promisify } from "node:util";
import { app } from "electron";
import log from "electron-log/main.js";
import { sidecarCrashReportingEnv } from "./diagnostics";
const serviceLog = log.scope("service");
const execFileAsync = promisify(execFile);
export const SERVICE_LABEL = "sh.executor.daemon";
interface CommandResult {
readonly code: number;
readonly stdout: string;
readonly stderr: string;
}
export interface SupervisedServiceStatus {
readonly supported: boolean;
readonly registered: boolean;
readonly running: boolean;
}
export interface InstallOptions {
readonly port: number;
readonly dataDir: string;
}
export const bundledExecutorPath = (): string =>
join(
process.resourcesPath,
"executor",
process.platform === "win32" ? "executor.exe" : "executor",
);
const executorAvailable = (): boolean => app.isPackaged && existsSync(bundledExecutorPath());
let userPathCapture: Promise<string | undefined> | null = null;
const captureUserPath = async (): Promise<string | undefined> => {
const shell = process.env.SHELL;
if (!shell) return process.env.PATH;
try {
const { stdout } = await execFileAsync(shell, ["-ilc", 'printf "%s" "$PATH"'], {
encoding: "utf8",
timeout: 5000,
});
const path = stdout.trim();
return path.length > 0 ? path : process.env.PATH;
} catch {
return process.env.PATH;
}
};
const memoizedUserPath = (): Promise<string | undefined> => {
userPathCapture ??= captureUserPath();
return userPathCapture;
};
const serviceEnv = async (
dataDir: string,
options: { readonly captureUserPath: boolean },
): Promise<NodeJS.ProcessEnv> => {
const path = options.captureUserPath ? await memoizedUserPath() : process.env.PATH;
return {
...process.env,
...(path ? { PATH: path } : {}),
EXECUTOR_DATA_DIR: dataDir,
EXECUTOR_SCOPE_DIR: dataDir,
EXECUTOR_CLIENT: "desktop",
...sidecarCrashReportingEnv(),
};
};
const runExecutor = async (
args: ReadonlyArray<string>,
options: { readonly dataDir: string; readonly captureUserPath?: boolean },
): Promise<CommandResult> => {
const bin = bundledExecutorPath();
try {
const { stdout, stderr } = await execFileAsync(bin, [...args], {
encoding: "utf8",
env: await serviceEnv(options.dataDir, {
captureUserPath: options.captureUserPath ?? true,
}),
});
return { code: 0, stdout, stderr };
} catch (error) {
const err = error as { code?: number | string; stdout?: string; stderr?: string };
if (typeof err.code === "string") {
throw new Error(`Failed to run \`${bin}\`: ${err.code}`);
}
return {
code: typeof err.code === "number" ? err.code : 1,
stdout: err.stdout ?? "",
stderr: err.stderr ?? "",
};
}
};
const statusValue = (stdout: string, key: "Registered" | "Running"): boolean =>
new RegExp(`^${key}:\\s+yes(?:\\s|$)`, "im").test(stdout);
export const supervisedServiceStatus = async (): Promise<SupervisedServiceStatus> => {
if (!executorAvailable()) return { supported: false, registered: false, running: false };
const dataDir = join(app.getPath("home"), ".executor");
const result = await runExecutor(["service", "status"], {
dataDir,
captureUserPath: false,
});
if (result.code !== 0) {
serviceLog.warn(`service status failed: ${result.stderr || result.stdout}`);
return { supported: true, registered: false, running: false };
}
const supported = !/^Platform:\s+unsupported$/im.test(result.stdout);
return {
supported,
registered: supported && statusValue(result.stdout, "Registered"),
running: supported && statusValue(result.stdout, "Running"),
};
};
export const installSupervisedService = async (opts: InstallOptions): Promise<void> => {
if (!executorAvailable()) {
throw new Error("Bundled executor binary is not available.");
}
const result = await runExecutor(["install", "--port", String(opts.port)], {
dataDir: opts.dataDir,
});
if (result.code !== 0) {
throw new Error((result.stderr || result.stdout).trim() || "`executor install` failed.");
}
serviceLog.info(`installed supervised service via bundled executor on port ${opts.port}`);
};
export const uninstallSupervisedService = async (dataDir: string): Promise<void> => {
if (!executorAvailable()) return;
const result = await runExecutor(["service", "uninstall"], { dataDir });
if (result.code !== 0) {
throw new Error(
(result.stderr || result.stdout).trim() || "`executor service uninstall` failed.",
);
}
serviceLog.info("uninstalled supervised service via bundled executor");
};
export const restartSupervisedService = async (): Promise<void> => {
if (!executorAvailable()) {
throw new Error("Bundled executor binary is not available.");
}
const dataDir = join(app.getPath("home"), ".executor");
const result = await runExecutor(["service", "restart"], { dataDir });
if (result.code !== 0) {
throw new Error(
(result.stderr || result.stdout).trim() || "`executor service restart` failed.",
);
}
};