radiant-docs 0.1.59 → 0.1.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/astro.config.mjs +24 -2
- package/template/package-lock.json +121 -508
- package/template/package.json +3 -2
- package/template/scripts/generate-og-images.mjs +338 -6
- package/template/scripts/generate-og-metadata.mjs +29 -0
- package/template/src/components/Footer.astro +1 -1
- package/template/src/components/Header.astro +3 -10
- package/template/src/components/OpenApiPage.astro +181 -843
- package/template/src/components/SidebarGroup.astro +1 -1
- package/template/src/components/ThemeSwitcher.astro +5 -15
- package/template/src/components/chat/AssistantEmbedPanel.tsx +18 -7
- package/template/src/components/endpoint/PlaygroundBar.astro +54 -9
- package/template/src/components/endpoint/PlaygroundField.astro +1 -1
- package/template/src/components/endpoint/PlaygroundForm.astro +9 -5
- package/template/src/components/endpoint/RequestSnippets.astro +6 -1
- package/template/src/components/endpoint/ResponseFieldTree.astro +17 -13
- package/template/src/components/endpoint/ResponseFields.astro +4 -6
- package/template/src/components/endpoint/ResponseSnippets.astro +6 -1
- package/template/src/components/sidebar/SidebarEndpointLink.astro +9 -12
- package/template/src/components/sidebar/SidebarOpenApi.astro +3 -9
- package/template/src/components/ui/Field.astro +18 -15
- package/template/src/components/ui/Tag.astro +16 -2
- package/template/src/layouts/Layout.astro +6 -12
- package/template/src/lib/ai-artifacts.ts +792 -0
- package/template/src/lib/mdx/remark-resolve-internal-links.ts +22 -8
- package/template/src/lib/oas.ts +5 -1
- package/template/src/lib/openapi/operation-doc.ts +1150 -0
- package/template/src/lib/page-description.ts +20 -0
- package/template/src/lib/routes.ts +73 -18
- package/template/src/lib/utils.ts +11 -0
- package/template/src/pages/[...slug]/index.md.ts +35 -0
- package/template/src/pages/[...spec].json.ts +33 -0
- package/template/src/pages/[...spec].yaml.ts +33 -0
- package/template/src/pages/[...spec].yml.ts +33 -0
- package/template/src/pages/index.md.ts +17 -0
- package/template/src/pages/llms-full.txt.ts +11 -0
- package/template/src/pages/llms.txt.ts +11 -0
- package/template/src/styles/global.css +18 -15
- package/template/src/styles/vaul.css +0 -255
package/template/package.json
CHANGED
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"alpinejs": "^3.15.2",
|
|
48
48
|
"astro": "^5.16.4",
|
|
49
49
|
"astro-icon": "^1.1.5",
|
|
50
|
+
"fontkit": "^2.0.4",
|
|
50
51
|
"fs-extra": "^11.3.3",
|
|
51
52
|
"mdast-util-gfm": "^3.1.0",
|
|
52
53
|
"micromark-extension-gfm": "^3.0.0",
|
|
@@ -56,13 +57,13 @@
|
|
|
56
57
|
"preact": "^10.29.0",
|
|
57
58
|
"prism-themes": "^1.9.0",
|
|
58
59
|
"prismjs": "^1.30.0",
|
|
59
|
-
"radiant-docs-validator": "^0.1.
|
|
60
|
+
"radiant-docs-validator": "^0.1.20",
|
|
60
61
|
"rehype-autolink-headings": "^7.1.0",
|
|
61
62
|
"rehype-slug": "^6.0.0",
|
|
62
63
|
"remark-gfm": "^4.0.1",
|
|
63
64
|
"simple-git": "^3.30.0",
|
|
64
65
|
"tailwindcss": "^4.2.2",
|
|
65
|
-
"
|
|
66
|
+
"wawoff2": "^2.0.1",
|
|
66
67
|
"yaml": "^2.8.2",
|
|
67
68
|
"zod": "^3.25.76"
|
|
68
69
|
},
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { Resvg } from "@resvg/resvg-js";
|
|
5
|
+
import { create as createFont } from "fontkit";
|
|
6
|
+
import wawoff2 from "wawoff2";
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_TEXT_FONT_PRESET,
|
|
9
|
+
FONT_PRESET_OPTIONS,
|
|
10
|
+
} from "radiant-docs-validator";
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
4
13
|
|
|
5
14
|
const CWD = process.cwd();
|
|
6
15
|
const DIST_DIR = path.join(CWD, "dist");
|
|
@@ -11,7 +20,93 @@ const OG_WIDTH = 1200;
|
|
|
11
20
|
const OG_HEIGHT = 630;
|
|
12
21
|
const OG_BACKGROUND = "#171717";
|
|
13
22
|
const OG_PRIMARY_COLOR = "#737373";
|
|
14
|
-
const
|
|
23
|
+
const DEFAULT_OG_FONT_FAMILY = "DejaVu Sans, Arial, sans-serif";
|
|
24
|
+
const FONT_PRESET_ID_SET = new Set(FONT_PRESET_OPTIONS);
|
|
25
|
+
const OG_FONT_SUBSETS = ["latin", "latin-ext"];
|
|
26
|
+
const baseFontCache = new Map();
|
|
27
|
+
const weightedFontCache = new Map();
|
|
28
|
+
|
|
29
|
+
const FONT_PRESETS = {
|
|
30
|
+
"google-sans": {
|
|
31
|
+
family: "Google Sans Variable, DejaVu Sans, Arial, sans-serif",
|
|
32
|
+
fontsource: {
|
|
33
|
+
packageName: "@fontsource-variable/google-sans",
|
|
34
|
+
fileId: "google-sans",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
inter: {
|
|
38
|
+
family: "Inter Variable, DejaVu Sans, Arial, sans-serif",
|
|
39
|
+
fontsource: {
|
|
40
|
+
packageName: "@fontsource-variable/inter",
|
|
41
|
+
fileId: "inter",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
"inter-tight": {
|
|
45
|
+
family: "Inter Tight Variable, DejaVu Sans, Arial, sans-serif",
|
|
46
|
+
fontsource: {
|
|
47
|
+
packageName: "@fontsource-variable/inter-tight",
|
|
48
|
+
fileId: "inter-tight",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
geist: {
|
|
52
|
+
family: "Geist Variable, DejaVu Sans, Arial, sans-serif",
|
|
53
|
+
fontsource: {
|
|
54
|
+
packageName: "@fontsource-variable/geist",
|
|
55
|
+
fileId: "geist",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
"source-sans-3": {
|
|
59
|
+
family: "Source Sans 3 Variable, DejaVu Sans, Arial, sans-serif",
|
|
60
|
+
fontsource: {
|
|
61
|
+
packageName: "@fontsource-variable/source-sans-3",
|
|
62
|
+
fileId: "source-sans-3",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"source-serif-4": {
|
|
66
|
+
family: "Source Serif 4 Variable, DejaVu Serif, Georgia, serif",
|
|
67
|
+
fontsource: {
|
|
68
|
+
packageName: "@fontsource-variable/source-serif-4",
|
|
69
|
+
fileId: "source-serif-4",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
"public-sans": {
|
|
73
|
+
family: "Public Sans Variable, DejaVu Sans, Arial, sans-serif",
|
|
74
|
+
fontsource: {
|
|
75
|
+
packageName: "@fontsource-variable/public-sans",
|
|
76
|
+
fileId: "public-sans",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
"geist-mono": {
|
|
80
|
+
family: "Geist Mono Variable, DejaVu Sans Mono, monospace",
|
|
81
|
+
fontsource: {
|
|
82
|
+
packageName: "@fontsource-variable/geist-mono",
|
|
83
|
+
fileId: "geist-mono",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
"jetbrains-mono": {
|
|
87
|
+
family: "JetBrains Mono Variable, DejaVu Sans Mono, monospace",
|
|
88
|
+
fontsource: {
|
|
89
|
+
packageName: "@fontsource-variable/jetbrains-mono",
|
|
90
|
+
fileId: "jetbrains-mono",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
"google-sans-code": {
|
|
94
|
+
family: "Google Sans Code Variable, DejaVu Sans Mono, monospace",
|
|
95
|
+
fontsource: {
|
|
96
|
+
packageName: "@fontsource-variable/google-sans-code",
|
|
97
|
+
fileId: "google-sans-code",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
"system-sans": {
|
|
101
|
+
family: DEFAULT_OG_FONT_FAMILY,
|
|
102
|
+
},
|
|
103
|
+
"system-serif": {
|
|
104
|
+
family: "DejaVu Serif, Georgia, serif",
|
|
105
|
+
},
|
|
106
|
+
"system-mono": {
|
|
107
|
+
family: "DejaVu Sans Mono, monospace",
|
|
108
|
+
},
|
|
109
|
+
};
|
|
15
110
|
|
|
16
111
|
function trimToUndefined(value) {
|
|
17
112
|
return typeof value === "string" && value.trim().length > 0
|
|
@@ -19,6 +114,78 @@ function trimToUndefined(value) {
|
|
|
19
114
|
: undefined;
|
|
20
115
|
}
|
|
21
116
|
|
|
117
|
+
function normalizeFontPresetId(value) {
|
|
118
|
+
if (typeof value !== "string") return undefined;
|
|
119
|
+
const normalized = value.trim().toLowerCase();
|
|
120
|
+
return FONT_PRESET_ID_SET.has(normalized) ? normalized : undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getFontPreset(presetId) {
|
|
124
|
+
return FONT_PRESETS[presetId] ?? FONT_PRESETS[DEFAULT_TEXT_FONT_PRESET];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function getBaseFontsourceFont(fontsource, subset) {
|
|
128
|
+
const cacheKey = `${fontsource.packageName}:${subset}`;
|
|
129
|
+
const cachedFont = baseFontCache.get(cacheKey);
|
|
130
|
+
if (cachedFont) return cachedFont;
|
|
131
|
+
|
|
132
|
+
const fontPath = require.resolve(
|
|
133
|
+
`${fontsource.packageName}/files/${fontsource.fileId}-${subset}-wght-normal.woff2`,
|
|
134
|
+
);
|
|
135
|
+
const woff2Buffer = fs.readFileSync(fontPath);
|
|
136
|
+
const ttfBuffer = Buffer.from(await wawoff2.decompress(woff2Buffer));
|
|
137
|
+
const font = createFont(ttfBuffer);
|
|
138
|
+
baseFontCache.set(cacheKey, font);
|
|
139
|
+
return font;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getWeightedFont(font, fontsource, subset, fontWeight) {
|
|
143
|
+
const cacheKey = `${fontsource.packageName}:${subset}:${fontWeight}`;
|
|
144
|
+
const cachedFont = weightedFontCache.get(cacheKey);
|
|
145
|
+
if (cachedFont) return cachedFont;
|
|
146
|
+
|
|
147
|
+
const weightAxis = font.variationAxes?.wght;
|
|
148
|
+
const weightedFont = weightAxis
|
|
149
|
+
? font.getVariation({
|
|
150
|
+
wght: Math.max(weightAxis.min, Math.min(weightAxis.max, fontWeight)),
|
|
151
|
+
})
|
|
152
|
+
: font;
|
|
153
|
+
|
|
154
|
+
weightedFontCache.set(cacheKey, weightedFont);
|
|
155
|
+
return weightedFont;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function getFontsourceFonts(fontsource, fontWeight) {
|
|
159
|
+
if (!fontsource) return [];
|
|
160
|
+
|
|
161
|
+
const fonts = [];
|
|
162
|
+
for (const subset of OG_FONT_SUBSETS) {
|
|
163
|
+
const font = await getBaseFontsourceFont(fontsource, subset);
|
|
164
|
+
fonts.push(getWeightedFont(font, fontsource, subset, fontWeight));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return fonts;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function getFontRenderer(presetId, fontWeight) {
|
|
171
|
+
const fontPreset = getFontPreset(presetId);
|
|
172
|
+
return {
|
|
173
|
+
family: fontPreset.family,
|
|
174
|
+
fonts: await getFontsourceFonts(fontPreset.fontsource, fontWeight),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function resolveOgFontConfig(fonts) {
|
|
179
|
+
const textFontId =
|
|
180
|
+
normalizeFontPresetId(fonts?.text) ?? DEFAULT_TEXT_FONT_PRESET;
|
|
181
|
+
const headingFontId = normalizeFontPresetId(fonts?.heading) ?? textFontId;
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
titleFont: await getFontRenderer(headingFontId, 700),
|
|
185
|
+
descriptionFont: await getFontRenderer(textFontId, 400),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
22
189
|
function decodeHtmlEntities(value) {
|
|
23
190
|
if (!value || typeof value !== "string") return "";
|
|
24
191
|
|
|
@@ -89,6 +256,131 @@ function escapeXml(value) {
|
|
|
89
256
|
.replace(/'/g, "'");
|
|
90
257
|
}
|
|
91
258
|
|
|
259
|
+
function findGlyphFont(fonts, codePoint) {
|
|
260
|
+
for (const font of fonts) {
|
|
261
|
+
if (font.hasGlyphForCodePoint(codePoint)) {
|
|
262
|
+
return font;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return fonts[0];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function splitTextIntoFontRuns(line, fonts) {
|
|
269
|
+
const runs = [];
|
|
270
|
+
for (const character of Array.from(line)) {
|
|
271
|
+
const codePoint = character.codePointAt(0);
|
|
272
|
+
if (codePoint === undefined) continue;
|
|
273
|
+
|
|
274
|
+
const font = findGlyphFont(fonts, codePoint);
|
|
275
|
+
const previousRun = runs[runs.length - 1];
|
|
276
|
+
if (previousRun?.font === font) {
|
|
277
|
+
previousRun.text += character;
|
|
278
|
+
} else {
|
|
279
|
+
runs.push({ font, text: character });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return runs;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function renderPathTextLine({
|
|
287
|
+
line,
|
|
288
|
+
x,
|
|
289
|
+
top,
|
|
290
|
+
fontRenderer,
|
|
291
|
+
fontSize,
|
|
292
|
+
fill,
|
|
293
|
+
fillOpacity,
|
|
294
|
+
letterSpacingEm = 0,
|
|
295
|
+
}) {
|
|
296
|
+
const fonts = fontRenderer.fonts ?? [];
|
|
297
|
+
const primaryFont = fonts[0];
|
|
298
|
+
if (!primaryFont) {
|
|
299
|
+
return "";
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const primaryScale = fontSize / primaryFont.unitsPerEm;
|
|
303
|
+
const baselineY = top + primaryFont.ascent * primaryScale;
|
|
304
|
+
const letterSpacingPx = fontSize * letterSpacingEm;
|
|
305
|
+
const pathData = [];
|
|
306
|
+
let cursorX = x;
|
|
307
|
+
|
|
308
|
+
for (const run of splitTextIntoFontRuns(line, fonts)) {
|
|
309
|
+
const font = run.font;
|
|
310
|
+
const scale = fontSize / font.unitsPerEm;
|
|
311
|
+
const glyphRun = font.layout(run.text);
|
|
312
|
+
|
|
313
|
+
for (let index = 0; index < glyphRun.glyphs.length; index += 1) {
|
|
314
|
+
const glyph = glyphRun.glyphs[index];
|
|
315
|
+
const position = glyphRun.positions[index] ?? {};
|
|
316
|
+
const glyphPath = glyph.path;
|
|
317
|
+
const xOffset = (position.xOffset ?? 0) * scale;
|
|
318
|
+
const yOffset = (position.yOffset ?? 0) * scale;
|
|
319
|
+
|
|
320
|
+
if (glyphPath.commands.length > 0) {
|
|
321
|
+
pathData.push(
|
|
322
|
+
glyphPath
|
|
323
|
+
.transform(
|
|
324
|
+
scale,
|
|
325
|
+
0,
|
|
326
|
+
0,
|
|
327
|
+
-scale,
|
|
328
|
+
cursorX + xOffset,
|
|
329
|
+
baselineY - yOffset,
|
|
330
|
+
)
|
|
331
|
+
.toSVG(),
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
cursorX +=
|
|
336
|
+
((position.xAdvance ?? glyph.advanceWidth ?? 0) * scale) +
|
|
337
|
+
letterSpacingPx;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (pathData.length === 0) {
|
|
342
|
+
return "";
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const opacityAttribute =
|
|
346
|
+
fillOpacity === undefined ? "" : ` fill-opacity="${fillOpacity}"`;
|
|
347
|
+
|
|
348
|
+
return `<path d="${pathData.join(" ")}" fill="${fill}"${opacityAttribute} />`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function renderTextLine({
|
|
352
|
+
line,
|
|
353
|
+
x,
|
|
354
|
+
top,
|
|
355
|
+
fontRenderer,
|
|
356
|
+
fontSize,
|
|
357
|
+
fontWeight,
|
|
358
|
+
fill,
|
|
359
|
+
fillOpacity,
|
|
360
|
+
letterSpacingEm = 0,
|
|
361
|
+
}) {
|
|
362
|
+
const pathText = renderPathTextLine({
|
|
363
|
+
line,
|
|
364
|
+
x,
|
|
365
|
+
top,
|
|
366
|
+
fontRenderer,
|
|
367
|
+
fontSize,
|
|
368
|
+
fill,
|
|
369
|
+
fillOpacity,
|
|
370
|
+
letterSpacingEm,
|
|
371
|
+
});
|
|
372
|
+
if (pathText) return pathText;
|
|
373
|
+
|
|
374
|
+
const opacityAttribute =
|
|
375
|
+
fillOpacity === undefined ? "" : ` fill-opacity="${fillOpacity}"`;
|
|
376
|
+
const letterSpacing = letterSpacingEm === 0 ? 0 : `${letterSpacingEm}em`;
|
|
377
|
+
return `<text x="${x}" y="${top}" fill="${fill}"${opacityAttribute} font-family="${escapeXml(
|
|
378
|
+
fontRenderer.family,
|
|
379
|
+
)}" font-size="${fontSize}" font-weight="${fontWeight}" letter-spacing="${letterSpacing}" dominant-baseline="hanging">${escapeXml(
|
|
380
|
+
line,
|
|
381
|
+
)}</text>`;
|
|
382
|
+
}
|
|
383
|
+
|
|
92
384
|
function wrapText(input, maxChars, maxLines) {
|
|
93
385
|
const text = trimToUndefined(input);
|
|
94
386
|
if (!text) return [];
|
|
@@ -188,6 +480,8 @@ function renderOgSvg({
|
|
|
188
480
|
description,
|
|
189
481
|
siteName,
|
|
190
482
|
logoDataUri,
|
|
483
|
+
titleFont,
|
|
484
|
+
descriptionFont,
|
|
191
485
|
}) {
|
|
192
486
|
const resolvedTitle = trimToUndefined(title) ?? siteName;
|
|
193
487
|
const titleLines = wrapText(resolvedTitle, 24, 2);
|
|
@@ -211,7 +505,16 @@ function renderOgSvg({
|
|
|
211
505
|
let currentY = titleBlockTop;
|
|
212
506
|
const titleSvg = titleLines
|
|
213
507
|
.map((line) => {
|
|
214
|
-
const element =
|
|
508
|
+
const element = renderTextLine({
|
|
509
|
+
line,
|
|
510
|
+
x: 56,
|
|
511
|
+
top: currentY,
|
|
512
|
+
fontRenderer: titleFont,
|
|
513
|
+
fontSize: 72,
|
|
514
|
+
fontWeight: 700,
|
|
515
|
+
fill: "#FFFFFF",
|
|
516
|
+
letterSpacingEm: -0.02,
|
|
517
|
+
});
|
|
215
518
|
currentY += titleLineHeight;
|
|
216
519
|
return element;
|
|
217
520
|
})
|
|
@@ -220,7 +523,16 @@ function renderOgSvg({
|
|
|
220
523
|
currentY += spaceBetweenTitleAndDescription;
|
|
221
524
|
const descriptionSvg = descriptionLines
|
|
222
525
|
.map((line) => {
|
|
223
|
-
const element =
|
|
526
|
+
const element = renderTextLine({
|
|
527
|
+
line,
|
|
528
|
+
x: 56,
|
|
529
|
+
top: currentY,
|
|
530
|
+
fontRenderer: descriptionFont,
|
|
531
|
+
fontSize: 32,
|
|
532
|
+
fontWeight: 400,
|
|
533
|
+
fill: "#FFFFFF",
|
|
534
|
+
fillOpacity: 0.82,
|
|
535
|
+
});
|
|
224
536
|
currentY += descriptionLineHeight;
|
|
225
537
|
return element;
|
|
226
538
|
})
|
|
@@ -228,7 +540,16 @@ function renderOgSvg({
|
|
|
228
540
|
|
|
229
541
|
const logoSvg = logoDataUri
|
|
230
542
|
? `<image href="${escapeXml(logoDataUri)}" x="56" y="56" width="420" height="132" preserveAspectRatio="xMinYMid meet" />`
|
|
231
|
-
:
|
|
543
|
+
: renderTextLine({
|
|
544
|
+
line: siteName,
|
|
545
|
+
x: 56,
|
|
546
|
+
top: 74,
|
|
547
|
+
fontRenderer: titleFont,
|
|
548
|
+
fontSize: 64,
|
|
549
|
+
fontWeight: 700,
|
|
550
|
+
fill: "#FFFFFF",
|
|
551
|
+
letterSpacingEm: -0.02,
|
|
552
|
+
});
|
|
232
553
|
|
|
233
554
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
234
555
|
<svg width="${OG_WIDTH}" height="${OG_HEIGHT}" viewBox="0 0 ${OG_WIDTH} ${OG_HEIGHT}" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -253,12 +574,15 @@ function writeImageForRoute({
|
|
|
253
574
|
description,
|
|
254
575
|
siteName,
|
|
255
576
|
logoDataUri,
|
|
577
|
+
ogFontConfig,
|
|
256
578
|
}) {
|
|
257
579
|
const svg = renderOgSvg({
|
|
258
580
|
title,
|
|
259
581
|
description,
|
|
260
582
|
siteName,
|
|
261
583
|
logoDataUri,
|
|
584
|
+
titleFont: ogFontConfig.titleFont,
|
|
585
|
+
descriptionFont: ogFontConfig.descriptionFont,
|
|
262
586
|
});
|
|
263
587
|
|
|
264
588
|
const resvg = new Resvg(svg, {
|
|
@@ -266,6 +590,8 @@ function writeImageForRoute({
|
|
|
266
590
|
loadSystemFonts: true,
|
|
267
591
|
defaultFontFamily: "DejaVu Sans",
|
|
268
592
|
sansSerifFamily: "DejaVu Sans",
|
|
593
|
+
serifFamily: "DejaVu Serif",
|
|
594
|
+
monospaceFamily: "DejaVu Sans Mono",
|
|
269
595
|
},
|
|
270
596
|
});
|
|
271
597
|
|
|
@@ -276,7 +602,7 @@ function writeImageForRoute({
|
|
|
276
602
|
fs.writeFileSync(outputPath, pngData);
|
|
277
603
|
}
|
|
278
604
|
|
|
279
|
-
function main() {
|
|
605
|
+
async function main() {
|
|
280
606
|
if (!fs.existsSync(DIST_DIR)) {
|
|
281
607
|
console.warn("Skipping OG image generation: dist directory not found.");
|
|
282
608
|
return;
|
|
@@ -295,6 +621,7 @@ function main() {
|
|
|
295
621
|
const siteName = trimToUndefined(decodeHtmlEntities(defaults.siteName)) ??
|
|
296
622
|
"Documentation";
|
|
297
623
|
const logoDataUri = resolveLogoDataUri(defaults.logoDark);
|
|
624
|
+
const ogFontConfig = await resolveOgFontConfig(defaults.fonts);
|
|
298
625
|
|
|
299
626
|
const routes = Object.keys(pages).sort();
|
|
300
627
|
if (routes.length === 0) {
|
|
@@ -322,6 +649,7 @@ function main() {
|
|
|
322
649
|
description,
|
|
323
650
|
siteName,
|
|
324
651
|
logoDataUri,
|
|
652
|
+
ogFontConfig,
|
|
325
653
|
});
|
|
326
654
|
|
|
327
655
|
generatedCount += 1;
|
|
@@ -332,4 +660,8 @@ function main() {
|
|
|
332
660
|
);
|
|
333
661
|
}
|
|
334
662
|
|
|
335
|
-
main()
|
|
663
|
+
main().catch((error) => {
|
|
664
|
+
console.error("Failed to generate OG images.");
|
|
665
|
+
console.error(error);
|
|
666
|
+
process.exitCode = 1;
|
|
667
|
+
});
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_TEXT_FONT_PRESET,
|
|
5
|
+
FONT_PRESET_OPTIONS,
|
|
6
|
+
} from "radiant-docs-validator";
|
|
3
7
|
|
|
4
8
|
const CWD = process.cwd();
|
|
5
9
|
const DIST_DIR = path.join(CWD, "dist");
|
|
6
10
|
const DOCS_CONFIG_PATH = path.join(CWD, "src/content/docs/docs.json");
|
|
7
11
|
const OUTPUT_DIR = path.join(DIST_DIR, "_og");
|
|
8
12
|
const OUTPUT_PATH = path.join(OUTPUT_DIR, "meta.json");
|
|
13
|
+
const FONT_PRESET_ID_SET = new Set(FONT_PRESET_OPTIONS);
|
|
9
14
|
|
|
10
15
|
function readDocsConfig() {
|
|
11
16
|
if (!fs.existsSync(DOCS_CONFIG_PATH)) {
|
|
@@ -106,6 +111,29 @@ function getLogoImagePath(variant) {
|
|
|
106
111
|
return typeof variant.image === "string" ? variant.image.trim() : "";
|
|
107
112
|
}
|
|
108
113
|
|
|
114
|
+
function normalizeFontPresetId(value) {
|
|
115
|
+
if (typeof value !== "string") return undefined;
|
|
116
|
+
const normalized = value.trim().toLowerCase();
|
|
117
|
+
return FONT_PRESET_ID_SET.has(normalized) ? normalized : undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function resolveOgFonts(docsConfig) {
|
|
121
|
+
const theme =
|
|
122
|
+
docsConfig?.theme && typeof docsConfig.theme === "object"
|
|
123
|
+
? docsConfig.theme
|
|
124
|
+
: {};
|
|
125
|
+
const fonts =
|
|
126
|
+
theme.fonts && typeof theme.fonts === "object" && !Array.isArray(theme.fonts)
|
|
127
|
+
? theme.fonts
|
|
128
|
+
: {};
|
|
129
|
+
const text = normalizeFontPresetId(fonts.text) ?? DEFAULT_TEXT_FONT_PRESET;
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
text,
|
|
133
|
+
heading: normalizeFontPresetId(fonts.heading) ?? text,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
109
137
|
function extractTitle(html) {
|
|
110
138
|
const ogTitle = decodeHtmlEntities(
|
|
111
139
|
matchMetaContent(html, "property", "og:title"),
|
|
@@ -165,6 +193,7 @@ function main() {
|
|
|
165
193
|
typeof docsConfig.primaryColor === "string"
|
|
166
194
|
? docsConfig.primaryColor.trim()
|
|
167
195
|
: "",
|
|
196
|
+
fonts: resolveOgFonts(docsConfig),
|
|
168
197
|
},
|
|
169
198
|
pages,
|
|
170
199
|
};
|
|
@@ -38,7 +38,7 @@ const footerLinks = footer?.links
|
|
|
38
38
|
? await Promise.all(
|
|
39
39
|
footer.links.map(async (link) => ({
|
|
40
40
|
...link,
|
|
41
|
-
href: await resolveConfiguredHref(link.href, config
|
|
41
|
+
href: await resolveConfiguredHref(link.href, config),
|
|
42
42
|
})),
|
|
43
43
|
)
|
|
44
44
|
: [];
|
|
@@ -112,21 +112,15 @@ const navbarLinks = config.navbar?.links
|
|
|
112
112
|
? await Promise.all(
|
|
113
113
|
config.navbar.links.map(async (link) => ({
|
|
114
114
|
...link,
|
|
115
|
-
href: await resolveConfiguredHref(link.href, config
|
|
115
|
+
href: await resolveConfiguredHref(link.href, config),
|
|
116
116
|
})),
|
|
117
117
|
)
|
|
118
118
|
: [];
|
|
119
119
|
const navbarSecondaryHref = config.navbar?.secondary
|
|
120
|
-
? await resolveConfiguredHref(
|
|
121
|
-
config.navbar.secondary.href,
|
|
122
|
-
config.hiddenPageRoutes,
|
|
123
|
-
)
|
|
120
|
+
? await resolveConfiguredHref(config.navbar.secondary.href, config)
|
|
124
121
|
: "";
|
|
125
122
|
const navbarPrimaryHref = config.navbar?.primary
|
|
126
|
-
? await resolveConfiguredHref(
|
|
127
|
-
config.navbar.primary.href,
|
|
128
|
-
config.hiddenPageRoutes,
|
|
129
|
-
)
|
|
123
|
+
? await resolveConfiguredHref(config.navbar.primary.href, config)
|
|
130
124
|
: "";
|
|
131
125
|
---
|
|
132
126
|
|
|
@@ -137,7 +131,6 @@ const navbarPrimaryHref = config.navbar?.primary
|
|
|
137
131
|
"sm:bg-background/85 sm:backdrop-blur-[18px] sm:backdrop-saturate-50 sm:border-b-neutral-100/85 sm:dark:border-neutral-800/85 sm:bg-clip-padding",
|
|
138
132
|
]}
|
|
139
133
|
data-pagefind-ignore
|
|
140
|
-
data-vaul-scale-chrome
|
|
141
134
|
>
|
|
142
135
|
<div class="flex h-full">
|
|
143
136
|
<div class="lg:w-[283px] h-full">
|