import { join, posix } from "../deps/path.ts"; import { merge } from "../core/utils/object.ts"; import type { Middleware } from "../core/server.ts"; export interface Options { /** The root folder to look for the 404 page */ root: string; /** The path to the 404 page */ page404: string; /** Show the directory index if the page404 is not found */ directoryIndex?: boolean; } export const defaults: Options = { root: `${Deno.cwd()}/_site`, page404: "/404.html", directoryIndex: false, }; /** Show a 404 page */ export function notFound(userOptions?: Partial): Middleware { const options = merge(defaults, userOptions); let { root, page404, directoryIndex } = options; if (page404.endsWith("/")) { page404 += "index.html"; } return async (request, next) => { const response = await next(request); if (response.status === 404) { const { headers, status } = response; headers.set("content-type", "text/html; charset=utf-8"); try { const body = await Deno.readFile(join(root, page404)); return new Response(body, { status, headers }); } catch { if (directoryIndex) { const { pathname } = new URL(request.url); const body = await getDirectoryIndex(root, pathname); return new Response(body, { status, headers }); } } } return response; }; } /** Generate the default body for a 404 response */ async function getDirectoryIndex(root: string, file: string): Promise { const folders: [string, string][] = []; const files: [string, string][] = []; const folderIcon = ``; const fileIcon = ``; try { for await (const info of Deno.readDir(join(root, file))) { info.isDirectory ? folders.push([`${info.name}/`, `${folderIcon} ${info.name}/`]) : files.push([ info.name === "index.html" ? "./" : info.name, `${fileIcon} ${info.name}`, ]); } } catch { // It's not a directory, so scan the parent directory try { const base = posix.dirname(file); for await (const info of Deno.readDir(join(root, base))) { info.isDirectory ? folders.push([ posix.join(base, `${info.name}/`), `${folderIcon} ${info.name}/`, ]) : files.push([ posix.join(base, info.name === "index.html" ? "./" : info.name), `${fileIcon} ${info.name}`, ]); } } catch { // Ignore } } const content = folders.concat(files); if (file.match(/.+\/.+/)) { content.unshift(["../", ".."]); } return ` 404 - Not found

404 - Not found

The URL ${file} does not exist

`; } export default notFound;