File size: 3,050 Bytes
1dbc34b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
 * 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;
  }
}