196 lines · 5.2 KB · JavaScript
RawDownload
import { spawn } from "node:child_process";import fs from "node:fs";import net from "node:net";import path from "node:path"; const port = Number(process.env.ELECTRON_RENDERER_PORT ?? 5173);const STARTUP_TIMEOUT_MS = Number(process.env.T3CODE_ELECTRON_STARTUP_TIMEOUT_MS ?? 120_000); async function canConnect(host, probePort, timeoutMs = 1_000) {  return new Promise((resolve) => {    const socket = new net.Socket();    let settled = false;     const finish = (value) => {      if (settled) return;      settled = true;      socket.destroy();      resolve(value);    };     socket.setTimeout(timeoutMs);    socket.once("connect", () => finish(true));    socket.once("timeout", () => finish(false));    socket.once("error", () => finish(false));    socket.connect(probePort, host);  });} const f = (p) => path.join(import.meta.dirname, "..", p); function inspectBundleFile(bundleFile) {  let stat = null;  try {    stat = fs.statSync(bundleFile.path);  } catch {    return {      ...bundleFile,      status: "missing",      mtimeMs: null,      reason: "not found",    };  }   return {    ...bundleFile,    status: "ready",    mtimeMs: stat.mtimeMs,    reason: "ok",  };} function describeBundleStates(states) {  return states    .map((state) => {      const mtime =        typeof state.mtimeMs === "number" ? new Date(state.mtimeMs).toISOString() : "n/a";      return `${state.label}=${state.status} (mtime=${mtime}; reason=${state.reason})`;    })    .join("; ");} function waitForDesktopBundles(timeoutMs) {  const startedAt = Date.now();  const bundleFiles = [    {      path: f("dist-electron/main.mjs"),      label: "desktop/main.mjs",    },    {      path: f("dist-electron/preload.cjs"),      label: "desktop/preload.cjs",    },    {      path: f("../server/dist/index.mjs"),      label: "server/index.mjs",    },  ];  let lastProgressLogAt = 0;   return new Promise((resolve, reject) => {    const tick = () => {      const states = bundleFiles.map(inspectBundleFile);      const missing = states.filter((state) => state.status === "missing");       if (missing.length === 0) {        resolve();        return;      }       const elapsedMs = Date.now() - startedAt;      if (elapsedMs >= timeoutMs) {        const parts = [];        if (missing.length > 0) {          parts.push(`missing: ${missing.map((state) => state.label).join(", ")}`);        }        reject(          new Error(            `[dev-electron] timed out after ${timeoutMs}ms waiting for bundles (${parts.join("; ")})\n[dev-electron] bundle state: ${describeBundleStates(states)}`,          ),        );        return;      }       if (Date.now() - lastProgressLogAt >= 5_000) {        lastProgressLogAt = Date.now();        const waitParts = [];        if (missing.length > 0) {          waitParts.push(`missing=${missing.map((state) => state.label).join(",")}`);        }        console.log(          `[dev-electron] still waiting for bundles after ${elapsedMs}ms (${waitParts.join("; ")})`,        );      }       setTimeout(tick, 250);    };     tick();  });} function waitForDevServer(probePort, timeoutMs) {  const startedAt = Date.now();  const candidates = [    { host: "127.0.0.1", url: `http://127.0.0.1:${probePort}` },    { host: "::1", url: `http://[::1]:${probePort}` },    { host: "localhost", url: `http://localhost:${probePort}` },  ];   return new Promise((resolve, reject) => {    const tick = () => {      void Promise.all(        candidates.map(async (candidate) => ({          candidate,          connected: await canConnect(candidate.host, probePort),        })),      )        .then((results) => {          const ready = results.find((entry) => entry.connected);          if (ready) {            resolve(ready.candidate.url);            return;          }           if (Date.now() - startedAt >= timeoutMs) {            reject(              new Error(                `[dev-electron] timed out after ${timeoutMs}ms waiting for renderer dev server on port ${probePort}`,              ),            );            return;          }           setTimeout(tick, 250);        })        .catch((error) => {          reject(error);        });    };     tick();  });} console.log("[dev-electron] waiting for desktop/server bundles");await waitForDesktopBundles(STARTUP_TIMEOUT_MS); console.log(`[dev-electron] waiting for renderer dev server on port ${port}`);const devServerUrl = await waitForDevServer(port, STARTUP_TIMEOUT_MS);console.log(`[dev-electron] launching electron with renderer url ${devServerUrl}`); const command = process.platform === "win32" ? "electronmon.cmd" : "electronmon";const childEnv = { ...process.env };delete childEnv.ELECTRON_RUN_AS_NODE; if (process.platform === "linux" && !childEnv.ELECTRON_DISABLE_SANDBOX) {  childEnv.ELECTRON_DISABLE_SANDBOX = "1";  console.log("[dev-electron] enabling ELECTRON_DISABLE_SANDBOX=1 on Linux");} const child = spawn(command, ["dist-electron/main.mjs"], {  stdio: "inherit",  env: {    ...childEnv,    VITE_DEV_SERVER_URL: devServerUrl,  },}); child.on("exit", (code) => {  process.exit(code ?? 0);}); child.on("error", (error) => {  console.error("[dev-electron] failed to launch electronmon:", error.message);  process.exit(1);});