@clidey/dory 0.30.0 → 0.31.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 (74) hide show
  1. package/bin/dist/dory.js +1 -8
  2. package/bin/dist/dory.js.map +1 -1
  3. package/index.html +54 -0
  4. package/package.json +12 -4
  5. package/src/app.css +5 -0
  6. package/src/app.tsx +13 -0
  7. package/src/components/dropdown.tsx +53 -0
  8. package/src/components/hooks.tsx +36 -0
  9. package/src/components/loading.tsx +21 -0
  10. package/src/components/mdx-provider.tsx +13 -0
  11. package/src/components/navigation.tsx +58 -0
  12. package/src/components/notification.tsx +13 -0
  13. package/src/components/routes.tsx +92 -0
  14. package/src/components/search-index.ts +43 -0
  15. package/src/components/search.tsx +448 -0
  16. package/src/components/store.ts +216 -0
  17. package/src/config/mdx.js +12 -0
  18. package/src/config/mdx.ts +12 -0
  19. package/src/embed/loader.ts +200 -0
  20. package/src/embed/types.ts +54 -0
  21. package/src/embed/widget-iframe.ts +302 -0
  22. package/src/main.tsx +22 -0
  23. package/src/mdx/accordion.tsx +41 -0
  24. package/src/mdx/api-playground.tsx +659 -0
  25. package/src/mdx/api.tsx +354 -0
  26. package/src/mdx/async-api.tsx +353 -0
  27. package/src/mdx/callout.tsx +64 -0
  28. package/src/mdx/card.tsx +54 -0
  29. package/src/mdx/code.tsx +289 -0
  30. package/src/mdx/expandable.tsx +36 -0
  31. package/src/mdx/fence.tsx +74 -0
  32. package/src/mdx/icon.tsx +73 -0
  33. package/src/mdx/latex.tsx +26 -0
  34. package/src/mdx/list.tsx +7 -0
  35. package/src/mdx/mdx.tsx +47 -0
  36. package/src/mdx/mermaid.tsx +147 -0
  37. package/src/mdx/open-api.tsx +311 -0
  38. package/src/mdx/processor.ts +174 -0
  39. package/src/mdx/source.tsx +58 -0
  40. package/src/mdx/step.tsx +51 -0
  41. package/src/mdx/table.tsx +20 -0
  42. package/src/mdx/tag.tsx +53 -0
  43. package/src/mdx/text.tsx +99 -0
  44. package/src/mdx/websocket-playground.tsx +824 -0
  45. package/src/plugins/docs-assets.ts +79 -0
  46. package/src/plugins/frontmatter-dev-server.ts +158 -0
  47. package/src/plugins/frontmatter-generator.ts +157 -0
  48. package/src/plugins/html-files-middleware.ts +108 -0
  49. package/src/plugins/html-metadata-injector.ts +76 -0
  50. package/src/plugins/llm-txt-dev-server.ts +183 -0
  51. package/src/plugins/llm-txt-generator.ts +186 -0
  52. package/src/plugins/sanitize.ts +163 -0
  53. package/src/plugins/unified-mdx-plugin.ts +35 -0
  54. package/src/sdk/index.ts +89 -0
  55. package/src/sdk/react.tsx +139 -0
  56. package/src/sdk/vue.ts +107 -0
  57. package/src/theme.scss +72 -0
  58. package/src/types/config.ts +89 -0
  59. package/src/ui/header.tsx +96 -0
  60. package/src/ui/layout.tsx +237 -0
  61. package/src/ui/mobile-navigation.tsx +72 -0
  62. package/src/ui/prev-next-link.tsx +74 -0
  63. package/src/ui/table-of-content.tsx +167 -0
  64. package/src/utils/colors.ts +54 -0
  65. package/src/utils/fonts.ts +23 -0
  66. package/src/utils/functions.ts +12 -0
  67. package/src/vite-env.d.ts +1 -0
  68. package/tsconfig.app.json +33 -0
  69. package/tsconfig.bin.json +25 -0
  70. package/tsconfig.json +7 -0
  71. package/tsconfig.node.json +26 -0
  72. package/vite.config.embed-loader.ts +28 -0
  73. package/vite.config.embed-widget.ts +31 -0
  74. package/vite.config.ts +45 -0
@@ -0,0 +1,79 @@
1
+ import type { Plugin } from 'vite';
2
+ import { cpSync, existsSync, readdirSync, statSync } from 'fs';
3
+ import { resolve, extname } from 'path';
4
+
5
+ /**
6
+ * Plugin to copy docs assets to the build output
7
+ * Since we can't use publicDir for docs (we need to import from it),
8
+ * this plugin manually copies asset files during build
9
+ */
10
+ export function docsAssetsPlugin(): Plugin {
11
+ return {
12
+ name: 'docs-assets',
13
+ apply: 'build', // Only run during build
14
+ closeBundle() {
15
+ const docsDir = resolve(process.cwd(), 'docs');
16
+ const outDir = resolve(process.cwd(), 'dist');
17
+
18
+ if (!existsSync(docsDir)) {
19
+ console.warn('[docs-assets] docs directory not found, skipping asset copy');
20
+ return;
21
+ }
22
+
23
+ // Define asset extensions to copy
24
+ const assetExtensions = new Set([
25
+ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico',
26
+ '.pdf', '.mp4', '.webm', '.mp3', '.wav',
27
+ '.woff', '.woff2', '.ttf', '.eot',
28
+ ]);
29
+
30
+ // Define file/folder patterns to skip
31
+ const skipPatterns = new Set([
32
+ 'dory.json',
33
+ 'node_modules',
34
+ '.DS_Store',
35
+ ]);
36
+
37
+ const shouldSkip = (name: string) => {
38
+ if (skipPatterns.has(name)) return true;
39
+ const ext = extname(name).toLowerCase();
40
+ // Skip MDX and JSON files (they're processed by Vite)
41
+ if (ext === '.mdx' || ext === '.md' || ext === '.json') return true;
42
+ return false;
43
+ };
44
+
45
+ const copyAssets = (srcDir: string, destDir: string, relativePath: string = '') => {
46
+ const items = readdirSync(srcDir);
47
+
48
+ for (const item of items) {
49
+ if (shouldSkip(item)) continue;
50
+
51
+ const srcPath = resolve(srcDir, item);
52
+ const destPath = resolve(destDir, item);
53
+ const itemRelativePath = relativePath ? `${relativePath}/${item}` : item;
54
+
55
+ try {
56
+ const stats = statSync(srcPath);
57
+
58
+ if (stats.isDirectory()) {
59
+ // Recursively copy directories
60
+ copyAssets(srcPath, destPath, itemRelativePath);
61
+ } else if (stats.isFile()) {
62
+ const ext = extname(item).toLowerCase();
63
+ // Copy if it's an asset file
64
+ if (assetExtensions.has(ext)) {
65
+ cpSync(srcPath, destPath, { force: true });
66
+ console.log(`[docs-assets] Copied ${itemRelativePath}`);
67
+ }
68
+ }
69
+ } catch (error) {
70
+ console.warn(`[docs-assets] Failed to copy ${itemRelativePath}:`, error);
71
+ }
72
+ }
73
+ };
74
+
75
+ console.log('[docs-assets] Copying assets from docs to dist...');
76
+ copyAssets(docsDir, outDir);
77
+ },
78
+ };
79
+ }
@@ -0,0 +1,158 @@
1
+ import type { Plugin } from 'vite';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ // Read dory.json to get navigation order (flat list, in order)
6
+ function getNavigationOrder(docsDir: string): string[] {
7
+ const doryJsonPath = path.join(docsDir, 'dory.json');
8
+ if (!fs.existsSync(doryJsonPath)) return [];
9
+ const doryJson = JSON.parse(fs.readFileSync(doryJsonPath, 'utf-8'));
10
+ const order: string[] = [];
11
+
12
+ function walkPages(pages: any[]) {
13
+ for (const page of pages) {
14
+ if (typeof page === 'string') {
15
+ order.push(page);
16
+ } else if (typeof page === 'object' && page.pages) {
17
+ walkPages(page.pages);
18
+ }
19
+ }
20
+ }
21
+
22
+ if (doryJson.navigation && Array.isArray(doryJson.navigation.tabs)) {
23
+ for (const tab of doryJson.navigation.tabs) {
24
+ if (tab.groups && Array.isArray(tab.groups)) {
25
+ for (const group of tab.groups) {
26
+ if (group.pages && Array.isArray(group.pages)) {
27
+ walkPages(group.pages);
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ return order;
34
+ }
35
+
36
+ // Function to parse frontmatter from MDX content
37
+ function parseFrontmatter(content: string) {
38
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
39
+ if (!frontmatterMatch) return { data: {}, content };
40
+
41
+ const frontmatter = frontmatterMatch[1];
42
+ const data: Record<string, any> = {};
43
+
44
+ frontmatter.split('\n').forEach(line => {
45
+ const [key, ...valueParts] = line.split(':');
46
+ if (key && valueParts.length) {
47
+ const value = valueParts.join(':').trim();
48
+ data[key.trim()] = value.replace(/^["']|["']$/g, '');
49
+ }
50
+ });
51
+
52
+ return { data, content: content.slice(frontmatterMatch[0].length) };
53
+ }
54
+
55
+ // Convert filename to path format used by the store
56
+ function pathFromFilename(filename: string, docsDir: string): string {
57
+ return filename
58
+ .replace(docsDir, '')
59
+ .replace(/\/?index\.mdx$/, '/')
60
+ .replace(/\.mdx$/, '')
61
+ .replace(/\\/g, '/')
62
+ .toLowerCase();
63
+ }
64
+
65
+ // Function to recursively find all MDX files and map by relative path (without .mdx)
66
+ function findMdxFilesMap(dir: string, baseDir: string): Record<string, string> {
67
+ const files: Record<string, string> = {};
68
+ const items = fs.readdirSync(dir, { withFileTypes: true });
69
+
70
+ for (const item of items) {
71
+ const fullPath = path.join(dir, item.name);
72
+ if (item.isDirectory()) {
73
+ Object.assign(files, findMdxFilesMap(fullPath, baseDir));
74
+ } else if (item.isFile() && item.name.endsWith('.mdx')) {
75
+ // Key: relative path without .mdx, with forward slashes
76
+ let rel = path.relative(baseDir, fullPath).replace(/\\/g, '/');
77
+ if (rel.endsWith('.mdx')) rel = rel.slice(0, -4);
78
+ files[rel] = fullPath;
79
+ }
80
+ }
81
+
82
+ return files;
83
+ }
84
+
85
+ // Function to generate frontmatter JSON in dory.json order, then append any extra files
86
+ function generateFrontmatterJson(docsDir: string): Array<Record<string, any>> {
87
+ const navOrder = getNavigationOrder(docsDir);
88
+ const mdxFilesMap = findMdxFilesMap(docsDir, docsDir);
89
+ const usedKeys = new Set<string>();
90
+ const frontmatterArray: Array<Record<string, any>> = [];
91
+
92
+ // Process files in navigation order first
93
+ for (const navKey of navOrder) {
94
+ // Try both with and without "index"
95
+ let fileKey = navKey;
96
+ if (mdxFilesMap[fileKey]) {
97
+ usedKeys.add(fileKey);
98
+ } else if (mdxFilesMap[`${fileKey}/index`]) {
99
+ fileKey = `${fileKey}/index`;
100
+ usedKeys.add(fileKey);
101
+ } else {
102
+ continue;
103
+ }
104
+
105
+ const filePath = mdxFilesMap[fileKey];
106
+ const rawContent = fs.readFileSync(filePath, 'utf-8');
107
+ const { data } = parseFrontmatter(rawContent);
108
+ const pathname = pathFromFilename(filePath, docsDir);
109
+
110
+ frontmatterArray.push({
111
+ ...data,
112
+ path: pathname
113
+ });
114
+ }
115
+
116
+ // Process any extra files not in navigation order, sorted
117
+ const extraKeys = Object.keys(mdxFilesMap).filter(k => !usedKeys.has(k)).sort();
118
+ for (const fileKey of extraKeys) {
119
+ const filePath = mdxFilesMap[fileKey];
120
+ const rawContent = fs.readFileSync(filePath, 'utf-8');
121
+ const { data } = parseFrontmatter(rawContent);
122
+ const pathname = pathFromFilename(filePath, docsDir);
123
+
124
+ frontmatterArray.push({
125
+ ...data,
126
+ path: pathname
127
+ });
128
+ }
129
+
130
+ return frontmatterArray;
131
+ }
132
+
133
+ export function frontmatterDevServer(): Plugin {
134
+ return {
135
+ name: 'frontmatter-dev-server',
136
+ configureServer(server) {
137
+ server.middlewares.use('/frontmatter.json', (req, res, next) => {
138
+ if (req.method !== 'GET') {
139
+ return next();
140
+ }
141
+
142
+ try {
143
+ const docsDir = path.join(process.cwd(), 'docs');
144
+ const frontmatterArray = generateFrontmatterJson(docsDir);
145
+ const jsonContent = JSON.stringify(frontmatterArray, null, 2);
146
+
147
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
148
+ res.setHeader('Content-Length', Buffer.byteLength(jsonContent));
149
+ res.end(jsonContent);
150
+ } catch (error) {
151
+ console.error('Error generating frontmatter JSON:', error);
152
+ res.statusCode = 500;
153
+ res.end('Internal Server Error');
154
+ }
155
+ });
156
+ }
157
+ };
158
+ }
@@ -0,0 +1,157 @@
1
+ import type { Plugin } from 'vite';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ interface FrontmatterGeneratorOptions {
6
+ docsDir?: string;
7
+ outputPath?: string;
8
+ }
9
+
10
+ // Read dory.json to get navigation order (flat list, in order)
11
+ function getNavigationOrder(docsDir: string): string[] {
12
+ const doryJsonPath = path.join(docsDir, 'dory.json');
13
+ if (!fs.existsSync(doryJsonPath)) return [];
14
+ const doryJson = JSON.parse(fs.readFileSync(doryJsonPath, 'utf-8'));
15
+ const order: string[] = [];
16
+
17
+ function walkPages(pages: any[]) {
18
+ for (const page of pages) {
19
+ if (typeof page === 'string') {
20
+ order.push(page);
21
+ } else if (typeof page === 'object' && page.pages) {
22
+ walkPages(page.pages);
23
+ }
24
+ }
25
+ }
26
+
27
+ if (doryJson.navigation && Array.isArray(doryJson.navigation.tabs)) {
28
+ for (const tab of doryJson.navigation.tabs) {
29
+ if (tab.groups && Array.isArray(tab.groups)) {
30
+ for (const group of tab.groups) {
31
+ if (group.pages && Array.isArray(group.pages)) {
32
+ walkPages(group.pages);
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ return order;
39
+ }
40
+
41
+ // Function to parse frontmatter from MDX content
42
+ function parseFrontmatter(content: string) {
43
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
44
+ if (!frontmatterMatch) return { data: {}, content };
45
+
46
+ const frontmatter = frontmatterMatch[1];
47
+ const data: Record<string, any> = {};
48
+
49
+ frontmatter.split('\n').forEach(line => {
50
+ const [key, ...valueParts] = line.split(':');
51
+ if (key && valueParts.length) {
52
+ const value = valueParts.join(':').trim();
53
+ data[key.trim()] = value.replace(/^["']|["']$/g, '');
54
+ }
55
+ });
56
+
57
+ return { data, content: content.slice(frontmatterMatch[0].length) };
58
+ }
59
+
60
+ // Convert filename to path format used by the store
61
+ function pathFromFilename(filename: string, docsDir: string): string {
62
+ return filename
63
+ .replace(docsDir, '')
64
+ .replace(/\/?index\.mdx$/, '/')
65
+ .replace(/\.mdx$/, '')
66
+ .replace(/\\/g, '/')
67
+ .toLowerCase();
68
+ }
69
+
70
+ // Function to recursively find all MDX files and map by relative path (without .mdx)
71
+ function findMdxFilesMap(dir: string, baseDir: string): Record<string, string> {
72
+ const files: Record<string, string> = {};
73
+ const items = fs.readdirSync(dir, { withFileTypes: true });
74
+
75
+ for (const item of items) {
76
+ const fullPath = path.join(dir, item.name);
77
+ if (item.isDirectory()) {
78
+ Object.assign(files, findMdxFilesMap(fullPath, baseDir));
79
+ } else if (item.isFile() && item.name.endsWith('.mdx')) {
80
+ // Key: relative path without .mdx, with forward slashes
81
+ let rel = path.relative(baseDir, fullPath).replace(/\\/g, '/');
82
+ if (rel.endsWith('.mdx')) rel = rel.slice(0, -4);
83
+ files[rel] = fullPath;
84
+ }
85
+ }
86
+
87
+ return files;
88
+ }
89
+
90
+ // Function to generate frontmatter JSON in dory.json order, then append any extra files
91
+ function generateFrontmatterJson(docsDir: string): Array<Record<string, any>> {
92
+ const navOrder = getNavigationOrder(docsDir);
93
+ const mdxFilesMap = findMdxFilesMap(docsDir, docsDir);
94
+ const usedKeys = new Set<string>();
95
+ const frontmatterArray: Array<Record<string, any>> = [];
96
+
97
+ // Process files in navigation order first
98
+ for (const navKey of navOrder) {
99
+ // Try both with and without "index"
100
+ let fileKey = navKey;
101
+ if (mdxFilesMap[fileKey]) {
102
+ usedKeys.add(fileKey);
103
+ } else if (mdxFilesMap[`${fileKey}/index`]) {
104
+ fileKey = `${fileKey}/index`;
105
+ usedKeys.add(fileKey);
106
+ } else {
107
+ continue;
108
+ }
109
+
110
+ const filePath = mdxFilesMap[fileKey];
111
+ const rawContent = fs.readFileSync(filePath, 'utf-8');
112
+ const { data } = parseFrontmatter(rawContent);
113
+ const pathname = pathFromFilename(filePath, docsDir);
114
+
115
+ frontmatterArray.push({
116
+ ...data,
117
+ path: pathname
118
+ });
119
+ }
120
+
121
+ // Process any extra files not in navigation order, sorted
122
+ const extraKeys = Object.keys(mdxFilesMap).filter(k => !usedKeys.has(k)).sort();
123
+ for (const fileKey of extraKeys) {
124
+ const filePath = mdxFilesMap[fileKey];
125
+ const rawContent = fs.readFileSync(filePath, 'utf-8');
126
+ const { data } = parseFrontmatter(rawContent);
127
+ const pathname = pathFromFilename(filePath, docsDir);
128
+
129
+ frontmatterArray.push({
130
+ ...data,
131
+ path: pathname
132
+ });
133
+ }
134
+
135
+ return frontmatterArray;
136
+ }
137
+
138
+ export function frontmatterGenerator(options: FrontmatterGeneratorOptions = {}): Plugin {
139
+ return {
140
+ name: 'frontmatter-generator',
141
+ generateBundle() {
142
+ const docsDir = options.docsDir || path.join(process.cwd(), 'docs');
143
+ const frontmatterArray = generateFrontmatterJson(docsDir);
144
+ const jsonContent = JSON.stringify(frontmatterArray, null, 2);
145
+
146
+ this.emitFile({
147
+ type: 'asset',
148
+ fileName: 'frontmatter.json',
149
+ source: jsonContent
150
+ });
151
+
152
+ console.log(`✅ Generated frontmatter JSON file`);
153
+ console.log(`📄 Processed ${frontmatterArray.length} MDX files`);
154
+ console.log(`📝 Total JSON size: ${jsonContent.length} characters`);
155
+ }
156
+ };
157
+ }
@@ -0,0 +1,108 @@
1
+ import type { Plugin } from 'vite';
2
+ import { readFileSync, existsSync, statSync } from 'fs';
3
+ import { resolve, extname } from 'path';
4
+
5
+ /**
6
+ * Vite plugin to serve HTML files and assets directly from the docs directory
7
+ * and static files from the dist directory without routing them through the SPA
8
+ */
9
+ export function htmlFilesMiddleware(): Plugin {
10
+ // Define asset extensions
11
+ const assetExtensions = new Set([
12
+ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico',
13
+ '.pdf', '.mp4', '.webm', '.mp3', '.wav', '.mov',
14
+ '.woff', '.woff2', '.ttf', '.eot',
15
+ ]);
16
+
17
+ // Map extensions to MIME types
18
+ const mimeTypes: Record<string, string> = {
19
+ '.html': 'text/html',
20
+ '.js': 'application/javascript',
21
+ '.css': 'text/css',
22
+ '.json': 'application/json',
23
+ '.svg': 'image/svg+xml',
24
+ '.png': 'image/png',
25
+ '.jpg': 'image/jpeg',
26
+ '.jpeg': 'image/jpeg',
27
+ '.gif': 'image/gif',
28
+ '.webp': 'image/webp',
29
+ '.ico': 'image/x-icon',
30
+ '.pdf': 'application/pdf',
31
+ '.mp4': 'video/mp4',
32
+ '.webm': 'video/webm',
33
+ '.mov': 'video/quicktime',
34
+ '.mp3': 'audio/mpeg',
35
+ '.wav': 'audio/wav',
36
+ '.woff': 'font/woff',
37
+ '.woff2': 'font/woff2',
38
+ '.ttf': 'font/ttf',
39
+ '.eot': 'application/vnd.ms-fontobject',
40
+ };
41
+
42
+ return {
43
+ name: 'html-files-middleware',
44
+ configureServer(server) {
45
+ server.middlewares.use((req, res, next) => {
46
+ if (!req.url) {
47
+ next();
48
+ return;
49
+ }
50
+
51
+ // Remove query params
52
+ const url = req.url.split('?')[0];
53
+
54
+ // Check if the request is for a file in the dist directory
55
+ if (url.startsWith('/dist/')) {
56
+ const distPath = resolve(process.cwd(), url.slice(1));
57
+
58
+ if (existsSync(distPath)) {
59
+ try {
60
+ const content = readFileSync(distPath, 'utf-8');
61
+ const ext = extname(url).toLowerCase();
62
+ const contentType = mimeTypes[ext] || 'application/octet-stream';
63
+
64
+ res.setHeader('Content-Type', contentType);
65
+ res.setHeader('Cache-Control', 'no-cache');
66
+ res.setHeader('Access-Control-Allow-Origin', '*');
67
+ res.end(content);
68
+ return;
69
+ } catch (error) {
70
+ console.error(`Error reading ${distPath}:`, error);
71
+ }
72
+ }
73
+ }
74
+
75
+ // Check if the request is for a file in the docs directory
76
+ // This handles both direct file requests and directory requests
77
+ const docsPath = resolve(process.cwd(), 'docs', url.slice(1));
78
+
79
+ if (existsSync(docsPath)) {
80
+ try {
81
+ const stats = statSync(docsPath);
82
+
83
+ // If it's a file, serve it
84
+ if (stats.isFile()) {
85
+ const ext = extname(url).toLowerCase();
86
+
87
+ // Only serve HTML files and assets, not MDX files
88
+ if (url.endsWith('.html') || assetExtensions.has(ext)) {
89
+ const content = readFileSync(docsPath);
90
+ const contentType = mimeTypes[ext] || 'application/octet-stream';
91
+
92
+ res.setHeader('Content-Type', contentType);
93
+ res.setHeader('Cache-Control', 'no-cache');
94
+ res.setHeader('Access-Control-Allow-Origin', '*');
95
+ res.end(content);
96
+ return;
97
+ }
98
+ }
99
+ } catch (error) {
100
+ console.error(`Error reading ${docsPath}:`, error);
101
+ }
102
+ }
103
+
104
+ next();
105
+ });
106
+ },
107
+ };
108
+ }
@@ -0,0 +1,76 @@
1
+ import type { Plugin } from 'vite';
2
+ import { readFileSync, existsSync } from 'fs';
3
+ import { resolve } from 'path';
4
+
5
+ /**
6
+ * Vite plugin that injects site metadata from dory.json into index.html
7
+ * This provides proper defaults before JavaScript hydrates the page
8
+ */
9
+ export function htmlMetadataInjector(): Plugin {
10
+ return {
11
+ name: 'html-metadata-injector',
12
+ transformIndexHtml(html) {
13
+ // Read dory.json configuration
14
+ const doryJsonPath = resolve(process.cwd(), 'docs', 'dory.json');
15
+
16
+ if (!existsSync(doryJsonPath)) {
17
+ console.warn('⚠️ dory.json not found, using default metadata');
18
+ return html;
19
+ }
20
+
21
+ try {
22
+ const config = JSON.parse(readFileSync(doryJsonPath, 'utf-8'));
23
+ const siteName = config.name || 'Documentation';
24
+ const defaultTitle = config.title || siteName;
25
+ const defaultDescription = config.description || `${siteName} - Technical Documentation`;
26
+
27
+ // Replace title
28
+ html = html.replace(
29
+ /<title>.*?<\/title>/,
30
+ `<title>${defaultTitle}</title>`
31
+ );
32
+
33
+ // Replace meta description
34
+ html = html.replace(
35
+ /<meta name="description" content=".*?" \/>/,
36
+ `<meta name="description" content="${defaultDescription}" />`
37
+ );
38
+
39
+ // Replace Open Graph title
40
+ html = html.replace(
41
+ /<meta property="og:title" content=".*?" \/>/,
42
+ `<meta property="og:title" content="${defaultTitle}" />`
43
+ );
44
+
45
+ // Replace Open Graph description
46
+ html = html.replace(
47
+ /<meta property="og:description" content=".*?" \/>/,
48
+ `<meta property="og:description" content="${defaultDescription}" />`
49
+ );
50
+
51
+ // Replace Open Graph site name
52
+ html = html.replace(
53
+ /<meta property="og:site_name" content=".*?" \/>/,
54
+ `<meta property="og:site_name" content="${siteName}" />`
55
+ );
56
+
57
+ // Replace Twitter title
58
+ html = html.replace(
59
+ /<meta name="twitter:title" content=".*?" \/>/,
60
+ `<meta name="twitter:title" content="${defaultTitle}" />`
61
+ );
62
+
63
+ // Replace Twitter description
64
+ html = html.replace(
65
+ /<meta name="twitter:description" content=".*?" \/>/,
66
+ `<meta name="twitter:description" content="${defaultDescription}" />`
67
+ );
68
+
69
+ return html;
70
+ } catch (error) {
71
+ console.error('❌ Failed to parse dory.json for metadata injection:', error);
72
+ return html;
73
+ }
74
+ },
75
+ };
76
+ }