// Adapted from https://deno.land/std@0.106.0/http/file_server.ts import { listenAndServe, posix, readAll, serveFile, ServerRequest, ServerResponse, Sys, } from "../deps.ts"; export function runDevServer(): void { const target = posix.resolve("./web/"); const handler = async (req: ServerRequest) => { let response: ServerResponse | undefined; try { const normalizedUrl = normalizeURL(req.url); let fsPath = posix.join(target, normalizedUrl); if (fsPath.indexOf(target) !== 0) { fsPath = target; } const srcPath = fsPath.replace(/\/web\/(.*)\.js$/, "/$1.ts"); if (srcPath != fsPath) { // TODO check only local await bundle(srcPath, fsPath); } const fileInfo = await Deno.stat(fsPath); if (fileInfo.isDirectory) { response = await serveFile(req, posix.join(fsPath, "index.html")); } else { response = await serveFile(req, fsPath); } } catch (e) { console.error(e.message); response = await serveFallback(req, e); } finally { serverLog(req, response!); try { await req.respond(response!); } catch (e) { console.error(e.message); } } }; const port = 4507; const host = "0.0.0.0"; const addr = `${host}:${port}`; listenAndServe(addr, handler); console.log(`Dev server listening on http://${addr}/`); } export function normalizeURL(url: string): string { let normalizedUrl = url; try { normalizedUrl = decodeURI(normalizedUrl); } catch (e) { if (!(e instanceof URIError)) { throw e; } } try { //allowed per https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html const absoluteURI = new URL(normalizedUrl); normalizedUrl = absoluteURI.pathname; } catch (e) { //wasn't an absoluteURI if (!(e instanceof TypeError)) { throw e; } } if (normalizedUrl[0] !== "/") { throw new URIError("The request URI is malformed."); } normalizedUrl = posix.normalize(normalizedUrl); const startOfParams = normalizedUrl.indexOf("?"); return startOfParams > -1 ? normalizedUrl.slice(0, startOfParams) : normalizedUrl; } const encoder = new TextEncoder(); function serveFallback(_req: ServerRequest, e: Error): Promise { if (e instanceof URIError) { return Promise.resolve({ status: 400, body: encoder.encode("Bad Request"), }); } else if (e instanceof Deno.errors.NotFound) { return Promise.resolve({ status: 404, body: encoder.encode("Not Found"), }); } else { return Promise.resolve({ status: 500, body: encoder.encode("Internal server error"), }); } } function serverLog(req: ServerRequest, res: ServerResponse): void { const d = new Date().toISOString(); const dateFmt = `[${d.slice(0, 10)} ${d.slice(11, 19)}]`; const s = `${dateFmt} "${req.method} ${req.url} ${req.proto}" ${res.status}`; console.log(s); } async function run(...cmd: string[]): Promise { const process = Sys.run({ cmd }); const result = await process.status(); return result.success; } async function deno(cmd: string, ...args: string[]): Promise { const flags = await readConfigFlags(cmd); return await run("deno", cmd, ...flags, ...args); } async function readConfigFlags(cmd: string): Promise { const path = `config/${cmd}.flags`; try { const f = await Sys.open(path); const content = await readAll(f); return new TextDecoder() .decode(content) .split(" ") .filter((part) => part && part.startsWith("--")); } catch { } return []; } async function bundle( source: string, destination: string, ): Promise { if ( await deno("bundle", "-q", source, destination) && await deno("fmt", "-q", destination) ) { console.log(`Bundled : ${source} -> ${destination}`); } else { console.error(`Bundle failed for: ${source}`); } }