Первый коммит существующего состояния. Ядро кладётся под git ради того, чтобы половины разъезжались в продуктовые репозитории через git subtree, а не копированием: subtree делает настоящий merge, хранит историю и умеет отдавать правки обратно. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019LeBkZLiQrvdECf5nfgiMz
33 lines
1.6 KiB
TypeScript
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'));
|
|
});
|
|
}
|