Files
ma7engine/core/server/spa.ts
T
maenekoandClaude Opus 5 18bd05bcbb ma7engine: ядро, шаблон панели, скаффолдер и доктор
Первый коммит существующего состояния. Ядро кладётся под git ради того,
чтобы половины разъезжались в продуктовые репозитории через git subtree,
а не копированием: subtree делает настоящий merge, хранит историю и умеет
отдавать правки обратно.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019LeBkZLiQrvdECf5nfgiMz
2026-08-26 20:39:37 +03:00

33 lines
1.6 KiB
TypeScript

// Раздача собранного фронта. Панель — один процесс и один образ: сервер сам
// отдаёт frontend/dist, отдельный nginx ей не нужен.
//
// Монтируется последним, после всех API-маршрутов: fallback отвечает на любой
// GET, поэтому маршрут, зарегистрированный после него, уже не сработает.
import express, { type Express, type Request, type Response } from 'express';
import fs from 'node:fs';
import path from 'node:path';
// Пути под этими префиксами получают 404, а не index.html: иначе опечатка в
// адресе API возвращает HTML со статусом 200, и клиент падает на разборе JSON
// вместо внятной ошибки.
const API_PREFIXES = ['/api'];
export function serveSpa(app: Express, dir: string) {
const root = path.resolve(dir);
if (!fs.existsSync(path.join(root, 'index.html'))) {
console.warn(`panel-server: в ${root} нет index.html — фронт не собран (npm run build) ` +
'или STATIC_DIR указывает не туда');
}
app.use(express.static(root));
app.get('*', (req: Request, res: Response) => {
if (API_PREFIXES.some(p => req.path.startsWith(p))) {
res.status(404).json({ message: 'Не найдено' });
return;
}
res.sendFile(path.join(root, 'index.html'));
});
}