import { spawn, spawnSync } from "node:child_process"; import { existsSync, mkdtempSync, readFileSync, statSync } from "node:fs"; import { rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; const requestedExecutablePath = process.argv[2]?.trim(); const executablePath = requestedExecutablePath ? resolve(requestedExecutablePath) : ""; if (!executablePath || !existsSync(executablePath) || !statSync(executablePath).isFile()) { throw new Error(`Packaged desktop executable is missing: ${executablePath || ""}`); } const userDataDirectory = mkdtempSync(join(tmpdir(), "edgeever-packaged-startup-")); const diagnosticLogPath = join(userDataDirectory, "logs", "desktop.log"); const output = []; let processError = null; const child = spawn(executablePath, [`--user-data-dir=${userDataDirectory}`], { env: { ...process.env, EDGE_EVER_DISABLE_AUTO_UPDATE: "1", }, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, }); const childClosed = new Promise((resolveClosed) => child.once("close", resolveClosed)); child.on("error", (error) => { processError = error; }); child.stdout.on("data", (chunk) => output.push(chunk.toString())); child.stderr.on("data", (chunk) => output.push(chunk.toString())); const timeoutMs = Number(process.env.EDGE_EVER_DESKTOP_STARTUP_TIMEOUT_MS) || 45_000; const deadline = Date.now() + timeoutMs; let startupReady = false; let lastLog = ""; const requiredEvents = new Set(["renderer.origin-ready", "sidecar.ready", "renderer.bootstrap-ready"]); const observedEvents = new Set(); const observedEntries = []; const wait = (milliseconds) => new Promise((resolveWait) => setTimeout(resolveWait, milliseconds)); const removeUserDataDirectory = async () => { let lastError = null; for (let attempt = 1; attempt <= 20; attempt += 1) { try { await rm(userDataDirectory, { recursive: true, force: true }); return; } catch (error) { lastError = error; if (!["EBUSY", "ENOTEMPTY", "EPERM"].includes(error?.code)) break; if (attempt < 20) await wait(250); } } console.warn(`Temporary profile cleanup failed without changing the packaged startup result: ${lastError?.message || lastError}`); }; try { while (Date.now() < deadline) { if (existsSync(diagnosticLogPath)) { lastLog = readFileSync(diagnosticLogPath, "utf8"); for (const line of lastLog.split(/\r?\n/)) { try { const entry = JSON.parse(line); const event = entry.event; if (typeof event === "string") { observedEvents.add(event); observedEntries.push(entry); } } catch { /* Ignore a partial trailing log line. */ } } if ([...requiredEvents].every((event) => observedEvents.has(event))) { startupReady = true; break; } } if (processError || child.exitCode !== null) break; await wait(100); } if (!startupReady) { throw new Error([ `Packaged desktop did not report renderer readiness within ${timeoutMs}ms (exit=${child.exitCode ?? "running"}).`, `Missing events: ${[...requiredEvents].filter((event) => !observedEvents.has(event)).join(", ") || "none"}`, processError && `Process error: ${processError.message}`, lastLog && `Diagnostic log:\n${lastLog}`, output.length > 0 && `Process output:\n${output.join("")}`, ].filter(Boolean).join("\n\n")); } const originEntry = observedEntries.find((entry) => entry.event === "renderer.origin-ready"); if (!originEntry || !["migrated", "already-migrated"].includes(originEntry.state)) { throw new Error(`Packaged desktop did not complete renderer origin migration: ${JSON.stringify(originEntry)}`); } const rendererEntry = observedEntries.find((entry) => entry.event === "renderer.loaded" && String(entry.url || "").startsWith("edgeever-app://app/")); if (!rendererEntry) { throw new Error(`Packaged desktop did not load from the private app protocol.\n\nDiagnostic log:\n${lastLog}`); } console.log(JSON.stringify({ ok: true, executablePath, events: [...requiredEvents], rendererUrl: rendererEntry.url })); } finally { if (child.exitCode === null && child.pid) { if (process.platform === "win32") { spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true }); } else { child.kill("SIGTERM"); } await Promise.race([childClosed, wait(5_000)]); } await removeUserDataDirectory(); }