Skip to content

Commit 2d322fd

Browse files
authored
fix static .html serving (#68)
1 parent 5dd0bac commit 2d322fd

File tree

10 files changed

+851
-134
lines changed

10 files changed

+851
-134
lines changed

bun.lock

Lines changed: 287 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/documentation/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"@types/react-dom": "19.2.3",
6868
"@types/rss": "^0.0.32",
6969
"@vitejs/plugin-react": "5.1.2",
70+
"rollup": "^4.60.0",
7071
"tailwindcss": "4.1.18",
7172
"typescript": "5.9.3",
7273
"vite-plugin-svgr": "4.5.0",

packages/documentation/src/components/navigation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const footerItems = {
5252
resources: [
5353
{
5454
children: "Privacy Policy",
55-
href: "https://the-guild.dev/graphql/hive/privacy-policy.html",
55+
href: "https://the-guild.dev/graphql/hive/privacy-policy",
5656
title: "Privacy Policy",
5757
},
5858
{

packages/documentation/src/router.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,7 @@ export function getRouter() {
3030
},
3131
routeTree,
3232
scrollRestoration: true,
33+
// matches website-router
34+
trailingSlash: "never",
3335
});
3436
}

packages/documentation/src/routes/docs/-$.test.ts

Lines changed: 94 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
*/
66
import { spawn, type Subprocess } from "bun";
77
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
8+
import { stat } from "node:fs/promises";
89
import { join } from "node:path";
910

1011
const TEST_PORT = 14_401;
1112
const BASE_URL = process.env["TEST_URL"] || `http://localhost:${TEST_PORT}`;
13+
const BUILD_FRESHNESS_WINDOW_MS = 20 * 60 * 1000;
1214
const CHANGELOG_CONTENT_TERM =
1315
process.env["DEPLOYMENT_CHANGELOG_CONTENT_TERM"] ??
1416
"SUPERTOKENS_ACCESS_TOKEN_KEY";
@@ -30,23 +32,45 @@ async function waitForServer(maxAttempts = 30): Promise<void> {
3032
throw new Error(`Server not ready after ${maxAttempts}s`);
3133
}
3234

35+
async function hasFreshBuildArtifacts(cwd: string): Promise<boolean> {
36+
const requiredArtifacts = [
37+
join(cwd, ".output/server/wrangler.json"),
38+
join(cwd, ".output/server/index.mjs"),
39+
join(cwd, ".output/public/graphql/hive/index.html"),
40+
];
41+
42+
let artifactStats: Awaited<ReturnType<typeof stat>>[];
43+
try {
44+
artifactStats = await Promise.all(
45+
requiredArtifacts.map((path) => stat(path)),
46+
);
47+
} catch {
48+
return false;
49+
}
50+
51+
const cutoffTime = Date.now() - BUILD_FRESHNESS_WINDOW_MS;
52+
return artifactStats.every((artifact) => artifact.mtimeMs >= cutoffTime);
53+
}
54+
3355
beforeAll(async () => {
3456
if (process.env["TEST_URL"]) return; // user-provided server
3557

3658
const cwd = join(import.meta.dir, "../../..");
37-
const build = spawn(["bun", "run", "build"], {
38-
cwd,
39-
env: {
40-
...process.env,
41-
NODE_ENV: "production",
42-
},
43-
stderr: "inherit",
44-
stdout: "inherit",
45-
});
59+
if (!(await hasFreshBuildArtifacts(cwd))) {
60+
const build = spawn(["bun", "run", "build"], {
61+
cwd,
62+
env: {
63+
...process.env,
64+
NODE_ENV: "production",
65+
},
66+
stderr: "inherit",
67+
stdout: "inherit",
68+
});
4669

47-
const exitCode = await build.exited;
48-
if (exitCode !== 0) {
49-
throw new Error(`Build failed with exit code ${exitCode}`);
70+
const exitCode = await build.exited;
71+
if (exitCode !== 0) {
72+
throw new Error(`Build failed with exit code ${exitCode}`);
73+
}
5074
}
5175

5276
devServer = spawn(
@@ -218,6 +242,44 @@ describe("Accept header negotiation", () => {
218242
});
219243
});
220244

245+
describe("prerendered HTML routing", () => {
246+
test("base-path route serves prerendered HTML without redirect", async () => {
247+
const res = await fetch(`${BASE_URL}/graphql/hive/docs/gateway`, {
248+
redirect: "manual",
249+
});
250+
251+
expect(res.status).toBe(200);
252+
expect(res.headers.get("content-type")).toContain("text/html");
253+
});
254+
255+
test("alias route serves prerendered HTML without redirect", async () => {
256+
const res = await fetch(`${BASE_URL}/docs/gateway`, {
257+
redirect: "manual",
258+
});
259+
260+
expect(res.status).toBe(200);
261+
expect(res.headers.get("content-type")).toContain("text/html");
262+
});
263+
264+
test("base-path trailing slash redirects to no-slash", async () => {
265+
const res = await fetch(`${BASE_URL}/graphql/hive/docs/gateway/`, {
266+
redirect: "manual",
267+
});
268+
269+
expect(res.status).toBe(307);
270+
expect(res.headers.get("location")).toBe("/graphql/hive/docs/gateway");
271+
});
272+
273+
test("alias trailing slash redirects to no-slash", async () => {
274+
const res = await fetch(`${BASE_URL}/docs/gateway/`, {
275+
redirect: "manual",
276+
});
277+
278+
expect(res.status).toBe(307);
279+
expect(res.headers.get("location")).toBe("/docs/gateway");
280+
});
281+
});
282+
221283
describe("deployment changelog", () => {
222284
test("renders changelog html with mdx code-block chrome", async () => {
223285
const res = await fetch(
@@ -241,6 +303,26 @@ describe("deployment changelog", () => {
241303
});
242304
});
243305

306+
describe("root routing", () => {
307+
test("aliased root serves landing page without redirect", async () => {
308+
const res = await fetch(`${BASE_URL}/`, {
309+
redirect: "manual",
310+
});
311+
312+
expect(res.status).toBe(200);
313+
expect(res.headers.get("content-type")).toContain("text/html");
314+
});
315+
316+
test("base-path trailing slash redirects to no-slash", async () => {
317+
const res = await fetch(`${BASE_URL}/graphql/hive/`, {
318+
redirect: "manual",
319+
});
320+
321+
expect(res.status).toBe(307);
322+
expect(res.headers.get("location")).toBe("/graphql/hive");
323+
});
324+
});
325+
244326
describe("404 handling", () => {
245327
test(".mdx extension returns notFound for non-existent page", async () => {
246328
const res = await fetch(`${BASE_URL}/docs/non-existent-xyz.mdx`, {

0 commit comments

Comments
 (0)