diff --git a/electron/app/main.ts b/electron/app/main.ts index 61d0fd2c..e86b9f24 100644 --- a/electron/app/main.ts +++ b/electron/app/main.ts @@ -1,6 +1,7 @@ require("dotenv").config(); import { app, BrowserWindow } from "electron"; import path from "path"; +import fs from "fs"; import { findUnusedPorts, killProcess, setupEnv, setUserConfig } from "./utils"; import { startFastApiServer, startNextJsServer } from "./utils/servers"; import { ChildProcessByStdio } from "child_process"; @@ -16,6 +17,30 @@ var nextjsProcess: any; app.commandLine.appendSwitch('gtk-version', '3'); +// Mitigate "Unable to move the cache: Access is denied" on Windows (Chromium disk cache). +// Use explicit cache paths and remove stale old_* dirs that cause move failures. +if (process.platform === "win32") { + const ud = app.getPath("userData"); + const cacheBase = path.join(ud, "Cache"); + const gpuCacheBase = path.join(ud, "GPUCache"); + app.setPath("cache", cacheBase); + app.commandLine.appendSwitch("disk-cache-dir", cacheBase); + try { + [cacheBase, gpuCacheBase].forEach((dir) => { + if (fs.existsSync(dir)) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const e of entries) { + if (e.isDirectory() && e.name.startsWith("old_")) { + fs.rmSync(path.join(dir, e.name), { recursive: true, force: true }); + } + } + } + }); + } catch { + /* ignore cleanup errors */ + } +} + const createWindow = () => { win = new BrowserWindow({ width: 1280, diff --git a/electron/app/utils/servers.ts b/electron/app/utils/servers.ts index 166aaffa..5a3895c1 100644 --- a/electron/app/utils/servers.ts +++ b/electron/app/utils/servers.ts @@ -24,8 +24,22 @@ export async function startFastApiServer( const binary = process.platform === "win32" ? "fastapi.exe" : "fastapi"; command = path.join(directory, binary); args = ["--port", port.toString()]; + if (!fs.existsSync(command)) { + throw new Error( + `FastAPI binary not found at ${command}. Rebuild the app for ${process.platform} or run in dev mode.` + ); + } } + const safeLog = (data: Buffer | string, logPath: string) => { + try { + fs.appendFileSync(logPath, data); + } catch { + /* ignore if logs dir not writable */ + } + }; + const fastapiLogPath = path.join(logsDir, "fastapi-server.log"); + const fastApiProcess = spawn( command, args, @@ -36,13 +50,16 @@ export async function startFastApiServer( } ); fastApiProcess.stdout.on("data", (data: any) => { - fs.appendFileSync(path.join(logsDir, "fastapi-server.log"), data); + safeLog(data, fastapiLogPath); console.log(`FastAPI: ${data}`); }); fastApiProcess.stderr.on("data", (data: any) => { - fs.appendFileSync(path.join(logsDir, "fastapi-server.log"), data); + safeLog(data, fastapiLogPath); console.error(`FastAPI: ${data}`); }); + fastApiProcess.on("error", (err) => { + safeLog(`Spawn error: ${err.message}\n`, fastapiLogPath); + }); // Wait for FastAPI server to start await waitForServer(`${localhost}:${port}/docs`); return fastApiProcess; @@ -67,17 +84,25 @@ export async function startNextJsServer( env: { ...process.env, ...env }, } ); + const nextjsLogPath = path.join(logsDir, "nextjs-server.log"); + const safeNextLog = (d: Buffer | string) => { + try { + fs.appendFileSync(nextjsLogPath, d); + } catch { + /* ignore */ + } + }; nextjsProcess.stdout.on("data", (data: any) => { - fs.appendFileSync(path.join(logsDir, "nextjs-server.log"), data); + safeNextLog(data); console.log(`NextJS: ${data}`); }); nextjsProcess.stderr.on("data", (data: any) => { - fs.appendFileSync(path.join(logsDir, "nextjs-server.log"), data); + safeNextLog(data); console.error(`NextJS: ${data}`); }); } else { // Start NextJS build server - nextjsProcess = startNextjsBuildServer(directory, port); + nextjsProcess = await startNextjsBuildServer(directory, port); } // Wait for NextJS server to start @@ -85,16 +110,20 @@ export async function startNextJsServer( return nextjsProcess; } -async function startNextjsBuildServer(directory: string, port: number) { - const server = http.createServer((req, res) => { - return handler(req, res, { - public: directory, - cleanUrls: true, +function startNextjsBuildServer(directory: string, port: number): Promise { + return new Promise((resolve, reject) => { + const server = http.createServer((req, res) => { + return handler(req, res, { + public: directory, + cleanUrls: true, + }); + }); + server.on("error", reject); + server.listen(port, () => { + server.off("error", reject); + resolve(server); }); }); - - server.listen(port); - return server; }