@clidey/dory 1.0.8 → 1.9.0

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.
Files changed (198) hide show
  1. package/README.md +12 -1
  2. package/bin/chat-server.ts +270 -0
  3. package/bin/dist/analytics-server.d.ts +11 -0
  4. package/bin/dist/analytics-server.d.ts.map +1 -0
  5. package/bin/dist/analytics-server.js +121 -0
  6. package/bin/dist/analytics-server.js.map +1 -0
  7. package/bin/dist/automation.d.ts +35 -0
  8. package/bin/dist/automation.d.ts.map +1 -0
  9. package/bin/dist/automation.js +259 -0
  10. package/bin/dist/automation.js.map +1 -0
  11. package/bin/dist/chat-server.d.ts +8 -0
  12. package/bin/dist/chat-server.d.ts.map +1 -0
  13. package/bin/dist/chat-server.js +322 -0
  14. package/bin/dist/chat-server.js.map +1 -0
  15. package/bin/dist/check.d.ts +16 -0
  16. package/bin/dist/check.d.ts.map +1 -0
  17. package/bin/dist/check.js +247 -0
  18. package/bin/dist/check.js.map +1 -0
  19. package/bin/dist/dory.js +252 -402
  20. package/bin/dist/dory.js.map +1 -1
  21. package/bin/dist/i18n-init.d.ts +6 -0
  22. package/bin/dist/i18n-init.d.ts.map +1 -0
  23. package/bin/dist/i18n-init.js +56 -0
  24. package/bin/dist/i18n-init.js.map +1 -0
  25. package/bin/dist/mcp-http.d.ts +8 -0
  26. package/bin/dist/mcp-http.d.ts.map +1 -0
  27. package/bin/dist/mcp-http.js +72 -0
  28. package/bin/dist/mcp-http.js.map +1 -0
  29. package/bin/dist/mcp.d.ts +2 -0
  30. package/bin/dist/mcp.d.ts.map +1 -0
  31. package/bin/dist/mcp.js +81 -0
  32. package/bin/dist/mcp.js.map +1 -0
  33. package/bin/dist/preview-entry.d.ts +2 -0
  34. package/bin/dist/preview-entry.d.ts.map +1 -0
  35. package/bin/dist/preview-entry.js +6 -0
  36. package/bin/dist/preview-entry.js.map +1 -0
  37. package/bin/dist/preview-server.d.ts +5 -0
  38. package/bin/dist/preview-server.d.ts.map +1 -0
  39. package/bin/dist/preview-server.js +159 -0
  40. package/bin/dist/preview-server.js.map +1 -0
  41. package/bin/dist/project-config.d.ts +22 -0
  42. package/bin/dist/project-config.d.ts.map +1 -0
  43. package/bin/dist/project-config.js +44 -0
  44. package/bin/dist/project-config.js.map +1 -0
  45. package/dist/index.html +57 -0
  46. package/dist/runtime/dory-runtime.css +2 -0
  47. package/dist/runtime/dory-runtime.html +14 -0
  48. package/dist/runtime/dory-runtime.js +5490 -0
  49. package/docs/api-playground-demo.mdx +119 -0
  50. package/docs/api-reference/asyncapi.json +324 -0
  51. package/docs/api-reference/openapi.json +366 -0
  52. package/docs/api-reference/operations/create.mdx +5 -0
  53. package/docs/api-reference/operations/delete.mdx +5 -0
  54. package/docs/api-reference/operations/get.mdx +5 -0
  55. package/docs/api-reference/operations/update.mdx +5 -0
  56. package/docs/api-reference/websocket-operations/receive-chat-message.mdx +5 -0
  57. package/docs/api-reference/websocket-operations/receive-notifications.mdx +5 -0
  58. package/docs/api-reference/websocket-operations/receive-presence-update.mdx +5 -0
  59. package/docs/api-reference/websocket-operations/send-chat-message.mdx +5 -0
  60. package/docs/api-reference/websocket-operations/send-presence-update.mdx +5 -0
  61. package/docs/assets/dory.jpeg +0 -0
  62. package/docs/components/accordion.mdx +145 -0
  63. package/docs/components/callouts.mdx +134 -0
  64. package/docs/components/cards.mdx +153 -0
  65. package/docs/components/code-blocks.mdx +281 -0
  66. package/docs/components/expandable.mdx +189 -0
  67. package/docs/components/icons.mdx +71 -0
  68. package/docs/components/images.mdx +82 -0
  69. package/docs/components/latex.mdx +148 -0
  70. package/docs/components/layout.mdx +175 -0
  71. package/docs/components/lists.mdx +134 -0
  72. package/docs/components/mermaid.mdx +149 -0
  73. package/docs/components/overview.mdx +81 -0
  74. package/docs/components/source.mdx +130 -0
  75. package/docs/components/steps.mdx +122 -0
  76. package/docs/components/tables.mdx +114 -0
  77. package/docs/components/tags.mdx +179 -0
  78. package/docs/components/typography.mdx +185 -0
  79. package/docs/configuration/analytics.mdx +45 -0
  80. package/docs/configuration/chat.mdx +59 -0
  81. package/docs/configuration/dory-json.mdx +278 -0
  82. package/docs/configuration/embed.mdx +73 -0
  83. package/docs/configuration/fonts.mdx +86 -0
  84. package/docs/configuration/internationalization.mdx +61 -0
  85. package/docs/configuration/navigation.mdx +217 -0
  86. package/docs/configuration/theming.mdx +166 -0
  87. package/docs/dory.json +129 -0
  88. package/docs/embed-example.html +176 -0
  89. package/docs/favicon.svg +18 -0
  90. package/docs/features.mdx +440 -0
  91. package/docs/getting-started/automation.mdx +76 -0
  92. package/docs/getting-started/configuration.mdx +95 -0
  93. package/docs/getting-started/installation.mdx +88 -0
  94. package/docs/getting-started/quick-start.mdx +53 -0
  95. package/docs/getting-started/whodb-ee-apps.mdx +122 -0
  96. package/docs/introduction.mdx +50 -0
  97. package/docs/logo/logo.svg +18 -0
  98. package/docs/playgrounds/api-playground.mdx +122 -0
  99. package/docs/playgrounds/websocket-playground.mdx +98 -0
  100. package/docs/social-card.png +0 -0
  101. package/docs/social-card.svg +17 -0
  102. package/docs/websocket-playground-demo.mdx +135 -0
  103. package/package.json +23 -17
  104. package/scripts/verify-runtime-parity.mjs +146 -0
  105. package/src/app.css +19 -1
  106. package/src/assets/fonts/HankenGrotesk.ttf +0 -0
  107. package/src/assets/fonts/OFL.txt +94 -0
  108. package/src/components/chat-assistant.tsx +229 -0
  109. package/src/components/dropdown.tsx +3 -3
  110. package/src/components/error-boundary.tsx +2 -2
  111. package/src/components/hooks.tsx +20 -1
  112. package/src/components/navigation.tsx +82 -51
  113. package/src/components/notification.tsx +2 -3
  114. package/src/components/routes.tsx +17 -19
  115. package/src/components/search-index.test.ts +15 -0
  116. package/src/components/search-index.ts +49 -48
  117. package/src/components/search.tsx +208 -104
  118. package/src/components/store.ts +27 -9
  119. package/src/config/mdx-common.ts +11 -0
  120. package/src/config/mdx.ts +10 -11
  121. package/src/embed/loader.ts +0 -3
  122. package/src/embed/types.ts +0 -2
  123. package/src/embed/widget-iframe.ts +19 -21
  124. package/src/embedded-runtime/config.ts +138 -0
  125. package/src/embedded-runtime/index.tsx +680 -0
  126. package/src/embedded-runtime/runtime.html +14 -0
  127. package/src/entry-server.tsx +46 -124
  128. package/src/main.tsx +16 -4
  129. package/src/mdx/accordion.tsx +3 -3
  130. package/src/mdx/api-playground.tsx +18 -16
  131. package/src/mdx/async-api.tsx +2 -2
  132. package/src/mdx/card.tsx +3 -2
  133. package/src/mdx/code.tsx +11 -9
  134. package/src/mdx/fence.tsx +6 -5
  135. package/src/mdx/icon-grid.tsx +4 -1
  136. package/src/mdx/icon-names.test.ts +15 -0
  137. package/src/mdx/icon-names.ts +60 -0
  138. package/src/mdx/icon-source-variants.ts +102 -0
  139. package/src/mdx/icon.tsx +12 -73
  140. package/src/mdx/latex.tsx +3 -2
  141. package/src/mdx/mdx.tsx +1 -1
  142. package/src/mdx/mermaid.tsx +12 -6
  143. package/src/mdx/open-api.tsx +130 -74
  144. package/src/mdx/preprocess.test.ts +16 -0
  145. package/src/mdx/preprocess.ts +61 -0
  146. package/src/mdx/processor.ts +4 -96
  147. package/src/mdx/source.tsx +5 -4
  148. package/src/mdx/specimen.tsx +38 -6
  149. package/src/mdx/tabs.tsx +29 -21
  150. package/src/mdx/text.tsx +9 -9
  151. package/src/mdx/try-it-panel.tsx +284 -0
  152. package/src/mdx/type-scale.tsx +4 -4
  153. package/src/mdx/websocket-playground.tsx +21 -14
  154. package/src/plugins/analytics-injector.ts +8 -0
  155. package/src/plugins/chat-dev-server.ts +32 -0
  156. package/src/plugins/docs-assets.test.ts +25 -0
  157. package/src/plugins/docs-assets.ts +6 -2
  158. package/src/plugins/frontmatter-dev-server.ts +2 -2
  159. package/src/plugins/frontmatter-generator.ts +1 -1
  160. package/src/plugins/html-files-middleware.test.ts +21 -1
  161. package/src/plugins/html-files-middleware.ts +26 -6
  162. package/src/plugins/html-metadata-injector.ts +24 -4
  163. package/src/plugins/llm-txt-dev-server.ts +2 -2
  164. package/src/plugins/llm-txt-generator.ts +21 -4
  165. package/src/plugins/prerender.ts +47 -20
  166. package/src/plugins/robots-generator.ts +1 -1
  167. package/src/plugins/sanitize.ts +1 -1
  168. package/src/plugins/shared.ts +6 -18
  169. package/src/plugins/sitemap-generator.ts +1 -1
  170. package/src/plugins/social-preview.test.ts +71 -0
  171. package/src/plugins/social-preview.ts +87 -0
  172. package/src/plugins/unified-mdx-plugin.ts +1 -1
  173. package/src/theme.scss +630 -207
  174. package/src/types/config.ts +53 -1
  175. package/src/ui/breadcrumb.tsx +17 -0
  176. package/src/ui/docs-header-view.tsx +32 -0
  177. package/src/ui/docs-navigation-view.tsx +69 -0
  178. package/src/ui/docs-page-frame.tsx +50 -0
  179. package/src/ui/dory-mark.tsx +9 -0
  180. package/src/ui/header.tsx +33 -90
  181. package/src/ui/layout.tsx +99 -153
  182. package/src/ui/mobile-navigation.tsx +10 -8
  183. package/src/ui/page-actions.tsx +67 -0
  184. package/src/ui/page-feedback.tsx +50 -0
  185. package/src/ui/page-footer.tsx +23 -0
  186. package/src/ui/prev-next-link.tsx +15 -69
  187. package/src/ui/prev-next-view.tsx +27 -0
  188. package/src/ui/table-of-content-view.tsx +167 -0
  189. package/src/ui/table-of-content.tsx +25 -183
  190. package/src/utils/analytics.test.ts +36 -0
  191. package/src/utils/analytics.ts +35 -0
  192. package/src/utils/colors.ts +7 -0
  193. package/src/utils/content-scope.test.ts +26 -0
  194. package/src/utils/content-scope.ts +39 -0
  195. package/src/utils/search-content.ts +12 -0
  196. package/src/utils/theme.ts +1 -7
  197. package/vite.config.ssr.ts +3 -3
  198. package/vite.config.ts +21 -15
@@ -1,7 +1,8 @@
1
1
  import matter from 'gray-matter';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
- import { normalizeBasePath } from '../utils/base-path';
4
+ import { normalizeBasePath } from '../utils/base-path.ts';
5
+ export { stripMdxToText } from '../utils/search-content.ts';
5
6
 
6
7
  /**
7
8
  * Shared helpers for build plugins that read docs/*.mdx and docs/dory.json.
@@ -62,6 +63,8 @@ export function getNavigationOrder(docsDir: string): string[] {
62
63
  for (const page of pages) {
63
64
  if (typeof page === 'string') {
64
65
  order.push(page);
66
+ } else if (typeof page === 'object' && typeof page.page === 'string') {
67
+ order.push(page.page);
65
68
  } else if (typeof page === 'object' && page.pages) {
66
69
  walkPages(page.pages);
67
70
  }
@@ -87,12 +90,13 @@ export function getNavigationOrder(docsDir: string): string[] {
87
90
  * e.g. docs/getting-started/installation.mdx -> /getting-started/installation.
88
91
  */
89
92
  export function pathFromFilename(filename: string, docsDir: string): string {
90
- return filename
93
+ const route = filename
91
94
  .replace(docsDir, '')
92
95
  .replace(/\/?index\.mdx$/, '/')
93
96
  .replace(/\.mdx$/, '')
94
97
  .replace(/\\/g, '/')
95
98
  .toLowerCase();
99
+ return route.length > 1 ? route.replace(/\/$/, '') : route;
96
100
  }
97
101
 
98
102
  /**
@@ -117,22 +121,6 @@ export function findMdxFilesMap(dir: string, baseDir: string): Record<string, st
117
121
  return files;
118
122
  }
119
123
 
120
- /**
121
- * Strips MDX/JSX syntax to extract plain text (for search indexing and
122
- * meta description fallbacks).
123
- */
124
- export function stripMdxToText(content: string): string {
125
- return content
126
- .replace(/^import\s+.*$/gm, '') // import statements
127
- .replace(/^export\s+.*$/gm, '') // export statements
128
- .replace(/```[\s\S]*?```/g, '') // fenced code blocks
129
- .replace(/<[^>]+>/g, '') // JSX/HTML tags
130
- .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') // markdown links -> text
131
- .replace(/[#*_~`>|]/g, '') // markdown syntax chars
132
- .replace(/\n{3,}/g, '\n\n') // collapse blank lines
133
- .trim();
134
- }
135
-
136
124
  /**
137
125
  * Escapes a string for safe use in HTML/XML text and attribute values.
138
126
  */
@@ -8,7 +8,7 @@ import {
8
8
  parseFrontmatter,
9
9
  escapeHtml,
10
10
  getSiteUrl,
11
- } from './shared';
11
+ } from './shared.ts';
12
12
 
13
13
  /**
14
14
  * Generates sitemap.xml at build time from all MDX files (not just those in
@@ -0,0 +1,71 @@
1
+ import { afterEach, expect, test } from 'vitest';
2
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { resolve } from 'node:path';
5
+ import { renderSocialPreview, socialPreviewPath, wrapSocialText } from './social-preview';
6
+
7
+ const folders: string[] = [];
8
+ afterEach(() => { for (const folder of folders.splice(0)) rmSync(folder, { recursive: true, force: true }); });
9
+
10
+ test('image paths distinguish nested routes and cannot escape the output directory', () => {
11
+ expect(socialPreviewPath('/guide/start')).not.toBe(socialPreviewPath('/reference/start'));
12
+ expect(socialPreviewPath('/../../private')).toMatch(/^\/_dory\/social\/page-[a-f0-9]+\.png$/);
13
+ });
14
+
15
+ test('long titles wrap into bounded lines without cutting Unicode characters', () => {
16
+ const lines = wrapSocialText('A'.repeat(200), 30, 3);
17
+ expect(lines).toHaveLength(3);
18
+ expect(lines.every(line => Array.from(line).length <= 30)).toBe(true);
19
+ expect(lines[2]).toMatch(/…$/);
20
+ expect(wrapSocialText('😀'.repeat(12), 6, 2)).toEqual(['😀'.repeat(6), '😀'.repeat(6)]);
21
+ });
22
+
23
+ test('renders distinct 1200 by 630 PNGs with no system font or browser dependency', () => {
24
+ const root = mkdtempSync(resolve(tmpdir(), 'dory-social-'));
25
+ folders.push(root);
26
+ const docsDir = resolve(root, 'docs');
27
+ mkdirSync(docsDir);
28
+ const options = { config: { name: 'Example & Docs' }, docsDir, outDir: root, description: 'A guide for your team.' };
29
+ const first = renderSocialPreview({ ...options, route: '/install', title: 'Installation' });
30
+ const second = renderSocialPreview({ ...options, route: '/start', title: 'Getting started <safely>' });
31
+ const png = readFileSync(resolve(root, first.slice(1)));
32
+ expect(png.subarray(0, 8)).toEqual(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
33
+ expect(png.readUInt32BE(16)).toBe(1200);
34
+ expect(png.readUInt32BE(20)).toBe(630);
35
+ expect(png).not.toEqual(readFileSync(resolve(root, second.slice(1))));
36
+ });
37
+
38
+ test('page overrides and shared-image opt-out produce correct base-prefixed metadata', async () => {
39
+ const { prerender } = await import('./prerender');
40
+ const { vi } = await import('vitest');
41
+ const { copyFileSync, writeFileSync } = await import('node:fs');
42
+ const root = mkdtempSync(resolve(tmpdir(), 'dory-social-metadata-'));
43
+ folders.push(root);
44
+ const dist = resolve(root, 'dist');
45
+ mkdirSync(resolve(root, 'docs'), { recursive: true });
46
+ mkdirSync(resolve(root, 'src/assets/fonts'), { recursive: true });
47
+ mkdirSync(dist);
48
+ copyFileSync(resolve('src/assets/fonts/HankenGrotesk.ttf'), resolve(root, 'src/assets/fonts/HankenGrotesk.ttf'));
49
+ const config = { name: 'Example', url: 'https://docs.example.com', basePath: '/manual', image: '/shared.png', navigation: { tabs: [] } };
50
+ const template = '<html lang="en"><head><title>Docs</title><meta name="description" content="" /><meta property="og:image" content="" /><meta name="twitter:image" content="" /><meta name="twitter:card" content="summary" /></head><body><div id="app"></div></body></html>';
51
+ writeFileSync(resolve(dist, 'frontmatter.json'), JSON.stringify([
52
+ { path: '/start', title: 'Start', description: 'Get started.' },
53
+ { path: '/custom', title: 'Custom', description: 'Custom page.', image: '/art/custom.png' },
54
+ ]));
55
+ const cwd = vi.spyOn(process, 'cwd').mockReturnValue(root);
56
+ try {
57
+ for (const enabled of [true, false]) {
58
+ writeFileSync(resolve(root, 'docs/dory.json'), JSON.stringify({ ...config, seo: { generateSocialImages: enabled } }));
59
+ writeFileSync(resolve(dist, 'index.html'), template);
60
+ const hooks = prerender() as { configResolved(config: unknown): void; closeBundle(): void };
61
+ hooks.configResolved({ build: { outDir: dist } });
62
+ hooks.closeBundle();
63
+ const start = readFileSync(resolve(dist, 'start/index.html'), 'utf8');
64
+ const custom = readFileSync(resolve(dist, 'custom/index.html'), 'utf8');
65
+ const image = enabled ? socialPreviewPath('/start') : '/shared.png';
66
+ expect(start).toContain(`content="https://docs.example.com/manual${image}"`);
67
+ expect(custom).toContain('content="https://docs.example.com/manual/art/custom.png"');
68
+ if (enabled) expect(readFileSync(resolve(dist, image.slice(1))).readUInt32BE(16)).toBe(1200);
69
+ }
70
+ } finally { cwd.mockRestore(); }
71
+ });
@@ -0,0 +1,87 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
3
+ import { dirname, extname, isAbsolute, relative, resolve } from 'node:path';
4
+ import { Resvg } from '@resvg/resvg-js';
5
+ import type { DoryConfig } from '../types/config.ts';
6
+ import { escapeHtml } from './shared.ts';
7
+
8
+ export function socialPreviewPath(route: string): string {
9
+ return route === '/' ? '/_dory/social/home.png' : `/_dory/social/page-${createHash('sha256').update(route).digest('hex').slice(0, 20)}.png`;
10
+ }
11
+
12
+ function localAsset(docsDir: string, source?: string): string | undefined {
13
+ if (!source || /^(?:[a-z]+:|\/\/)/i.test(source)) return;
14
+ const file = resolve(docsDir, source.replace(/^\//, ''));
15
+ if (!existsSync(file)) return;
16
+ const rel = relative(realpathSync(docsDir), realpathSync(file));
17
+ if (rel.startsWith('..') || isAbsolute(rel)) return;
18
+ return file;
19
+ }
20
+
21
+ /** Bounded lines, including long words and titles without spaces. */
22
+ export function wrapSocialText(text: string, columns: number, maxLines: number): string[] {
23
+ const words = text.replace(/\s+/g, ' ').trim().split(' ');
24
+ const lines: string[] = [];
25
+ let line = '';
26
+ for (const word of words) {
27
+ if (line && Array.from(`${line} ${word}`).length > columns) { lines.push(line); line = ''; }
28
+ const characters = Array.from(word);
29
+ while (characters.length > columns) {
30
+ if (line) { lines.push(line); line = ''; }
31
+ lines.push(characters.splice(0, columns).join(''));
32
+ }
33
+ const tail = characters.join('');
34
+ if (tail) line = line ? `${line} ${tail}` : tail;
35
+ }
36
+ if (line) lines.push(line);
37
+ if (lines.length > maxLines) {
38
+ lines.length = maxLines;
39
+ lines[maxLines - 1] = `${Array.from(lines[maxLines - 1]).slice(0, columns - 1).join('').trimEnd()}…`;
40
+ }
41
+ return lines;
42
+ }
43
+
44
+ /** Build-only PNG generation: local files, bundled font, no browser or network. */
45
+ export function renderSocialPreview({ route, title, description, config, docsDir, outDir }: {
46
+ route: string; title: string; description: string; config: Partial<DoryConfig>; docsDir: string; outDir: string;
47
+ }): string {
48
+ const name = config.name || 'Documentation';
49
+ const hex = (value: string | undefined, fallback: string) => value && /^#[a-f\d]{3,8}$/i.test(value) ? value : fallback;
50
+ const background = hex(config.colors?.background, '#15243e');
51
+ const primary = hex(config.colors?.primary, '#5c8cf4');
52
+ const foreground = hex(config.colors?.foreground, '#f8f5ed');
53
+ const muted = hex(config.colors?.mutedForeground, '#b2bdd0');
54
+ const logo = localAsset(docsDir, config.logo?.dark ?? config.logo?.light);
55
+ const imageTypes: Record<string, string> = { '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp' };
56
+ const logoType = logo && imageTypes[extname(logo).toLowerCase()];
57
+ const logoData = logo && logoType ? `data:${logoType};base64,${readFileSync(logo).toString('base64')}` : undefined;
58
+ const font = localAsset(docsDir, config.fonts?.heading?.source ?? config.fonts?.source);
59
+ const customFont = font && /\.(ttf|otf)$/i.test(font) ? font : undefined;
60
+ const family = customFont ? config.fonts?.heading?.family ?? config.fonts?.family ?? 'Hanken Grotesk' : 'Hanken Grotesk';
61
+ const size = title.length > 85 ? 56 : 64;
62
+ const lines = wrapSocialText(title, Math.floor(1050 / (size * 0.62)), 3);
63
+ const text = (value: string, x: number, y: number, fontSize: number, color: string, weight = 400) => `<text x="${x}" y="${y}" font-size="${fontSize}" fill="${color}" font-weight="${weight}">${escapeHtml(value)}</text>`;
64
+ const subtitleY = Math.max(350, 225 + (lines.length - 1) * 76 + 65);
65
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
66
+ <rect width="1200" height="630" fill="${background}"/><rect width="1200" height="8" fill="${primary}"/>
67
+ <circle cx="1170" cy="360" r="280" fill="${primary}" opacity="0.06"/>
68
+ <g font-family="${escapeHtml(family)}, sans-serif">
69
+ ${logoData ? `<image href="${logoData}" x="72" y="62" width="46" height="50"/>` : `<rect x="72" y="64" width="44" height="44" rx="10" fill="${primary}"/>`}
70
+ ${text(wrapSocialText(name, 55, 1)[0] ?? '', 136, 100, 32, foreground, 700)}
71
+ ${lines.map((line, i) => text(line, 72, 225 + i * 76, size, foreground, 700)).join('')}
72
+ ${wrapSocialText(description, 78, 2).map((line, i) => text(line, 76, subtitleY + i * 36, 26, muted)).join('')}
73
+ <path d="M76 518H1124" stroke="${foreground}" opacity="0.15"/>
74
+ ${text('DOCUMENTATION', 76, 565, 17, muted)}
75
+ <text x="1124" y="565" text-anchor="end" font-size="20" fill="${primary}">${escapeHtml(wrapSocialText(config.url?.replace(/^https?:\/\//, '') ?? name, 60, 1)[0] ?? '')}</text>
76
+ </g></svg>`;
77
+ const path = socialPreviewPath(route);
78
+ const file = resolve(outDir, path.slice(1));
79
+ mkdirSync(dirname(file), { recursive: true });
80
+ const renderer = new Resvg(svg, { font: {
81
+ loadSystemFonts: false,
82
+ fontFiles: [resolve(process.cwd(), 'src/assets/fonts/HankenGrotesk.ttf'), ...(customFont ? [customFont] : [])],
83
+ defaultFontFamily: family,
84
+ } });
85
+ writeFileSync(file, renderer.render().asPng());
86
+ return path;
87
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Plugin } from 'vite';
2
- import { preprocessMdxContent } from '../mdx/processor';
2
+ import { preprocessMdxContent } from '../mdx/processor.ts';
3
3
 
4
4
  /**
5
5
  * Unified MDX preprocessing plugin for Vite.