Public: anyone can read this; the crew can change it.

GET /openapi.json at the root redirects to /contract/openapi.json, as /api/v1/openapi.json does #8420

Merged 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[] = [
9898 "apple-touch-icon.png",
9999 "apple-touch-icon-precomposed.png",
100100 "llms.txt",
101 "openapi.json",
101102 "runs",
102103 "search",
103104 "settings",
src/http/server.ts +6
@@ -726,6 +726,12 @@ export function buildRouter(): Router<Handler> {
726726 router.add("GET", "/llms.txt", llmsTxt);
727727 router.add("GET", "/.well-known/change-password", changePassword);
728728 router.add("GET", "/robots.txt", robotsTxt);
729 // The conventional path an agent guesses, answered as /api/v1/openapi.json is.
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 );
729735 router.add("GET", "/releases.json", releasesJson);
730736 router.add("GET", "/sitemap.xml", sitemapXml);
731737 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");
1111
1212 /** Routes that answer a file, a fragment, a redirect hop or a callback. */
1313 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\/)/;
1515
1616 const getRoutes = [
1717 ...server.matchAll(/router\.add\(\s*"GET",\s*"([^"]+)"/g),
test/forge-looks.test.ts +1
@@ -22,6 +22,7 @@ quietLogs();
2222 const NOT_A_PAGE: Record<string, string> = {
2323 "/llms.txt": "plain markdown for machines, not a page to picture",
2424 "/robots.txt": "plain text for crawlers",
25 "/openapi.json": "a redirect to the contract document",
2526 "/favicon.ico": "the tab icon, an image",
2627 "/releases.json": "JSON feed of /releases",
2728 "/sitemap.xml": "XML for crawlers",
test/root-openapi.test.ts added +27
@@ -0,0 +1,27 @@
1 /** #8330: /openapi.json at the root redirects to the contract, as /api/v1/openapi.json does. */
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([
4545 "/big",
4646 "/flow",
4747 "/login",
48 "/openapi.json",
4849 "/settings/origins/callback",
4950 "/settings/resilience",
5051 "/sign-in",

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs