mirror of
https://github.com/xxnuo/MTranServer.git
synced 2026-09-03 06:35:20 +08:00
fix(ui): serve
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,3 +19,4 @@ deprecated/go/bin/worker
|
||||
deprecated/go/dist/mtranserver-darwin-arm64
|
||||
*.o
|
||||
src/generated/
|
||||
src/assets/ui.ts
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"dev": "bun src/main.ts --log-level debug",
|
||||
"build": "rm -rf dist && bun build src/main.ts --compile --outfile ./dist/mtranserver --minify --sourcemap",
|
||||
"build:all": "bun scripts/build.ts",
|
||||
"build:dev": "bun run gen && rm -rf dist && bun build src/main.ts --outdir dist --target node --format esm --sourcemap --external zstd-wasm-decoder --external express && tsc --emitDeclarationOnly --outDir dist",
|
||||
"build:dev": "bun run gen && rm -rf dist && bun build src/main.ts --outdir dist --target node --format esm --sourcemap --external zstd-wasm-decoder --external express",
|
||||
"start": "node dist/main.js",
|
||||
"test": "bun test",
|
||||
"typecheck": "tsc --noEmit",
|
||||
|
||||
@@ -20,6 +20,12 @@ console.log("Cleaning dist...");
|
||||
await $`rm -rf dist`;
|
||||
await $`mkdir -p dist`;
|
||||
|
||||
console.log("Building UI...");
|
||||
await $`cd ui && bun run build`;
|
||||
|
||||
console.log("Generating UI assets map...");
|
||||
await $`bun run scripts/gen-ui-assets.ts`;
|
||||
|
||||
console.log("Generating routes and spec...");
|
||||
await $`bun run gen`;
|
||||
|
||||
|
||||
44
scripts/gen-ui-assets.ts
Normal file
44
scripts/gen-ui-assets.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { $ } from "bun";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const distDir = "ui/dist";
|
||||
const outputFile = "src/assets/ui.ts";
|
||||
|
||||
function* walkDir(dir: string): Generator<string> {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
yield* walkDir(fullPath);
|
||||
} else {
|
||||
yield fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const files = Array.from(walkDir(distDir));
|
||||
|
||||
const imports = files
|
||||
.map((file, i) => {
|
||||
const absolutePath = path.resolve(file);
|
||||
return `import _asset${i} from "${absolutePath}" with { type: "file" };`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const assetMap = files
|
||||
.map((file, i) => {
|
||||
const urlPath = "/" + path.relative(distDir, file).replace(/\\/g, "/");
|
||||
return ` "${urlPath}": _asset${i}`;
|
||||
})
|
||||
.join(",\n");
|
||||
|
||||
const code = `${imports}
|
||||
|
||||
export const assets: Record<string, string> = {
|
||||
${assetMap}
|
||||
};
|
||||
`;
|
||||
|
||||
await Bun.write(outputFile, code);
|
||||
console.log(`Generated ${outputFile} with ${files.length} assets`);
|
||||
@@ -1,14 +1,5 @@
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
export function getEmbeddedAssetPath(filename: string): string {
|
||||
const assetsDir = path.resolve(__dirname, '../../assets')
|
||||
return path.join(assetsDir, filename)
|
||||
}
|
||||
|
||||
export async function cleanupLegacyBin(configDir: string): Promise<void> {
|
||||
const binDir = path.join(configDir, 'bin')
|
||||
|
||||
48
src/middleware/ui.ts
Normal file
48
src/middleware/ui.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Request, Response, NextFunction, RequestHandler } from 'express';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { assets } from '@/assets/ui.js';
|
||||
|
||||
const mimeTypes: Record<string, string> = {
|
||||
'.html': 'text/html',
|
||||
'.css': 'text/css',
|
||||
'.js': 'application/javascript',
|
||||
'.json': 'application/json',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.ico': 'image/x-icon',
|
||||
};
|
||||
|
||||
export const UI: RequestHandler = async (req: Request, res: Response, next: NextFunction) => {
|
||||
let filePath = req.path;
|
||||
|
||||
if (filePath.startsWith('/ui/')) {
|
||||
filePath = filePath.substring(3);
|
||||
}
|
||||
|
||||
if (filePath === '/' || filePath === '') {
|
||||
filePath = '/index.html';
|
||||
}
|
||||
|
||||
const assetPath = assets[filePath];
|
||||
|
||||
if (assetPath) {
|
||||
const ext = filePath.substring(filePath.lastIndexOf('.'));
|
||||
const mimeType = mimeTypes[ext] || 'application/octet-stream';
|
||||
|
||||
res.setHeader('Content-Type', mimeType);
|
||||
const buffer = await readFile(assetPath);
|
||||
res.send(buffer);
|
||||
} else {
|
||||
const indexPath = assets['/index.html'];
|
||||
if (indexPath) {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
const buffer = await readFile(indexPath);
|
||||
res.send(buffer);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,5 @@
|
||||
import express from 'express';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
import { getConfig } from '@/config/index.js';
|
||||
import * as logger from '@/logger/index.js';
|
||||
@@ -11,9 +9,7 @@ import { cleanupLegacyBin } from '@/assets/index.js';
|
||||
import { requestId, errorHandler, cors } from '@/middleware/index.js';
|
||||
import { RegisterRoutes } from '@/generated/routes.js';
|
||||
import swaggerDocument from '@/generated/swagger.json';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
import { UI } from '@/middleware/ui.js';
|
||||
|
||||
export async function run() {
|
||||
const config = getConfig();
|
||||
@@ -34,22 +30,16 @@ export async function run() {
|
||||
app.use(express.json());
|
||||
app.use(cors());
|
||||
|
||||
const uiPath = path.resolve(__dirname, '../../ui/dist');
|
||||
app.use('/ui', express.static(uiPath));
|
||||
|
||||
app.get('/', (_req, res) => {
|
||||
res.redirect('/ui');
|
||||
});
|
||||
|
||||
RegisterRoutes(app);
|
||||
|
||||
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
|
||||
|
||||
app.use(UI);
|
||||
|
||||
app.use(errorHandler());
|
||||
|
||||
const server = app.listen(parseInt(config.port), config.host, () => {
|
||||
logger.info(`HTTP Service URL: http://${config.host}:${config.port}`);
|
||||
logger.info(`Web UI: http://${config.host}:${config.port}/ui`);
|
||||
logger.info(`Swagger Docs: http://${config.host}:${config.port}/docs`);
|
||||
logger.info(`Log level set to: ${config.logLevel}`);
|
||||
});
|
||||
|
||||
@@ -26,5 +26,5 @@
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "tests", "deprecated"]
|
||||
"exclude": ["node_modules", "dist", "tests", "deprecated", "src/assets/ui.ts"]
|
||||
}
|
||||
|
||||
@@ -816,9 +816,9 @@
|
||||
|
||||
"@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "https://registry.npmmirror.com/@emnapi/core/-/core-1.7.1.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.0", "https://registry.npmmirror.com/@emnapi/core/-/core-1.8.0.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-ryJnSmj4UhrGLZZPJ6PKVb4wNPAIkW6iyLy+0TRwazd3L1u0wzMe8RfqevAh2HbcSkoeLiSYnOVDOys4JSGYyg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.7.1.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.0", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.8.0.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-Z82FDl1ByxqPEPrAYYeTQVlx2FSHPe1qwX465c+96IRS3fTdSYRoJcRxg3g2fEG5I69z1dSEWQlNRRr0/677mg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
@@ -832,8 +832,6 @@
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"cmdk/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
|
||||
|
||||
"prop-types/react-is": ["react-is@16.13.1", "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from "path"
|
||||
import tailwindcss from "@tailwindcss/vite"
|
||||
|
||||
export default defineConfig({
|
||||
base: '/ui/',
|
||||
base: '/',
|
||||
plugins: [react(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
Reference in New Issue
Block a user