lili fd0f80b226
Some checks failed
Backend Maven CI / backend-local (push) Has been cancelled
docs(release): 记录mini部署证据
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:22:53 -07:00

89 lines
3.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import http from 'node:http';
import { createReadStream, existsSync, statSync } from 'node:fs';
import { extname, join, normalize, resolve } from 'node:path';
// 这是 mini-desktop RC 部署的轻量静态服务:同时把前端相对 API 路径反代到 muse-server。
// 生产化部署应换成 Nginx/CaddyRC smoke 用 Node 单文件可减少额外系统依赖和端口冲突。
const args = Object.fromEntries(
process.argv.slice(2).map((arg) => {
const [key, ...rest] = arg.replace(/^--/, '').split('=');
return [key, rest.join('=') || ''];
}),
);
const root = resolve(args.root || '.');
const port = Number(args.port || 4175);
const backend = new URL(args.backend || 'http://127.0.0.1:48081');
const proxyPrefixes = (args.proxy || '/app-api,/admin-api,/sse').split(',').filter(Boolean);
const contentTypes = new Map([
['.html', 'text/html; charset=utf-8'],
['.js', 'text/javascript; charset=utf-8'],
['.mjs', 'text/javascript; charset=utf-8'],
['.css', 'text/css; charset=utf-8'],
['.json', 'application/json; charset=utf-8'],
['.png', 'image/png'],
['.jpg', 'image/jpeg'],
['.jpeg', 'image/jpeg'],
['.svg', 'image/svg+xml'],
['.ico', 'image/x-icon'],
['.woff', 'font/woff'],
['.woff2', 'font/woff2'],
['.ttf', 'font/ttf'],
['.eot', 'application/vnd.ms-fontobject'],
]);
function sendNotFound(res) {
res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
res.end('Not Found');
}
function proxy(req, res) {
const target = new URL(req.url || '/', backend);
const headers = { ...req.headers, host: backend.host };
const upstream = http.request(target, { method: req.method, headers }, (proxyRes) => {
res.writeHead(proxyRes.statusCode || 502, proxyRes.headers);
proxyRes.pipe(res);
});
upstream.on('error', (error) => {
res.writeHead(502, { 'content-type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({ code: 502, msg: `backend unavailable: ${error.code || error.message}` }));
});
req.pipe(upstream);
}
function serve(req, res) {
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
if (proxyPrefixes.some((prefix) => url.pathname === prefix || url.pathname.startsWith(`${prefix}/`))) {
proxy(req, res);
return;
}
const decodedPath = decodeURIComponent(url.pathname);
const relative = normalize(decodedPath).replace(/^\/+/, '');
let file = resolve(join(root, relative));
if (!file.startsWith(root)) {
sendNotFound(res);
return;
}
// SPA 深链统一回落到 index.html避免刷新路由时 404。
if (!existsSync(file) || statSync(file).isDirectory()) {
file = join(root, 'index.html');
}
if (!existsSync(file) || statSync(file).isDirectory()) {
sendNotFound(res);
return;
}
const type = contentTypes.get(extname(file).toLowerCase()) || 'application/octet-stream';
res.writeHead(200, { 'content-type': type });
createReadStream(file).pipe(res);
}
http.createServer(serve).listen(port, '0.0.0.0', () => {
console.log(`static proxy listening on ${port}, root=${root}, backend=${backend.origin}`);
});