From a25028aa30bf0f3b89a9a7c99192e1a14267fc97 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Mon, 11 Sep 2023 14:52:07 +0200 Subject: Initial commit --- test/index.test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/index.test.ts (limited to 'test') diff --git a/test/index.test.ts b/test/index.test.ts new file mode 100644 index 0000000..d82cb9e --- /dev/null +++ b/test/index.test.ts @@ -0,0 +1,21 @@ +import { type Server } from "bun" +import { expect, test, beforeAll, afterAll } from "bun:test" + +import app from "../src/index" + +const port = 33000; +let server: Server + +beforeAll(async function () { + server = Bun.serve(Object.assign({}, app, { port })) +}) + +afterAll(function () { + server.stop() +}) + +test("/status returns 200 OK", async function () { + const res = await fetch(`http://localhost:${port}/status`) + expect(res.status).toBe(200) + expect(await res.text()).toBe("OK") +}) -- cgit 1.4.1 From 51cc4389f6dc7947ee34d1b3367876941e8a8fbc Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Mon, 11 Sep 2023 19:43:06 +0200 Subject: Serve static files --- README.md | 5 +++-- bun.lockb | Bin 2354 -> 2042 bytes package.json | 2 +- src/index.ts | 12 ++++++------ test/index.test.ts | 9 ++++++++- 5 files changed, 18 insertions(+), 10 deletions(-) (limited to 'test') diff --git a/README.md b/README.md index 3430625..34f3f42 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ ## Goals -1. To be a near-drop-in replacement for Zola -2. More indieweb features +1. Static web server with prometheus-based analytics +2. Dynamic web server capable of generating Zola-based websites +3. More indieweb features ## Installing diff --git a/bun.lockb b/bun.lockb index 2ab9dd9..f724266 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 96a8252..d0e61be 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,6 @@ }, "type": "module", "dependencies": { - "siopao": "^0.4.0" + "serve-static-bun": "^0.5.3" } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 8c0a3ba..59913b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ -import { withHtmlLiveReload } from "bun-html-live-reload"; -import Siopao from "siopao"; +import { withHtmlLiveReload } from "bun-html-live-reload" +import serveStatic from "serve-static-bun" -const router = new Siopao(); - -router.get("/status", () => new Response("OK")); +const dir = Bun.argv.length > 2 ? Bun.argv[Bun.argv.length - 1] : "./" export default withHtmlLiveReload({ - fetch: router.fetch.bind(router), + fetch: serveStatic(dir, { + dotfiles: "allow" + }), }) diff --git a/test/index.test.ts b/test/index.test.ts index d82cb9e..8a32e47 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -4,6 +4,7 @@ import { expect, test, beforeAll, afterAll } from "bun:test" import app from "../src/index" const port = 33000; +const base = `http://localhost:${port}/`; let server: Server beforeAll(async function () { @@ -15,7 +16,13 @@ afterAll(function () { }) test("/status returns 200 OK", async function () { - const res = await fetch(`http://localhost:${port}/status`) + const res = await fetch(new URL("/status", base)) expect(res.status).toBe(200) expect(await res.text()).toBe("OK") }) + +test("/ returns 200 and says Hello world", async function () { + const res = await fetch(base) + expect(res.status).toBe(200) + expect(await res.text()).toBe("Hello world") +}) -- cgit 1.4.1 From b45b4e37af14d94726b0c5d2691289886c0527cf Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Tue, 12 Sep 2023 10:56:10 +0200 Subject: Reformat with prettier --- src/index.ts | 10 +++++----- test/index.test.ts | 32 ++++++++++++++++---------------- 2 files changed, 21 insertions(+), 21 deletions(-) (limited to 'test') diff --git a/src/index.ts b/src/index.ts index 59913b3..4887dd6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ -import { withHtmlLiveReload } from "bun-html-live-reload" -import serveStatic from "serve-static-bun" +import { withHtmlLiveReload } from "bun-html-live-reload"; +import serveStatic from "serve-static-bun"; -const dir = Bun.argv.length > 2 ? Bun.argv[Bun.argv.length - 1] : "./" +const dir = Bun.argv.length > 2 ? Bun.argv[Bun.argv.length - 1] : "./"; export default withHtmlLiveReload({ fetch: serveStatic(dir, { - dotfiles: "allow" + dotfiles: "allow", }), -}) +}); diff --git a/test/index.test.ts b/test/index.test.ts index 8a32e47..234bd1e 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,28 +1,28 @@ -import { type Server } from "bun" -import { expect, test, beforeAll, afterAll } from "bun:test" +import { type Server } from "bun"; +import { expect, test, beforeAll, afterAll } from "bun:test"; -import app from "../src/index" +import app from "../src/index"; const port = 33000; const base = `http://localhost:${port}/`; -let server: Server +let server: Server; beforeAll(async function () { - server = Bun.serve(Object.assign({}, app, { port })) -}) + server = Bun.serve(Object.assign({}, app, { port })); +}); afterAll(function () { - server.stop() -}) + server.stop(); +}); test("/status returns 200 OK", async function () { - const res = await fetch(new URL("/status", base)) - expect(res.status).toBe(200) - expect(await res.text()).toBe("OK") -}) + const res = await fetch(new URL("/status", base)); + expect(res.status).toBe(200); + expect(await res.text()).toBe("OK"); +}); test("/ returns 200 and says Hello world", async function () { - const res = await fetch(base) - expect(res.status).toBe(200) - expect(await res.text()).toBe("Hello world") -}) + const res = await fetch(base); + expect(res.status).toBe(200); + expect(await res.text()).toBe("Hello world"); +}); -- cgit 1.4.1 From 2f6152539c540697290ec73a7c1b50b9f2db88c6 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Tue, 12 Sep 2023 16:54:29 +0200 Subject: Use own logic for static file serving --- bun.lockb | Bin 2386 -> 3923 bytes package.json | 3 ++- src/index.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++++++----- test/index.test.ts | 12 ++++----- 4 files changed, 72 insertions(+), 14 deletions(-) (limited to 'test') diff --git a/bun.lockb b/bun.lockb index c8b2651..e8b79d3 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 27f95e0..0eb65a3 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "homestead", "module": "src/index.ts", "devDependencies": { + "@types/contains-path": "^1.0.2", "bun-html-live-reload": "^0.1.1", "bun-types": "latest" }, @@ -10,7 +11,7 @@ }, "type": "module", "dependencies": { - "serve-static-bun": "^0.5.3", + "contains-path": "^1.0.0", "toml": "^3.0.0" } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index d8c9bf1..ec1c296 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,75 @@ import path from "node:path"; +import fs, { Stats } from "node:fs"; +import fsp from "node:fs/promises"; +import util from "node:util"; + import { withHtmlLiveReload } from "bun-html-live-reload"; -import serveStatic from "serve-static-bun"; +import containsPath from "contains-path"; import readConfig from "./config"; -const base = Bun.argv.length > 2 ? Bun.argv[Bun.argv.length - 1] : "."; +const base = "../website/"; +const publicDir = path.resolve(base, "public") + path.sep; const config = readConfig(base); +const defaultHeaders = config.extra.headers; + +function getFilename(name: string): string { + return path.join(publicDir, `${name}`); +} + +let files: Map = new Map(); + +function registerFile(pathname: string, absPath: string, stat: Stats): void { + pathname = "/" + (pathname === "." || pathname === "./" ? "" : pathname); + + if (files.get(pathname) !== undefined) { + console.warn("File already registered:", pathname); + } + files.set(pathname, { + filename: absPath, + size: stat.size, + mtime: stat.mtime.toUTCString(), + }); +} + +function walkDirectory(root: string, dir: string) { + const absDir = path.join(root, dir); + for (let pathname of fs.readdirSync(absDir)) { + const relPath = path.join(dir, pathname); + const absPath = path.join(absDir, pathname); + const stat = fs.statSync(absPath); + if (stat.isDirectory()) { + walkDirectory(root, relPath + path.sep); + } else if (stat.isFile()) { + if (pathname === "index.html") { + registerFile(path.dirname(relPath), absPath, stat); + registerFile(path.dirname(relPath) + path.sep, absPath, stat); + } + registerFile(relPath, absPath, stat); + } + } +} + +walkDirectory(publicDir, ""); export default withHtmlLiveReload({ - fetch: serveStatic(path.join(base, "public"), { - headers: config.extra.headers, - dotfiles: "allow", - }), + fetch: async function (request) { + const pathname = new URL(request.url).pathname; + if (files.has(pathname)) { + const file = files.get(pathname); + console.info("filename", file.filename); + return new Response(Bun.file(file.filename), { + headers: defaultHeaders, + status: 200, + }); + } + return new Response(Bun.file(getFilename("404.html")), { + headers: Object.assign({}, defaultHeaders, { + "cache-control": "max-age=5, no-cache", + }), + status: 404, + statusText: "Not Found", + }); + }, }); diff --git a/test/index.test.ts b/test/index.test.ts index 234bd1e..c1f5f64 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -15,14 +15,12 @@ afterAll(function () { server.stop(); }); -test("/status returns 200 OK", async function () { - const res = await fetch(new URL("/status", base)); +test("/ returns 200", async function () { + const res = await fetch(base); expect(res.status).toBe(200); - expect(await res.text()).toBe("OK"); }); -test("/ returns 200 and says Hello world", async function () { - const res = await fetch(base); - expect(res.status).toBe(200); - expect(await res.text()).toBe("Hello world"); +test("/asdf returns 404", async function () { + const res = await fetch(`${base}asdf`); + expect(res.status).toBe(404); }); -- cgit 1.4.1 From 2574e38f6241bfa4dd5193fcb636e8b76f5c6437 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Wed, 13 Sep 2023 14:46:08 +0200 Subject: Add tests --- test/index.test.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) (limited to 'test') diff --git a/test/index.test.ts b/test/index.test.ts index c1f5f64..2f682a9 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -24,3 +24,74 @@ test("/asdf returns 404", async function () { const res = await fetch(`${base}asdf`); expect(res.status).toBe(404); }); + +test("/ returns 304 with newer if-modified-since header", async function () { + const res = await fetch(base, { + headers: { + "if-modified-since": new Date().toUTCString(), + }, + }); + expect(res.status).toBe(304); + expect(res.headers.get("vary")).toBe("Accept-Encoding"); +}); + +test("/ returns 200 with older if-modified-since header", async function () { + const res = await fetch(base, { + headers: { + "if-modified-since": new Date(0).toUTCString(), + }, + }); + expect(res.status).toBe(200); +}); + +test("/ returns gzipped content with accept-encoding: gzip", async function () { + const res = await fetch(base, { + headers: { + "accept-encoding": "gzip", + }, + }); + expect(res.status).toBe(200); + // Bun 0.8.1 this doesn't work, but `verbose` shows it's there + // expect(res.headers.get("content-encoding")).toBe("gzip"); + // response is automatically gunzipped + const body = await res.text(); + expect(body.length).toBeGreaterThan( + Number(res.headers.get("content-length")), + ); +}); + +test("/ returns uncompressed content with accept-encoding: identity", async function () { + const res = await fetch(base, { + headers: { + "accept-encoding": "identity", + }, + }); + expect(res.status).toBe(200); + const body = await res.text(); + expect(body.length).toBe(Number(res.headers.get("content-length"))); +}); + +test("/ returns brotli-compressed content with accept-encoding: br", async function () { + const res = await fetch(base, { + headers: { + "accept-encoding": "br", + }, + }); + expect(res.status).toBe(200); + expect(res.headers.get("content-encoding")).toBe("br"); + const body = await res.text(); + expect(body.length).toBeLessThan(Number(res.headers.get("content-length"))); +}); + +test("/ returns zstd-compressed content with accept-encoding: zstd", async function () { + const res = await fetch(base, { + headers: { + "accept-encoding": "zstd", + }, + }); + expect(res.status).toBe(200); + expect(res.headers.get("content-encoding")).toBe("zstd"); + expect(res.headers.get("vary")).toBe("Accept-Encoding"); + const body = await res.text(); + expect(body.length).toBeLessThan(Number(res.headers.get("content-length"))); +}); -- cgit 1.4.1