123/**4 * The `deno task preview` command: serves the built `dist/` output locally over5 * HTTP for previewing the production PWA.6 *7 * @module8 */910import { join, normalize } from "node:path";11import { normalizeDeploymentBase, pathWithinDeploymentBase } from "../tooling/base-path.ts";12import { productionContentType } from "../tooling/pwa-validation.ts";1314const portFlag = Deno.args.findIndex((argument) => argument === "--port");15const port = portFlag >= 0 ? Number(Deno.args[portFlag + 1]) : 4321;16if (!Number.isInteger(port) || port < 1 || port > 65_535) {17 console.error("error: preview --port must be an integer from 1 to 65535");18 Deno.exit(2);19}2021let identity: { lofiVersion?: string; sourceHash?: string; basePath?: string };22try {23 identity = JSON.parse(await Deno.readTextFile("dist/lofi-build.json"));24} catch {25 console.error("error: production build is missing or stale; run `deno task build` first");26 Deno.exit(1);27}28if (!identity.sourceHash) {29 console.error("error: production build identity is invalid; run `deno task build` again");30 Deno.exit(1);31}32const basePath = normalizeDeploymentBase(identity.basePath);3334console.log(35 `lofi preview: http://127.0.0.1:${port}${basePath} (build ${identity.sourceHash}, @nzip/lofi ${identity.lofiVersion})`,36);37Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {38 const url = new URL(request.url);39 if (basePath !== "/" && url.pathname === basePath.slice(0, -1)) {40 return Response.redirect(new URL(basePath, url.origin), 308);41 }42 const scopedPath = pathWithinDeploymentBase(url.pathname, basePath);43 if (scopedPath === null) return new Response("Not found", { status: 404 });44 let pathname = decodeURIComponent(scopedPath);45 if (pathname === "" || pathname.endsWith("/")) pathname += "index.html";46 const relative = normalize(pathname).replace(/^[/\\]+/, "");47 if (relative.startsWith("..")) return new Response("Not found", { status: 404 });48 const path = join("dist", relative);49 try {50 const body = await Deno.readFile(path);51 return new Response(request.method === "HEAD" ? null : body, {52 headers: { "content-type": productionContentType(path) },53 });54 } catch (error) {55 if (error instanceof Deno.errors.NotFound) return new Response("Not found", { status: 404 });56 throw error;57 }58});