GET /openapi.json at the root redirects to /contract/openapi.json, as /api/v1/openapi.json does #8422
Merged
scoutcrew
opened this
Check · Review · Land · Live done
— Landed and live in production.
Merged
scout/forge:scout/8330-get-openapi-json-at-the-root-redirects-t → main
·
3 commits
·fork
-
Merged.
scout/8330-get-openapi-json-at-the-root-redirects-t was merged into
main as 1f3c7a6.
Review
Sign in to review this change.
6 files
changed +37
−1
src/core/reserved.ts
+1
|
|
@@ -98,6 +98,7 @@ export const SYSTEM_NAMES: readonly string[] = [ |
| 98 | 98 |
"apple-touch-icon.png", |
| 99 | 99 |
"apple-touch-icon-precomposed.png", |
| 100 | 100 |
"llms.txt", |
| 101 |
"openapi.json", |
| 101 | 102 |
"runs", |
| 102 | 103 |
"search", |
| 103 | 104 |
"settings", |
src/http/server.ts
+6
|
|
@@ -726,6 +726,12 @@ export function buildRouter(): Router<Handler> { |
| 726 | 726 |
router.add("GET", "/llms.txt", llmsTxt); |
| 727 | 727 |
router.add("GET", "/.well-known/change-password", changePassword); |
| 728 | 728 |
router.add("GET", "/robots.txt", robotsTxt); |
| 729 |
|
| 730 |
router.add( |
| 731 |
"GET", |
| 732 |
"/openapi.json", |
| 733 |
() => new Response(null, { status: 308, headers: { location: "/contract/openapi.json", "cache-control": "public, max-age=86400" } }), |
| 734 |
); |
| 729 | 735 |
router.add("GET", "/releases.json", releasesJson); |
| 730 | 736 |
router.add("GET", "/sitemap.xml", sitemapXml); |
| 731 | 737 |
router.add("GET", "/manifest.webmanifest", webManifest); |
test/design-audit.test.ts
+1
−1
|
|
@@ -11,7 +11,7 @@ const doc = readFileSync("docs/design/audit.md", "utf8"); |
| 11 | 11 |
|
| 12 | 12 |
|
| 13 | 13 |
const NOT_PAGES = |
| 14 | |
/^\/(assets\/|\.well-known\/|icons\/|og\/|live\/|__forge\/|go\/|events$|system\/stream$|talk\/stream$|settings\/teams\/stream$|sw\.js$|robots\.txt$|favicon\.ico$|apple-touch-icon(-precomposed)?\.png$|sitemap\.xml$|manifest\.webmanifest$|opensearch\.xml$|llms\.txt$|releases\.json$|audit\.csv$|fleet\/graph\/live$|fleet\/graph\.csv$|settings\/origins\/callback$|home\/archived\/|settings\/backups\/live$|api\/v1\/)/; |
| 14 |
/^\/(assets\/|\.well-known\/|icons\/|og\/|live\/|__forge\/|go\/|events$|system\/stream$|talk\/stream$|settings\/teams\/stream$|sw\.js$|robots\.txt$|favicon\.ico$|apple-touch-icon(-precomposed)?\.png$|sitemap\.xml$|manifest\.webmanifest$|opensearch\.xml$|llms\.txt$|openapi\.json$|releases\.json$|audit\.csv$|fleet\/graph\/live$|fleet\/graph\.csv$|settings\/origins\/callback$|home\/archived\/|settings\/backups\/live$|api\/v1\/)/; |
| 15 | 15 |
|
| 16 | 16 |
const getRoutes = [ |
| 17 | 17 |
...server.matchAll(/router\.add\(\s*"GET",\s*"([^"]+)"/g), |
test/forge-looks.test.ts
+1
|
|
@@ -22,6 +22,7 @@ quietLogs(); |
| 22 | 22 |
const NOT_A_PAGE: Record<string, string> = { |
| 23 | 23 |
"/llms.txt": "plain markdown for machines, not a page to picture", |
| 24 | 24 |
"/robots.txt": "plain text for crawlers", |
| 25 |
"/openapi.json": "a redirect to the contract document", |
| 25 | 26 |
"/favicon.ico": "the tab icon, an image", |
| 26 | 27 |
"/releases.json": "JSON feed of /releases", |
| 27 | 28 |
"/sitemap.xml": "XML for crawlers", |
test/root-openapi.test.ts
added
+27
|
|
@@ -0,0 +1,27 @@ |
| 1 |
|
| 2 |
import { afterAll, beforeAll, expect, test } from "bun:test"; |
| 3 |
import { mkdtemp, rm } from "node:fs/promises"; |
| 4 |
import { tmpdir } from "node:os"; |
| 5 |
import { join } from "node:path"; |
| 6 |
import { createApp } from "../src/app.ts"; |
| 7 |
import { serve, type Servers } from "../src/http/server.ts"; |
| 8 |
|
| 9 |
let dataDir: string; |
| 10 |
let servers: Servers; |
| 11 |
|
| 12 |
beforeAll(async () => { |
| 13 |
dataDir = await mkdtemp(join(tmpdir(), "forge-rootopenapi-")); |
| 14 |
servers = await serve(createApp({ dataDir, port: 0 })); |
| 15 |
}); |
| 16 |
afterAll(async () => { |
| 17 |
servers.stop(); |
| 18 |
await rm(dataDir, { recursive: true, force: true }); |
| 19 |
}); |
| 20 |
|
| 21 |
test("GET and HEAD /openapi.json answer 308 to the contract", async () => { |
| 22 |
for (const method of ["GET", "HEAD"]) { |
| 23 |
const res = await fetch(`${servers.url}/openapi.json`, { method, redirect: "manual" }); |
| 24 |
expect(res.status).toBe(308); |
| 25 |
expect(res.headers.get("location")).toBe("/contract/openapi.json"); |
| 26 |
} |
| 27 |
}); |
test/server-timing-sweep.test.ts
+1
|
|
@@ -45,6 +45,7 @@ const REDIRECTS = new Set([ |
| 45 | 45 |
"/big", |
| 46 | 46 |
"/flow", |
| 47 | 47 |
"/login", |
| 48 |
"/openapi.json", |
| 48 | 49 |
"/settings/origins/callback", |
| 49 | 50 |
"/settings/resilience", |
| 50 | 51 |
"/sign-in", |