feat: update Electron configuration and static server implementation

- Upgraded Electron version to 39.2.7 and TypeScript to 5.9.3 in package-lock.json.
- Modified next.config.ts to set output to "export" for static site generation.
- Changed package.json to include the output directory for deployment.
- Enhanced main.js to implement a static file server for production builds, serving files from the "out" directory.
- Adjusted the loading mechanism to use the static server in production and the Next.js dev server in development.
This commit is contained in:
Kacper
2025-12-13 23:12:10 +01:00
parent bc46a18372
commit bea115d1e4
4 changed files with 935 additions and 647 deletions

View File

@@ -8,15 +8,23 @@
const path = require("path"); const path = require("path");
const { spawn } = require("child_process"); const { spawn } = require("child_process");
const fs = require("fs"); const fs = require("fs");
const http = require("http");
// Load environment variables from .env file
require("dotenv").config({ path: path.join(__dirname, "../.env") });
const { app, BrowserWindow, ipcMain, dialog, shell } = require("electron"); const { app, BrowserWindow, ipcMain, dialog, shell } = require("electron");
// Load environment variables from .env file (development only)
if (!app.isPackaged) {
try {
require("dotenv").config({ path: path.join(__dirname, "../.env") });
} catch (error) {
console.warn("[Electron] dotenv not available:", error.message);
}
}
let mainWindow = null; let mainWindow = null;
let serverProcess = null; let serverProcess = null;
let staticServer = null;
const SERVER_PORT = 3008; const SERVER_PORT = 3008;
const STATIC_PORT = 3007;
// Get icon path - works in both dev and production // Get icon path - works in both dev and production
function getIconPath() { function getIconPath() {
@@ -25,6 +33,67 @@ function getIconPath() {
: path.join(__dirname, "../public/logo.png"); : path.join(__dirname, "../public/logo.png");
} }
/**
* Start static file server for production builds
*/
function startStaticServer() {
const staticPath = path.join(__dirname, "../out");
staticServer = http.createServer((request, response) => {
// Parse the URL and remove query string
let filePath = path.join(staticPath, request.url.split("?")[0]);
// Default to index.html for directory requests
if (filePath.endsWith("/")) {
filePath = path.join(filePath, "index.html");
} else if (!path.extname(filePath)) {
filePath += ".html";
}
// Check if file exists
fs.stat(filePath, (err, stats) => {
if (err || !stats.isFile()) {
// Try index.html for SPA fallback
filePath = path.join(staticPath, "index.html");
}
// Read and serve the file
fs.readFile(filePath, (error, content) => {
if (error) {
response.writeHead(500);
response.end("Server Error");
return;
}
// Set content type based on file extension
const ext = path.extname(filePath);
const contentTypes = {
".html": "text/html",
".js": "application/javascript",
".css": "text/css",
".json": "application/json",
".png": "image/png",
".jpg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".eot": "application/vnd.ms-fontobject",
};
response.writeHead(200, { "Content-Type": contentTypes[ext] || "application/octet-stream" });
response.end(content);
});
});
});
staticServer.listen(STATIC_PORT, () => {
console.log(`[Electron] Static server running at http://localhost:${STATIC_PORT}`);
});
}
/** /**
* Start the backend server * Start the backend server
*/ */
@@ -165,15 +234,11 @@ function createWindow() {
backgroundColor: "#0a0a0a", backgroundColor: "#0a0a0a",
}); });
// Load Next.js dev server in development or production build // Load Next.js dev server in development or static server in production
const isDev = !app.isPackaged; const isDev = !app.isPackaged;
if (isDev) { mainWindow.loadURL(`http://localhost:${STATIC_PORT}`);
mainWindow.loadURL("http://localhost:3007"); if (isDev && process.env.OPEN_DEVTOOLS === "true") {
if (process.env.OPEN_DEVTOOLS === "true") { mainWindow.webContents.openDevTools();
mainWindow.webContents.openDevTools();
}
} else {
mainWindow.loadFile(path.join(__dirname, "../.next/server/app/index.html"));
} }
mainWindow.on("closed", () => { mainWindow.on("closed", () => {
@@ -195,6 +260,11 @@ app.whenReady().then(async () => {
} }
try { try {
// Start static file server in production
if (app.isPackaged) {
startStaticServer();
}
// Start backend server // Start backend server
await startServer(); await startServer();
@@ -225,6 +295,13 @@ app.on("before-quit", () => {
serverProcess.kill(); serverProcess.kill();
serverProcess = null; serverProcess = null;
} }
// Close static server
if (staticServer) {
console.log("[Electron] Stopping static server...");
staticServer.close();
staticServer = null;
}
}); });
// ============================================ // ============================================

View File

@@ -1,6 +1,7 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: "export",
env: { env: {
CLAUDE_CODE_OAUTH_TOKEN: process.env.CLAUDE_CODE_OAUTH_TOKEN || "", CLAUDE_CODE_OAUTH_TOKEN: process.env.CLAUDE_CODE_OAUTH_TOKEN || "",
}, },

View File

@@ -100,7 +100,7 @@
}, },
"files": [ "files": [
"electron/**/*", "electron/**/*",
".next/**/*", "out/**/*",
"public/**/*", "public/**/*",
"!node_modules/**/*" "!node_modules/**/*"
], ],

1478
package-lock.json generated

File diff suppressed because it is too large Load Diff