// Tiny zero-dependency static file server for the repo root. Tooling, not course code. // Usage: node scripts/serve.mjs [port] (default 8000) import { createServer } from 'node:http' import { readFile, stat } from 'node:fs/promises' import { extname, join, normalize, resolve, sep } from 'node:path' import { fileURLToPath } from 'node:url' const root = resolve(fileURLToPath(new URL('..', import.meta.url))) const port = Number(process.argv[2] ?? 8000) const types = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.mjs': 'text/javascript; charset=utf-8', '.json': 'application/json', '.css': 'text/css', '.md': 'text/markdown; charset=utf-8', '.svg': 'image/svg+xml', '.png': 'image/png', '.wasm': 'application/wasm', '.map': 'application/json', } createServer(async (req, res) => { try { const path = normalize(decodeURIComponent(new URL(req.url, 'http://x').pathname)) let file = join(root, path) if (file !== root && !file.startsWith(root + sep)) throw Object.assign(new Error(), { code: 'EACCES' }) if ((await stat(file)).isDirectory()) file = join(file, 'index.html') const body = await readFile(file) res.writeHead(200, { 'content-type': types[extname(file)] ?? 'application/octet-stream' }) res.end(body) } catch (e) { res.writeHead(e.code === 'EACCES' ? 403 : 404, { 'content-type': 'text/plain' }) res.end(e.code === 'EACCES' ? 'forbidden' : 'not found') } }).listen(port, () => console.log(`Serving ${root} on http://localhost:${port}`))