Files
automaker/apps/ui/src/electron/server/static-server.ts
Shirone d7ad87bd1b fix: Correct __dirname paths for Vite bundled electron modules
Vite bundles all electron modules into a single main.js file,
so __dirname remains apps/ui/dist-electron regardless of source
file location. Updated path comments to clarify this behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 20:49:53 +01:00

102 lines
3.0 KiB
TypeScript

/**
* Static file server for production builds
*
* Serves the built frontend files in production mode.
* Uses centralized electronApp methods for serving static files from app bundle.
*/
import path from 'path';
import http from 'http';
import { electronAppExists, electronAppStat, electronAppReadFile } from '@automaker/platform';
import { createLogger } from '@automaker/utils/logger';
import { state } from '../state';
const logger = createLogger('StaticServer');
/**
* MIME type mapping for static files
*/
const CONTENT_TYPES: Record<string, string> = {
'.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',
};
/**
* Start static file server for production builds
* Uses centralized electronApp methods for serving static files from app bundle.
*/
export async function startStaticServer(): Promise<void> {
// __dirname is apps/ui/dist-electron (Vite bundles all into single file)
const staticPath = path.join(__dirname, '../dist');
state.staticServer = http.createServer((request, response) => {
let filePath = path.join(staticPath, request.url?.split('?')[0] || '/');
if (filePath.endsWith('/')) {
filePath = path.join(filePath, 'index.html');
} else if (!path.extname(filePath)) {
// For client-side routing, serve index.html for paths without extensions
const possibleFile = filePath + '.html';
try {
if (!electronAppExists(filePath) && !electronAppExists(possibleFile)) {
filePath = path.join(staticPath, 'index.html');
} else if (electronAppExists(possibleFile)) {
filePath = possibleFile;
}
} catch {
filePath = path.join(staticPath, 'index.html');
}
}
electronAppStat(filePath, (err, stats) => {
if (err || !stats?.isFile()) {
filePath = path.join(staticPath, 'index.html');
}
electronAppReadFile(filePath, (error, content) => {
if (error || !content) {
response.writeHead(500);
response.end('Server Error');
return;
}
const ext = path.extname(filePath);
response.writeHead(200, {
'Content-Type': CONTENT_TYPES[ext] || 'application/octet-stream',
});
response.end(content);
});
});
});
return new Promise((resolve, reject) => {
state.staticServer!.listen(state.staticPort, () => {
logger.info('Static server running at http://localhost:' + state.staticPort);
resolve();
});
state.staticServer!.on('error', reject);
});
}
/**
* Stop the static server if running
*/
export function stopStaticServer(): void {
if (state.staticServer) {
logger.info('Stopping static server...');
state.staticServer.close();
state.staticServer = null;
}
}