forked from syntaxbullet/AuroraBot-discord
refactor: fix frontend
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./frontend.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -4,7 +4,8 @@
|
||||
* This allows the server to be started in-process from the main application.
|
||||
*/
|
||||
|
||||
import { serve } from "bun";
|
||||
import { serve, spawn, type Subprocess } from "bun";
|
||||
import { join, resolve, dirname } from "path";
|
||||
|
||||
export interface WebServerConfig {
|
||||
port?: number;
|
||||
@@ -20,32 +21,78 @@ export interface WebServerInstance {
|
||||
/**
|
||||
* Creates and starts the web server.
|
||||
*
|
||||
* IMPORTANT: This function must be called from within the web project directory
|
||||
* or the bundler won't resolve paths correctly. Use `startWebServerFromRoot`
|
||||
* if calling from the main application.
|
||||
* Automatically handles building the frontend:
|
||||
* - In development: Spawns 'bun run build.ts --watch'
|
||||
* - In production: Assumes 'dist' is already built (or builds once)
|
||||
*/
|
||||
export async function createWebServer(config: WebServerConfig = {}): Promise<WebServerInstance> {
|
||||
const { port = 3000, hostname = "localhost" } = config;
|
||||
|
||||
// Dynamic import of the HTML to ensure bundler context is correct
|
||||
const index = await import("./index.html");
|
||||
// Resolve directories
|
||||
// server.ts is in web/src/, so we go up one level to get web/
|
||||
const currentDir = dirname(new URL(import.meta.url).pathname);
|
||||
const webRoot = resolve(currentDir, "..");
|
||||
const distDir = join(webRoot, "dist");
|
||||
|
||||
// Manage build process in development
|
||||
let buildProcess: Subprocess | undefined;
|
||||
const isDev = process.env.NODE_ENV !== "production";
|
||||
|
||||
if (isDev) {
|
||||
console.log("🛠️ Starting Web Bundler in Watch Mode...");
|
||||
try {
|
||||
buildProcess = spawn(["bun", "run", "build.ts", "--watch"], {
|
||||
cwd: webRoot,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to start build process:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const server = serve({
|
||||
port,
|
||||
hostname,
|
||||
routes: {
|
||||
// Serve index.html for all unmatched routes (SPA catch-all)
|
||||
"/*": index.default,
|
||||
async fetch(req) {
|
||||
const url = new URL(req.url);
|
||||
|
||||
// API routes
|
||||
if (url.pathname === "/api/health") {
|
||||
return Response.json({ status: "ok", timestamp: Date.now() });
|
||||
}
|
||||
|
||||
// Static File Serving
|
||||
let pathName = url.pathname;
|
||||
if (pathName === "/") pathName = "/index.html";
|
||||
|
||||
"/api/health": () => Response.json({ status: "ok", timestamp: Date.now() }),
|
||||
// Construct safe file path
|
||||
// Remove leading slash to join correctly
|
||||
const safePath = join(distDir, pathName.replace(/^\//, ""));
|
||||
|
||||
// Security check: ensure path is within distDir
|
||||
if (!safePath.startsWith(distDir)) {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
|
||||
const fileRef = Bun.file(safePath);
|
||||
if (await fileRef.exists()) {
|
||||
return new Response(fileRef);
|
||||
}
|
||||
|
||||
// SPA Fallback: Serve index.html for unknown non-file routes
|
||||
// If the path looks like a file (has extension), return 404
|
||||
// Otherwise serve index.html
|
||||
const parts = pathName.split("/");
|
||||
const lastPart = parts[parts.length - 1];
|
||||
if (lastPart?.includes(".")) {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
|
||||
return new Response(Bun.file(join(distDir, "index.html")));
|
||||
},
|
||||
|
||||
development: process.env.NODE_ENV !== "production" && {
|
||||
hmr: true,
|
||||
console: true,
|
||||
},
|
||||
development: isDev,
|
||||
});
|
||||
|
||||
const url = `http://${hostname}:${port}`;
|
||||
@@ -54,6 +101,9 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
||||
server,
|
||||
url,
|
||||
stop: async () => {
|
||||
if (buildProcess) {
|
||||
buildProcess.kill();
|
||||
}
|
||||
server.stop(true);
|
||||
},
|
||||
};
|
||||
@@ -61,25 +111,13 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
||||
|
||||
/**
|
||||
* Starts the web server from the main application root.
|
||||
* Handles the working directory context switch needed for bundler resolution.
|
||||
* Kept for backward compatibility, but assumes webProjectPath is handled internally or ignored
|
||||
* in favor of relative path resolution from this file.
|
||||
*/
|
||||
export async function startWebServerFromRoot(
|
||||
webProjectPath: string,
|
||||
config: WebServerConfig = {}
|
||||
): Promise<WebServerInstance> {
|
||||
const originalCwd = process.cwd();
|
||||
|
||||
try {
|
||||
// Change to web project directory for correct bundler resolution
|
||||
process.chdir(webProjectPath);
|
||||
|
||||
const instance = await createWebServer(config);
|
||||
|
||||
console.log(`🌐 Web server running at ${instance.url}`);
|
||||
|
||||
return instance;
|
||||
} finally {
|
||||
// Restore original working directory
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
// Current implementation doesn't need CWD switching thanks to absolute path resolution
|
||||
return createWebServer(config);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user