import { file } from "bun"; import { inspectAsync, listAsync } from "fs-jetpack"; import { join } from "path"; import { createRouter } from "radix3"; import { ensureNotRunning } from "utils/ensure"; import { prodIndex } from "utils/prod-index"; import { dir } from "../utils/dir"; import { g } from "../utils/global"; import { parseArgs } from "./parse-args"; import { serveAPI } from "./serve-api"; import { serveWeb } from "./serve-web"; import exitHook from "exit-hook"; import { fileTypeFromBlob } from "file-type"; export const createServer = async () => { console.log(`[DEBUG] Starting server creation...`); g.router = createRouter({ strictTrailingSlash: true }); g.api = {}; const scan = async (path: string, root?: string) => { console.log(`[DEBUG] Scanning API directory: ${path}`); const apis = await listAsync(path); if (apis) { for (const filename of apis) { const importPath = join(path, filename); if (filename.endsWith(".ts")) { try { const api = await import(importPath); let args: string[] = await parseArgs(importPath); const route = { url: api._.url, args, raw: !!api._.raw, fn: api._.api, path: importPath.substring((root || path).length + 1), }; g.api[filename] = route; g.router.insert(route.url, g.api[filename]); } catch (e) { g.log.warn( `Failed to import app/srv/api${importPath.substring( (root || path).length )}` ); const f = file(importPath); if (f.size > 0) { console.error(e); } else { g.log.warn(` ➨ file is empty`); } } } else { const dir = await inspectAsync(importPath); if (dir?.type === "dir") { await scan(importPath, path); } } } } }; console.log(`[DEBUG] Starting API directory scans...`); await scan(dir(`app/srv/api`)); console.log(`[DEBUG] app/srv/api scan completed`); await scan(dir(`pkgs/api`)); console.log(`[DEBUG] pkgs/api scan completed`); console.log(`[DEBUG] Creating server functions...`); g.createServer = (arg) => { return async (site_id: string) => { return arg; }; }; if (g.mode === "prod") { exitHook((signal) => { g.server.stop(); }); } // console.log(`[DEBUG] Ensuring server is not running...`); // await ensureNotRunning(); console.log(`[DEBUG] Starting Bun.serve on port ${g.port}...`); // First try a minimal test server to isolate Bun.serve issues // console.log(`[DEBUG] Creating minimal test server first...`); // try { // const testServer = Bun.serve({ // port: 3001, // Use different port for testing // hostname: "0.0.0.0", // development: false, // fetch(req) { // console.log(`[TEST] Minimal server request: ${req.method} ${req.url}`); // return new Response("Test server working!", { // status: 200, // headers: { "Content-Type": "text/plain" } // }); // }, // }); // console.log(`[DEBUG] ✓ Test server listening on ${testServer.hostname}:${testServer.port}`); // } catch (testError) { // console.error(`[ERROR] Test server failed:`, testError); // } try { g.server = Bun.serve({ port: g.port, maxRequestBodySize: 1024 * 1024 * 128, hostname: "0.0.0.0", // Explicitly bind to all interfaces development: false, // Force production mode async fetch(req) { console.log(`[DEBUG] === FETCH HANDLER CALLED ===`); console.log(`[DEBUG] Request received: ${req.method} ${req.url}`); console.log(`[DEBUG] Request headers:`, Object.fromEntries(req.headers.entries())); // IMMEDIATE RESPONSE FOR TESTING if (req.url === '/') { console.log(`[DEBUG] Returning immediate test response for root path`); return new Response('Test response working!', { status: 200, headers: { 'Content-Type': 'text/plain' } }); } try { console.log(`[DEBUG] Processing normal request flow...`); const url = new URL(req.url) as URL; url.pathname = url.pathname.replace(/\/+/g, "/"); console.log(`[DEBUG] Processed URL: ${url.pathname}`); console.log(`[DEBUG] Creating prasi object...`); const prasi = {}; console.log(`[DEBUG] Creating index...`); const index = prodIndex(g.deploy.config.site_id, prasi); console.log(`[DEBUG] Index created successfully`); const handle = async ( req: Request, opt?: { rewrite?: (arg: { body: Bun.BodyInit; headers: Headers | any; }) => Bun.BodyInit; } ) => { const api = await serveAPI(url, req); if (api) { return api; } console.log(`[DEBUG] No API found, lookup for ${url.pathname}`); if (g.deploy.router) { const found = g.deploy.router.lookup(url.pathname); if (found) { return await serveWeb({ content: index.render(), pathname: "index.html", cache_accept: req.headers.get("accept-encoding") || "", opt, }); } console.log(`[DEBUG] No route found, check content for ${url.pathname}`); if (g.deploy.content) { const core = g.deploy.content.code.core; const site = g.deploy.content.code.site; const pub = g.deploy.content.public; //list all content // console.log(`[DEBUG] Content paths: core=${Object.keys(core).join(", ")}, site=${Object.keys(site).join(", ")}, pub=${Object.keys(pub).join(", ")}`); let pathname = url.pathname; if (url.pathname[0] === "/") pathname = pathname.substring(1); if ( !pathname || pathname === "index.html" || pathname === "index.htm" ) { return await serveWeb({ content: index.render(), pathname: "index.html", cache_accept: req.headers.get("accept-encoding") || "", opt, }); } let content = ""; if (core[pathname]) content = core[pathname]; else if (site[pathname]) content = site[pathname]; else if (pub[pathname]){ //read content from file content = await Bun.file(dir(`public/${pathname}`)).text(); //detect file type const fileType = await fileTypeFromBlob(Bun.file(dir(`public/${pathname}`))); if(fileType){ //set content type opt?.headers?.set("Content-Type", fileType.mime); } return new Response(content, { status: 200, headers: opt?.headers || new Headers(), }); } if (content) { return await serveWeb({ content, pathname, cache_accept: req.headers.get("accept-encoding") || "", opt, }); } } } return new Response(`404 Not Found`, { status: 404, statusText: "Not Found", }); }; if ( !url.pathname.startsWith("/_deploy") && !url.pathname.startsWith("/_prasi") ) { if (g.deploy.server && index) { try { return await g.deploy.server.http({ handle, mode: "prod", req, server: g.server, url: { pathname: url.pathname, raw: url }, index: index, prasi, }); } catch (e) { console.error(e); } } } return handle(req); } catch (fetchError) { console.error(`[ERROR] Fetch handler error:`, fetchError); console.error(`[ERROR] Stack trace:`, fetchError.stack); return new Response(`Fetch Handler Error: ${fetchError.message}`, { status: 500, statusText: "Internal Server Error", }); } }, error(error) { console.error(`[ERROR] Server error:`, error); return new Response(`Internal Server Error: ${error.message}`, { status: 500, statusText: "Internal Server Error", }); }, }); console.log(`[DEBUG] Server object created successfully!`); console.log(`[DEBUG] Server actually listening on ${g.server.hostname}:${g.server.port}`); // Verify the server is actually listening setTimeout(() => { if (g.server && g.server.port === g.port) { console.log(`[DEBUG] ✓ Server verified to be listening on port ${g.port}`); } else { console.error(`[ERROR] ✗ Server not properly bound to port ${g.port}`); } }, 100); } catch (error) { console.error(`[ERROR] Failed to start Bun.serve:`, error); throw error; } if (process.env.PRASI_MODE === "dev") { g.log.info(`http://localhost:${g.server.port}`); console.log(`[DEBUG] Server started in DEV mode`); } else { g.log.info(`Started at port: ${g.server.port}`); console.log(`[DEBUG] Server started in PROD mode`); } console.log(`[DEBUG] createServer function completed successfully!`); };