import { APPS_DIR } from '$apps' import { existsSync, readdirSync, statSync } from 'fs' import { join } from 'path' export async function serveStatic(appName: string, req: Request): Promise { const url = new URL(req.url) const pathname = decodeURIComponent(url.pathname) const pubDir = join(APPS_DIR, appName, 'pub') // Resolve the file path, preventing directory traversal const filePath = join(pubDir, pathname) if (!filePath.startsWith(pubDir)) { return new Response('Forbidden', { status: 403 }) } // Directory: try index.html, then file listing if (existsSync(filePath) && statSync(filePath).isDirectory()) { const indexPath = join(filePath, 'index.html') if (existsSync(indexPath)) { return new Response(Bun.file(indexPath)) } return fileListing(appName, pathname, filePath) } // Exact file match if (existsSync(filePath)) { return new Response(Bun.file(filePath)) } // Clean URLs: try .html extension const htmlPath = filePath + '.html' if (existsSync(htmlPath)) { return new Response(Bun.file(htmlPath)) } return new Response('Not Found', { status: 404 }) } function fileListing(appName: string, pathname: string, dirPath: string): Response { const trail = pathname.endsWith('/') ? pathname : pathname + '/' const entries = readdirSync(dirPath, { withFileTypes: true }) .filter(e => !e.name.startsWith('.')) .sort((a, b) => { if (a.isDirectory() && !b.isDirectory()) return -1 if (!a.isDirectory() && b.isDirectory()) return 1 return a.name.localeCompare(b.name) }) const rows = entries.map(e => { const display = e.isDirectory() ? `${e.name}/` : e.name const href = `${trail}${e.name}` const stat = statSync(join(dirPath, e.name)) const size = e.isDirectory() ? '—' : formatSize(stat.size) return ` ${display}${size}` }).join('\n') const parent = pathname !== '/' ? ` ..\n` : '' const html = ` ${appName} — ${pathname}

${appName}${pathname}

${parent}${rows}
` return new Response(html, { headers: { 'content-type': 'text/html' } }) } const formatSize = (bytes: number): string => bytes < 1024 ? `${bytes} B` : bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / (1024 * 1024)).toFixed(1)} MB`