Public: anyone can read this; the crew can change it.
forge.json's app.looks lists every top-level page that answers on main today #2785
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/2783-forge-json-s-app-looks-lists-every-top-l → main 3 commits fork 1 thread · 1 unresolved
2 files
changed +103
−0
forge.json +1
| @@ -3,6 +3,7 @@ | ||
| 3 | 3 | "api.anthropic.com" |
| 4 | 4 | ], |
| 5 | 5 | "app": { |
| 6 | "looks": ["/", "/forges", "/family", "/front", "/explore", "/board", "/boards", "/asked", "/search", "/new", "/audit", "/releases", "/apps", "/migrate", "/map", "/flow", "/system", "/contribute", "/resilience", "/settings"], | |
| 6 | 7 | "run": "bun run src/main.ts", |
| 7 | 8 | "install": "", |
| 8 | 9 | "health": "/healthz", |
test/forge-looks.test.ts added +102
| @@ -0,0 +1,102 @@ | ||
| 1 | /** | |
| 2 | * forge.json's `app.looks` pictures the forge's own top-level pages (#2783): | |
| 3 | * every page the router answers at one path segment is in the list, or in | |
| 4 | * NOT_A_PAGE with the reason it is not worth a picture; each listed path | |
| 5 | * answers 200 to an admin. | |
| 6 | */ | |
| 7 | ||
| 8 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 9 | import { readFileSync } from "node:fs"; | |
| 10 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 11 | import { tmpdir } from "node:os"; | |
| 12 | import { join } from "node:path"; | |
| 13 | import { createApp, type App } from "../src/app.ts"; | |
| 14 | import { createToken } from "../src/core/tokens.ts"; | |
| 15 | import { createUser } from "../src/core/users.ts"; | |
| 16 | import { buildRouter, serve, type Servers } from "../src/http/server.ts"; | |
| 17 | import { quietLogs } from "./helpers/globals.ts"; | |
| 18 | ||
| 19 | quietLogs(); | |
| 20 | ||
| 21 | /** Top-level routes that are not pages, or are not worth a picture, and why. */ | |
| 22 | const NOT_A_PAGE: Record<string, string> = { | |
| 23 | "/llms.txt": "plain markdown for machines, not a page to picture", | |
| 24 | "/robots.txt": "plain text for crawlers", | |
| 25 | "/releases.json": "JSON feed of /releases", | |
| 26 | "/sitemap.xml": "XML for crawlers", | |
| 27 | "/manifest.webmanifest": "the install manifest, JSON", | |
| 28 | "/sw.js": "the service worker script", | |
| 29 | "/audit.csv": "a CSV download of /audit", | |
| 30 | "/claim": "answers 404 to an admin: it exists only for a link-holder", | |
| 31 | "/first-admin": "the setup form for a forge with no admin yet", | |
| 32 | "/signin": "a sign-in form, not a page of the forge", | |
| 33 | "/signup": "a sign-up form, not a page of the forge", | |
| 34 | "/recover": "an account-recovery form, not a page of the forge", | |
| 35 | "/verify": "answers 400 without the emailed code", | |
| 36 | "/logout": "a mutation, answers 404 to a GET", | |
| 37 | "/theme": "a mutation, answers 404 to a GET", | |
| 38 | "/tell": "answers 404 to a GET", | |
| 39 | "/asks": "redirects to /#needs-you", | |
| 40 | "/big": "redirects to /board?wall", | |
| 41 | "/mcp": "a protocol endpoint, answers 405 to a GET", | |
| 42 | "/_measure": "the page-load beacon, a POST endpoint", | |
| 43 | "/events": "a live event stream that never ends", | |
| 44 | }; | |
| 45 | ||
| 46 | let dir: string; | |
| 47 | let app: App; | |
| 48 | let servers: Servers; | |
| 49 | let token: string; | |
| 50 | ||
| 51 | beforeAll(async () => { | |
| 52 | dir = await mkdtemp(join(tmpdir(), "forge-looks-")); | |
| 53 | app = createApp({ dataDir: dir, port: 0 }); | |
| 54 | servers = await serve(app); | |
| 55 | const r = await createUser(app.db, { | |
| 56 | origin: "seed", | |
| 57 | trusted: true, | |
| 58 | username: "alice", | |
| 59 | email: "[email protected]", | |
| 60 | password: "a-good-passphrase", | |
| 61 | adminIfFirst: true, | |
| 62 | }); | |
| 63 | if (!r.ok) throw new Error(r.error); | |
| 64 | token = createToken(app.db, r.value.id, "t", "admin").token; | |
| 65 | }); | |
| 66 | ||
| 67 | afterAll(async () => { | |
| 68 | servers.stop(); | |
| 69 | app.close(); | |
| 70 | await rm(dir, { recursive: true, force: true }); | |
| 71 | }); | |
| 72 | ||
| 73 | const topLevel = () => | |
| 74 | buildRouter() | |
| 75 | .patterns() | |
| 76 | .filter((p) => p === "/" || /^\/[^/:*]+$/.test(p)); | |
| 77 | ||
| 78 | const declared = (): string[] => | |
| 79 | JSON.parse(readFileSync(join(import.meta.dir, "..", "forge.json"), "utf8")).app.looks; | |
| 80 | ||
| 81 | const get = (path: string) => | |
| 82 | fetch(`${servers.url}${path}`, { | |
| 83 | redirect: "manual", | |
| 84 | headers: { authorization: `Bearer ${token}` }, | |
| 85 | }); | |
| 86 | ||
| 87 | test("app.looks is the router's top-level pages minus the not-a-page list", () => { | |
| 88 | const pages = topLevel().filter((p) => !(p in NOT_A_PAGE)); | |
| 89 | expect([...declared()].sort()).toEqual([...pages].sort()); | |
| 90 | }); | |
| 91 | ||
| 92 | test("every not-a-page entry is a real route with a reason", () => { | |
| 93 | const routes = new Set(topLevel()); | |
| 94 | for (const [path, why] of Object.entries(NOT_A_PAGE)) { | |
| 95 | expect(routes.has(path)).toBe(true); | |
| 96 | expect(why.length).toBeGreaterThan(10); | |
| 97 | } | |
| 98 | }); | |
| 99 | ||
| 100 | test("every listed path answers 200 to an admin", async () => { | |
| 101 | for (const p of declared()) expect([p, (await get(p)).status]).toEqual([p, 200]); | |
| 102 | }); |
new above
offline
Nit: 20 sequential fetches. Promise.all would keep this fast as the list grows toward the cap.