Skip to content

Commit 9a574d4

Browse files
committed
docs
1 parent 726c420 commit 9a574d4

4 files changed

Lines changed: 108 additions & 9 deletions

File tree

nitro.config.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,8 @@ export default defineNitroConfig({
1717
description: "Minecraft avatars, skins, capes, renders, player lookup, formatting, and server status endpoints.",
1818
},
1919
ui: {
20-
scalar: {
21-
route: "/docs",
22-
// Use Nitro-generated OpenAPI document for docs.
23-
url: "/_openapi.json",
24-
spec: {
25-
url: "/_openapi.json",
26-
},
27-
},
20+
// Serve /docs from an app route so we can reliably point Scalar at /openapi.json.
21+
scalar: false,
2822
swagger: false,
2923
},
3024
},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nitrocraft",
3-
"version": "1.1.9",
3+
"version": "1.2.0",
44
"private": false,
55
"type": "module",
66
"scripts": {

src/routes/docs.get.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { getExternalBaseUrl } from "../utils/request";
2+
import { respond } from "../utils/response";
3+
4+
function escapeHtml(value: string): string {
5+
return value
6+
.replaceAll("&", "&")
7+
.replaceAll("<", "&lt;")
8+
.replaceAll(">", "&gt;")
9+
.replaceAll("\"", "&quot;")
10+
.replaceAll("'", "&#39;");
11+
}
12+
13+
function escapeAttributeJson(value: Record<string, unknown>): string {
14+
return escapeHtml(JSON.stringify(value));
15+
}
16+
17+
export default defineEventHandler((event) => {
18+
const baseUrl = getExternalBaseUrl(event);
19+
const canonicalUrl = `${baseUrl}/docs`;
20+
const openApiUrl = `${baseUrl}/openapi.json`;
21+
const scalarConfig = {
22+
url: openApiUrl,
23+
};
24+
25+
const html = `<!doctype html>
26+
<html lang="en">
27+
<head>
28+
<meta charset="utf-8">
29+
<meta name="viewport" content="width=device-width, initial-scale=1">
30+
<title>NitroCraft API Docs</title>
31+
<meta name="description" content="Interactive NitroCraft API reference powered by Scalar.">
32+
<link rel="canonical" href="${escapeHtml(canonicalUrl)}">
33+
<style>
34+
:root {
35+
color-scheme: light dark;
36+
}
37+
38+
body {
39+
margin: 0;
40+
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
41+
background: #0b1220;
42+
color: #e2e8f0;
43+
}
44+
45+
.docs-topbar {
46+
position: sticky;
47+
top: 0;
48+
z-index: 5;
49+
display: flex;
50+
align-items: center;
51+
justify-content: space-between;
52+
padding: 0.75rem 1rem;
53+
background: rgba(11, 18, 32, 0.92);
54+
border-bottom: 1px solid rgba(148, 163, 184, 0.25);
55+
backdrop-filter: blur(8px);
56+
}
57+
58+
.docs-topbar a {
59+
color: #93c5fd;
60+
text-decoration: none;
61+
font-weight: 600;
62+
}
63+
</style>
64+
</head>
65+
<body>
66+
<header class="docs-topbar">
67+
<a href="/">NitroCraft</a>
68+
<a href="${escapeHtml(openApiUrl)}">OpenAPI JSON</a>
69+
</header>
70+
<script id="api-reference" data-configuration="${escapeAttributeJson(scalarConfig)}"></script>
71+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
72+
</body>
73+
</html>`;
74+
75+
return respond(event, {
76+
status: 1,
77+
body: html,
78+
type: "text/html; charset=utf-8",
79+
cacheControl: "no-cache, max-age=0",
80+
});
81+
});

test/unit/routes-and-rate-limit.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,30 @@ test("metrics and openapi routes return expected payloads", async () => {
285285
assert.ok(String(spec.servers[0].url).startsWith("http"));
286286
});
287287

288+
test("docs route points Scalar to custom openapi schema", async () => {
289+
const route = (await import("../../src/routes/docs.get")).default;
290+
const originalExternalUrl = config.server.externalUrl;
291+
try {
292+
(config.server as any).externalUrl = "";
293+
const { event, res } = createMockEvent({
294+
url: "/docs",
295+
headers: {
296+
host: "nitrocraft.test",
297+
"x-forwarded-proto": "https",
298+
"x-forwarded-host": "nitrocraft.test",
299+
},
300+
});
301+
302+
const body = await route(event);
303+
assert.equal(res.statusCode, 200);
304+
assert.match(String(body), /NitroCraft API Docs/i);
305+
assert.match(String(body), /id="api-reference"/);
306+
assert.match(String(body), /https:\/\/nitrocraft\.test\/openapi\.json/);
307+
} finally {
308+
(config.server as any).externalUrl = originalExternalUrl;
309+
}
310+
});
311+
288312
test("server list builder page renders expected shell", async () => {
289313
const route = (await import("../../src/routes/tools/server-list.get")).default;
290314
const { event, res } = createMockEvent({

0 commit comments

Comments
 (0)