mirror of
https://github.com/VibedByKaKi/t3-code-android-nightly.git
synced 2026-10-09 20:01:15 +02:00
Co-authored-by: Dara Adedeji <daraadedeji07@gmail.com> Co-authored-by: Dara Adedeji <76637177+SunkenInTime@users.noreply.github.com> Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
269 lines
6.3 KiB
JavaScript
269 lines
6.3 KiB
JavaScript
import * as NodeChildProcess from "node:child_process";
|
|
import * as NodeFS from "node:fs";
|
|
import * as NodeOS from "node:os";
|
|
import * as NodePath from "node:path";
|
|
|
|
import {
|
|
desktopDir,
|
|
resolveDevProtocolClient,
|
|
resolveElectronLaunchCommand,
|
|
} from "./electron-launcher.mjs";
|
|
import { waitForResources } from "./wait-for-resources.mjs";
|
|
|
|
const devServerUrl = process.env.VITE_DEV_SERVER_URL?.trim();
|
|
if (!devServerUrl) {
|
|
throw new Error("VITE_DEV_SERVER_URL is required for desktop development.");
|
|
}
|
|
|
|
const devServer = new URL(devServerUrl);
|
|
const port = Number.parseInt(devServer.port, 10);
|
|
if (!Number.isInteger(port) || port <= 0) {
|
|
throw new Error(`VITE_DEV_SERVER_URL must include an explicit port: ${devServerUrl}`);
|
|
}
|
|
|
|
const requiredFiles = [
|
|
"dist-electron/main.cjs",
|
|
"dist-electron/electron/WindowsForegroundFocusWorker.cjs",
|
|
"dist-electron/preload.cjs",
|
|
"dist-electron/snapShot/GlobalShiftShortcutWorker.cjs",
|
|
"dist-electron/snapShot/RegionSnapShotWorker.cjs",
|
|
"dist-electron/snapShot/SnapShotAccessibilityWorker.cjs",
|
|
"../server/dist/bin.mjs",
|
|
];
|
|
const watchedDirectories = [
|
|
{ directory: "dist-electron", files: new Set(["main.cjs", "preload.cjs"]) },
|
|
{
|
|
directory: "dist-electron/electron",
|
|
files: new Set(["WindowsForegroundFocusWorker.cjs"]),
|
|
},
|
|
{
|
|
directory: "dist-electron/snapShot",
|
|
files: new Set([
|
|
"GlobalShiftShortcutWorker.cjs",
|
|
"RegionSnapShotWorker.cjs",
|
|
"SnapShotAccessibilityWorker.cjs",
|
|
]),
|
|
},
|
|
{ directory: "../server/dist", files: new Set(["bin.mjs"]) },
|
|
];
|
|
const forcedShutdownTimeoutMs = 1_500;
|
|
const restartDebounceMs = 120;
|
|
const childTreeGracePeriodMs = 1_200;
|
|
const remoteDebuggingPort = process.env.T3CODE_DESKTOP_REMOTE_DEBUGGING_PORT?.trim();
|
|
// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone dev script has no Effect runtime.
|
|
const hostPlatform = NodeOS.platform();
|
|
|
|
NodeChildProcess.execFileSync(
|
|
process.execPath,
|
|
[NodePath.join(desktopDir, "scripts/build-browser-secret.mjs")],
|
|
{ stdio: "inherit" },
|
|
);
|
|
|
|
await waitForResources({
|
|
baseDir: desktopDir,
|
|
files: requiredFiles,
|
|
tcpHost: devServer.hostname,
|
|
tcpPort: port,
|
|
});
|
|
|
|
const childEnv = { ...process.env };
|
|
delete childEnv.ELECTRON_RUN_AS_NODE;
|
|
const devProtocolClient = resolveDevProtocolClient();
|
|
if (devProtocolClient) {
|
|
childEnv.T3CODE_DESKTOP_APP_USER_MODEL_ID = devProtocolClient.appBundleId;
|
|
childEnv.T3CODE_DESKTOP_PROTOCOL_REGISTRATION_MANAGED = "1";
|
|
}
|
|
|
|
let shuttingDown = false;
|
|
let restartTimer = null;
|
|
let currentApp = null;
|
|
let restartQueue = Promise.resolve();
|
|
const expectedExits = new WeakSet();
|
|
const watchers = [];
|
|
|
|
function killChildTreeByPid(pid, signal) {
|
|
if (hostPlatform === "win32" || typeof pid !== "number") {
|
|
return;
|
|
}
|
|
|
|
NodeChildProcess.spawnSync("pkill", [`-${signal}`, "-P", String(pid)], { stdio: "ignore" });
|
|
}
|
|
|
|
function cleanupStaleDevApps() {
|
|
if (hostPlatform === "win32") {
|
|
return;
|
|
}
|
|
|
|
NodeChildProcess.spawnSync("pkill", ["-f", "--", `--t3code-dev-root=${desktopDir}`], {
|
|
stdio: "ignore",
|
|
});
|
|
}
|
|
|
|
function startApp() {
|
|
if (shuttingDown || currentApp !== null) {
|
|
return;
|
|
}
|
|
|
|
const electronArgs = remoteDebuggingPort
|
|
? [`--remote-debugging-port=${remoteDebuggingPort}`]
|
|
: [];
|
|
const launchArgs = devProtocolClient
|
|
? electronArgs
|
|
: [...electronArgs, `--t3code-dev-root=${desktopDir}`, "dist-electron/main.cjs"];
|
|
const electronCommand = resolveElectronLaunchCommand(launchArgs);
|
|
const app = NodeChildProcess.spawn(electronCommand.electronPath, electronCommand.args, {
|
|
cwd: desktopDir,
|
|
env: childEnv,
|
|
stdio: "inherit",
|
|
});
|
|
|
|
currentApp = app;
|
|
|
|
app.once("error", () => {
|
|
if (currentApp === app) {
|
|
currentApp = null;
|
|
}
|
|
|
|
if (!shuttingDown) {
|
|
scheduleRestart();
|
|
}
|
|
});
|
|
|
|
app.once("exit", (code, signal) => {
|
|
if (currentApp === app) {
|
|
currentApp = null;
|
|
}
|
|
|
|
const exitedAbnormally = signal !== null || code !== 0;
|
|
if (!shuttingDown && !expectedExits.has(app) && exitedAbnormally) {
|
|
scheduleRestart();
|
|
}
|
|
});
|
|
}
|
|
|
|
async function stopApp() {
|
|
const app = currentApp;
|
|
if (!app) {
|
|
return;
|
|
}
|
|
|
|
currentApp = null;
|
|
expectedExits.add(app);
|
|
|
|
await new Promise((resolve) => {
|
|
let settled = false;
|
|
|
|
const finish = () => {
|
|
if (settled) {
|
|
return;
|
|
}
|
|
|
|
settled = true;
|
|
resolve();
|
|
};
|
|
|
|
app.once("exit", finish);
|
|
app.kill("SIGTERM");
|
|
killChildTreeByPid(app.pid, "TERM");
|
|
cleanupStaleDevApps();
|
|
|
|
setTimeout(() => {
|
|
if (settled) {
|
|
return;
|
|
}
|
|
|
|
app.kill("SIGKILL");
|
|
killChildTreeByPid(app.pid, "KILL");
|
|
cleanupStaleDevApps();
|
|
finish();
|
|
}, forcedShutdownTimeoutMs).unref();
|
|
});
|
|
}
|
|
|
|
function scheduleRestart() {
|
|
if (shuttingDown) {
|
|
return;
|
|
}
|
|
|
|
if (restartTimer) {
|
|
clearTimeout(restartTimer);
|
|
}
|
|
|
|
restartTimer = setTimeout(() => {
|
|
restartTimer = null;
|
|
restartQueue = restartQueue
|
|
.catch(() => undefined)
|
|
.then(async () => {
|
|
await stopApp();
|
|
if (!shuttingDown) {
|
|
startApp();
|
|
}
|
|
});
|
|
}, restartDebounceMs);
|
|
}
|
|
|
|
function startWatchers() {
|
|
for (const { directory, files } of watchedDirectories) {
|
|
const watcher = NodeFS.watch(
|
|
NodePath.join(desktopDir, directory),
|
|
{ persistent: true },
|
|
(_eventType, filename) => {
|
|
if (typeof filename !== "string" || !files.has(filename)) {
|
|
return;
|
|
}
|
|
|
|
scheduleRestart();
|
|
},
|
|
);
|
|
|
|
watchers.push(watcher);
|
|
}
|
|
}
|
|
|
|
function killChildTree(signal) {
|
|
if (hostPlatform === "win32") {
|
|
return;
|
|
}
|
|
|
|
// Kill direct children as a final fallback in case normal shutdown leaves stragglers.
|
|
NodeChildProcess.spawnSync("pkill", [`-${signal}`, "-P", String(process.pid)], {
|
|
stdio: "ignore",
|
|
});
|
|
}
|
|
|
|
async function shutdown(exitCode) {
|
|
if (shuttingDown) return;
|
|
shuttingDown = true;
|
|
|
|
if (restartTimer) {
|
|
clearTimeout(restartTimer);
|
|
restartTimer = null;
|
|
}
|
|
|
|
for (const watcher of watchers) {
|
|
watcher.close();
|
|
}
|
|
|
|
await stopApp();
|
|
killChildTree("TERM");
|
|
await new Promise((resolve) => {
|
|
setTimeout(resolve, childTreeGracePeriodMs);
|
|
});
|
|
killChildTree("KILL");
|
|
|
|
process.exit(exitCode);
|
|
}
|
|
|
|
startWatchers();
|
|
cleanupStaleDevApps();
|
|
startApp();
|
|
|
|
process.once("SIGINT", () => {
|
|
void shutdown(130);
|
|
});
|
|
process.once("SIGTERM", () => {
|
|
void shutdown(143);
|
|
});
|
|
process.once("SIGHUP", () => {
|
|
void shutdown(129);
|
|
});
|