feat: improvements to web dashboard
This commit is contained in:
68
src/index.ts
68
src/index.ts
@@ -1,15 +1,70 @@
|
||||
import { AuroraClient } from "@/lib/BotClient";
|
||||
import { env } from "@lib/env";
|
||||
|
||||
import { webServer } from "./web/src";
|
||||
import { join } from "node:path";
|
||||
|
||||
// Load commands & events
|
||||
await AuroraClient.loadCommands();
|
||||
await AuroraClient.loadEvents();
|
||||
await AuroraClient.deployCommands();
|
||||
|
||||
webServer.start();
|
||||
console.log("Web server is running on http://localhost:3000")
|
||||
console.log("🌐 Starting web server...");
|
||||
|
||||
const webProjectPath = join(import.meta.dir, "web");
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
let shuttingDown = false;
|
||||
|
||||
const startWebServer = () => {
|
||||
const args = isProduction
|
||||
? [process.execPath, "src/index.ts"]
|
||||
: [process.execPath, "--hot", "src/index.ts"];
|
||||
|
||||
return Bun.spawn(args, {
|
||||
cwd: webProjectPath,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
WEB_PORT: process.env.WEB_PORT || "3000",
|
||||
...(process.env.HOST && { WEB_HOST: process.env.HOST }),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
let webServer = startWebServer();
|
||||
|
||||
// Monitor web server and restart on unexpected exit
|
||||
const monitorWebServer = async () => {
|
||||
const exitCode = await webServer.exited;
|
||||
if (!shuttingDown && exitCode !== 0) {
|
||||
console.warn(`⚠️ Web server exited with code ${exitCode}, restarting in 1s...`);
|
||||
await Bun.sleep(1000);
|
||||
webServer = startWebServer();
|
||||
monitorWebServer(); // Continue monitoring the new process
|
||||
}
|
||||
};
|
||||
monitorWebServer();
|
||||
|
||||
// Wait for web server to be ready
|
||||
const waitForWebServer = async (url: string, maxAttempts = 30): Promise<boolean> => {
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
try {
|
||||
const res = await fetch(`${url}/api/health`);
|
||||
if (res.ok) return true;
|
||||
} catch {
|
||||
// Server not ready yet
|
||||
}
|
||||
await Bun.sleep(100);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const webPort = process.env.WEB_PORT || "3000";
|
||||
const webReady = await waitForWebServer(`http://localhost:${webPort}`);
|
||||
if (webReady) {
|
||||
console.log(`✅ Web server ready at http://localhost:${webPort}`);
|
||||
} else {
|
||||
console.warn("⚠️ Web server did not become ready in time, continuing anyway...");
|
||||
}
|
||||
|
||||
// login with the token from .env
|
||||
if (!env.DISCORD_BOT_TOKEN) {
|
||||
@@ -19,7 +74,10 @@ AuroraClient.login(env.DISCORD_BOT_TOKEN);
|
||||
|
||||
// Handle graceful shutdown
|
||||
const shutdownHandler = () => {
|
||||
webServer.stop();
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
console.log("🛑 Shutdown signal received. Stopping web server...");
|
||||
webServer.kill();
|
||||
AuroraClient.shutdown();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,46 +1,18 @@
|
||||
import { serve } from "bun";
|
||||
import index from "./index.html";
|
||||
/**
|
||||
* Web server entry point.
|
||||
*
|
||||
* This file can be run directly for standalone development:
|
||||
* bun --hot src/index.ts
|
||||
*
|
||||
* Or the server can be started in-process by importing from ./server.ts
|
||||
*/
|
||||
|
||||
import { createWebServer } from "./server";
|
||||
|
||||
const server = serve({
|
||||
routes: {
|
||||
// Serve index.html for all unmatched routes.
|
||||
"/*": index,
|
||||
|
||||
"/api/hello": {
|
||||
async GET(req) {
|
||||
return Response.json({
|
||||
message: "Hello, world!",
|
||||
method: "GET",
|
||||
});
|
||||
},
|
||||
async PUT(req) {
|
||||
return Response.json({
|
||||
message: "Hello, world!",
|
||||
method: "PUT",
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
"/api/hello/:name": async req => {
|
||||
const name = req.params.name;
|
||||
return Response.json({
|
||||
message: `Hello, ${name}!`,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
development: process.env.NODE_ENV !== "production" && {
|
||||
// Enable browser hot reloading in development
|
||||
hmr: true,
|
||||
|
||||
// Echo console logs from the browser to the server
|
||||
console: true,
|
||||
},
|
||||
|
||||
|
||||
|
||||
// Auto-start when run directly
|
||||
const instance = await createWebServer({
|
||||
port: Number(process.env.WEB_PORT) || 3000,
|
||||
hostname: process.env.WEB_HOST || "localhost",
|
||||
});
|
||||
|
||||
export const webServer = { start: () => server, stop: () => server.stop() };
|
||||
|
||||
console.log(`🌐 Web server is running at ${instance.url}`);
|
||||
105
src/web/src/server.ts
Normal file
105
src/web/src/server.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Web server factory module.
|
||||
* Exports a function to create and start the web server.
|
||||
* This allows the server to be started in-process from the main application.
|
||||
*/
|
||||
|
||||
import { serve } from "bun";
|
||||
|
||||
export interface WebServerConfig {
|
||||
port?: number;
|
||||
hostname?: string;
|
||||
}
|
||||
|
||||
export interface WebServerInstance {
|
||||
server: ReturnType<typeof serve>;
|
||||
stop: () => Promise<void>;
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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");
|
||||
|
||||
const server = serve({
|
||||
port,
|
||||
hostname,
|
||||
routes: {
|
||||
// Serve index.html for all unmatched routes (SPA catch-all)
|
||||
"/*": index.default,
|
||||
|
||||
"/api/hello": {
|
||||
async GET(req) {
|
||||
return Response.json({
|
||||
message: "Hello, world!",
|
||||
method: "GET",
|
||||
});
|
||||
},
|
||||
async PUT(req) {
|
||||
return Response.json({
|
||||
message: "Hello, world!",
|
||||
method: "PUT",
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
"/api/hello/:name": async (req) => {
|
||||
const name = req.params.name;
|
||||
return Response.json({
|
||||
message: `Hello, ${name}!`,
|
||||
});
|
||||
},
|
||||
|
||||
"/api/health": () => Response.json({ status: "ok", timestamp: Date.now() }),
|
||||
},
|
||||
|
||||
development: process.env.NODE_ENV !== "production" && {
|
||||
hmr: true,
|
||||
console: true,
|
||||
},
|
||||
});
|
||||
|
||||
const url = `http://${hostname}:${port}`;
|
||||
|
||||
return {
|
||||
server,
|
||||
url,
|
||||
stop: async () => {
|
||||
server.stop(true);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the web server from the main application root.
|
||||
* Handles the working directory context switch needed for bundler resolution.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user