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);});
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);
});