| @@ -1,19 +1,84 @@ |
| 1 | 1 | import { spawn } from "node:child_process"; |
| 2 | +import net from "node:net"; |
| 2 | 3 | |
| 3 | 4 | import waitOn from "wait-on"; |
| 4 | 5 | |
| 5 | 6 | const port = Number(process.env.ELECTRON_RENDERER_PORT ?? 5173); |
| 6 | | −const devServerUrl = `http://localhost:${port}`; |
| 7 | +const STARTUP_TIMEOUT_MS = Number(process.env.T3CODE_ELECTRON_STARTUP_TIMEOUT_MS ?? 120_000); |
| 7 | 8 | |
| 9 | +async function canConnect(host, probePort, timeoutMs = 1_000) { |
| 10 | + return new Promise((resolve) => { |
| 11 | + const socket = new net.Socket(); |
| 12 | + let settled = false; |
| 13 | + |
| 14 | + const finish = (value) => { |
| 15 | + if (settled) return; |
| 16 | + settled = true; |
| 17 | + socket.destroy(); |
| 18 | + resolve(value); |
| 19 | + }; |
| 20 | + |
| 21 | + socket.setTimeout(timeoutMs); |
| 22 | + socket.once("connect", () => finish(true)); |
| 23 | + socket.once("timeout", () => finish(false)); |
| 24 | + socket.once("error", () => finish(false)); |
| 25 | + socket.connect(probePort, host); |
| 26 | + }); |
| 27 | +} |
| 28 | + |
| 29 | +function waitForDevServer(probePort, timeoutMs) { |
| 30 | + const startedAt = Date.now(); |
| 31 | + const candidates = [ |
| 32 | + { host: "127.0.0.1", url: `http://127.0.0.1:${probePort}` }, |
| 33 | + { host: "::1", url: `http://[::1]:${probePort}` }, |
| 34 | + { host: "localhost", url: `http://localhost:${probePort}` }, |
| 35 | + ]; |
| 36 | + |
| 37 | + return new Promise((resolve, reject) => { |
| 38 | + const tick = () => { |
| 39 | + void Promise.all( |
| 40 | + candidates.map(async (candidate) => ({ |
| 41 | + candidate, |
| 42 | + connected: await canConnect(candidate.host, probePort), |
| 43 | + })), |
| 44 | + ) |
| 45 | + .then((results) => { |
| 46 | + const ready = results.find((entry) => entry.connected); |
| 47 | + if (ready) { |
| 48 | + resolve(ready.candidate.url); |
| 49 | + return; |
| 50 | + } |
| 51 | + |
| 52 | + if (Date.now() - startedAt >= timeoutMs) { |
| 53 | + reject( |
| 54 | + new Error( |
| 55 | + `[dev-electron] timed out after ${timeoutMs}ms waiting for renderer dev server on port ${probePort}`, |
| 56 | + ), |
| 57 | + ); |
| 58 | + return; |
| 59 | + } |
| 60 | + |
| 61 | + setTimeout(tick, 250); |
| 62 | + }) |
| 63 | + .catch((error) => { |
| 64 | + reject(error); |
| 65 | + }); |
| 66 | + }; |
| 67 | + |
| 68 | + tick(); |
| 69 | + }); |
| 70 | +} |
| 71 | + |
| 72 | +console.log("[dev-electron] waiting for desktop/server bundles"); |
| 8 | 73 | await waitOn({ |
| 9 | | − resources: [ |
| 10 | | − `tcp:${port}`, |
| 11 | | − "file:dist-electron/main.js", |
| 12 | | − "file:dist-electron/preload.js", |
| 13 | | − "file:../server/dist/index.mjs", |
| 14 | | − ], |
| 74 | + resources: ["file:dist-electron/main.js", "file:dist-electron/preload.js", "file:../server/dist/index.mjs"], |
| 75 | + timeout: STARTUP_TIMEOUT_MS, |
| 15 | 76 | }); |
| 16 | 77 | |
| 78 | +console.log(`[dev-electron] waiting for renderer dev server on port ${port}`); |
| 79 | +const devServerUrl = await waitForDevServer(port, STARTUP_TIMEOUT_MS); |
| 80 | +console.log(`[dev-electron] launching electron with renderer url ${devServerUrl}`); |
| 81 | + |
| 17 | 82 | const command = process.platform === "win32" ? "electronmon.cmd" : "electronmon"; |
| 18 | 83 | const childEnv = { ...process.env }; |
| 19 | 84 | delete childEnv.ELECTRON_RUN_AS_NODE; |
| @@ -29,3 +94,8 @@ const child = spawn(command, ["dist-electron/main.js"], { · 9 unmodified lines |
| 29 | 94 | child.on("exit", (code) => { |
| 30 | 95 | process.exit(code ?? 0); |
| 31 | 96 | }); |
| 97 | + |
| 98 | +child.on("error", (error) => { |
| 99 | + console.error("[dev-electron] failed to launch electronmon:", error.message); |
| 100 | + process.exit(1); |
| 101 | +}); |