Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
"@types/node": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:",
"wait-on": "^8.0.2"
"vitest": "catalog:"
},
"productName": "T3 Code (Alpha)"
}
8 changes: 5 additions & 3 deletions apps/desktop/scripts/dev-electron.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { spawn, spawnSync } from "node:child_process";
import { watch } from "node:fs";
import { join } from "node:path";
import waitOn from "wait-on";

import { desktopDir, resolveElectronPath } from "./electron-launcher.mjs";
import { waitForResources } from "./wait-for-resources.mjs";

const port = Number(process.env.ELECTRON_RENDERER_PORT ?? 5733);
const devServerUrl = `http://localhost:${port}`;
Expand All @@ -20,8 +20,10 @@ const forcedShutdownTimeoutMs = 1_500;
const restartDebounceMs = 120;
const childTreeGracePeriodMs = 1_200;

await waitOn({
resources: [`tcp:${port}`, ...requiredFiles.map((filePath) => `file:${filePath}`)],
await waitForResources({
baseDir: desktopDir,
files: requiredFiles,
tcpPort: port,
});

const childEnv = { ...process.env };
Expand Down
119 changes: 119 additions & 0 deletions apps/desktop/scripts/wait-for-resources.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import * as FileSystem from "node:fs/promises";
import * as Net from "node:net";
import * as Path from "node:path";
import * as Timers from "node:timers/promises";

const defaultTcpHosts = ["127.0.0.1", "localhost", "::1"];

async function fileExists(filePath) {
try {
await FileSystem.access(filePath);
return true;
} catch {
return false;
}
}

function tcpPortIsReady({ host, port, connectTimeoutMs = 500 }) {
return new Promise((resolveReady) => {
const socket = Net.createConnection({ host, port });
let settled = false;

const finish = (ready) => {
if (settled) {
return;
}

settled = true;
socket.removeAllListeners();
socket.destroy();
resolveReady(ready);
};

socket.once("connect", () => {
finish(true);
});
socket.once("timeout", () => {
finish(false);
});
socket.once("error", () => {
finish(false);
});
socket.setTimeout(connectTimeoutMs);
});
}

async function resolvePendingResources({ baseDir, files, tcpPort, tcpHosts, connectTimeoutMs }) {
const pendingFiles = [];

for (const relativeFilePath of files) {
const ready = await fileExists(Path.resolve(baseDir, relativeFilePath));
if (!ready) {
pendingFiles.push(relativeFilePath);
}
}

let tcpReady = false;
for (const host of tcpHosts) {
tcpReady = await tcpPortIsReady({
host,
port: tcpPort,
connectTimeoutMs,
});
if (tcpReady) {
break;
}
}

return {
pendingFiles,
tcpReady,
};
}

export async function waitForResources({
baseDir,
files = [],
intervalMs = 100,
timeoutMs = 120_000,
tcpHost,
tcpPort,
connectTimeoutMs = 500,
}) {
if (!Number.isInteger(tcpPort) || tcpPort <= 0) {
throw new TypeError("waitForResources requires a positive integer tcpPort");
}

const startedAt = Date.now();
const tcpHosts = tcpHost ? [tcpHost] : defaultTcpHosts;

while (true) {
const { pendingFiles, tcpReady } = await resolvePendingResources({
baseDir,
files,
tcpPort,
tcpHosts,
connectTimeoutMs,
});

if (pendingFiles.length === 0 && tcpReady) {
return;
}

if (Date.now() - startedAt >= timeoutMs) {
const pendingResources = [];
if (!tcpReady) {
pendingResources.push(tcpHost ? `tcp:${tcpHost}:${tcpPort}` : `tcp:${tcpPort}`);
}
for (const filePath of pendingFiles) {
pendingResources.push(`file:${filePath}`);
}

throw new Error(
`Timed out waiting for desktop dev resources after ${timeoutMs}ms: ${pendingResources.join(", ")}`,
);
}

await Timers.setTimeout(intervalMs);
}
}
Loading
Loading