From 31c91fb307a2536e69347b9d192ece4f21713b81 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Mon, 27 Jul 2026 15:50:01 -0500 Subject: [PATCH] docs(website): document the OpenAPI path-key normalization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matches website PR "build(swagger): normalize and sort generated OpenAPI path keys", which post-processes swagger-autogen's output ahead of the admin router domain split (API_V2_PLAN.md § Phase 2). - website-README.md § Regenerating the spec: why trailing slashes are stripped (a capability router mounted at /users declaring router.get('/') would document /api/v1/admin/users/, a URL no client calls) and why path keys are sorted. - BACKEND_DESIGN.md § generated artifacts: note that both the route manifest and the spec are emitted sorted, so a diff in either is proportional to the change. Co-Authored-By: Claude --- website/BACKEND_DESIGN.md | 5 +++++ website/website-README.md | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/website/BACKEND_DESIGN.md b/website/BACKEND_DESIGN.md index 7e732d5..6318e63 100644 --- a/website/BACKEND_DESIGN.md +++ b/website/BACKEND_DESIGN.md @@ -278,6 +278,11 @@ it churns whenever a description is reworded — it documents *intent*. The mani derived and records *reality*, which is why it, not Swagger, is the thing PR checks freeze (`npm run routes:manifest -- --check`). +Both artifacts are emitted with **sorted** keys, so a diff in either is proportional to the change +rather than to how the routers happen to be traversed. `swagger.js` additionally strips trailing +slashes from generated path keys — see *Regenerating the spec* in the website README for why the +domain split makes that necessary. + Scope: the manifest keeps `/api/**` and `/.well-known/**` from the public app plus everything on the internal listener. The SPA catch-all, `/uploads` and `/brand` are filesystem-conditional static mounts — not API contract, and including them would make the output depend on whether CI had built diff --git a/website/website-README.md b/website/website-README.md index 7ac7c10..527e558 100644 --- a/website/website-README.md +++ b/website/website-README.md @@ -367,6 +367,18 @@ cd server npm run swagger # → server/swagger/swagger-output.json ``` +`swagger.js` post-processes the generator's output in two ways before writing it: + +- **Trailing slashes are stripped from path keys.** swagger-autogen builds a path by + string-concatenating the mount prefix with the route argument, so a capability router mounted at + `/users` whose collection route is `router.get('/')` would document as `/api/v1/admin/users/` — + a URL no client calls, while dropping the one they all do. Express is indifferent (non-strict + routing treats the two as one route), but the published spec is a contract. +- **Path keys are sorted.** The generator emits them in router-traversal order, so moving a route + between files rewrote most of this ~5k-line committed artifact even when the API was provably + unchanged. Sorting keeps the diff proportional to the change. OpenAPI attaches no meaning to path + order, and `scripts/routeManifest.js` already sorts for the same reason. + If the generated spec is missing, the server logs a warning and simply disables `/api/docs` (it does not crash).