@clidey/dory 0.47.0 → 0.48.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.
- package/bin/dist/dory.js +103 -45
- package/bin/dist/dory.js.map +1 -1
- package/index.html +11 -15
- package/package.json +35 -29
- package/scripts/render-routes.mjs +45 -14
- package/src/components/routes.tsx +19 -22
- package/src/components/store.ts +5 -5
- package/src/config/mdx.js +7 -3
- package/src/config/mdx.ts +6 -2
- package/src/entry-server.tsx +53 -20
- package/src/main.tsx +9 -1
- package/src/mdx/code.tsx +8 -5
- package/src/mdx/fence.tsx +10 -2
- package/src/mdx/icon.tsx +21 -14
- package/src/mdx/mermaid.tsx +10 -1
- package/src/mdx/processor.test.ts +47 -0
- package/src/mdx/step.tsx +3 -3
- package/src/plugins/analytics-injector.ts +7 -5
- package/src/plugins/docs-assets.ts +5 -4
- package/src/plugins/frontmatter-dev-server.ts +8 -159
- package/src/plugins/frontmatter-generator.ts +37 -129
- package/src/plugins/html-files-middleware.test.ts +21 -0
- package/src/plugins/html-files-middleware.ts +27 -5
- package/src/plugins/html-metadata-injector.ts +94 -53
- package/src/plugins/llm-txt-dev-server.ts +25 -178
- package/src/plugins/llm-txt-generator.ts +94 -133
- package/src/plugins/prerender.ts +156 -50
- package/src/plugins/robots-generator.ts +15 -16
- package/src/plugins/sanitize.test.ts +104 -0
- package/src/plugins/sanitize.ts +90 -59
- package/src/plugins/shared.ts +139 -0
- package/src/plugins/sitemap-generator.ts +58 -44
- package/src/theme.scss +138 -15
- package/src/types/config.ts +42 -2
- package/src/types/globals.d.ts +7 -0
- package/src/ui/header.tsx +12 -4
- package/src/ui/layout.tsx +66 -47
- package/src/ui/table-of-content.tsx +19 -2
- package/src/utils/colors.ts +38 -18
- package/src/utils/fonts.ts +55 -12
- package/src/utils/theme.ts +29 -0
- package/vite.config.embed-loader.ts +3 -11
- package/vite.config.embed-widget.ts +3 -10
- package/vite.config.ts +11 -7
- package/scripts/generate-frontmatter-json.js +0 -154
- package/scripts/generate-llm-txt.js +0 -115
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import docsConfig from '../../docs/dory.json' with { type: 'json' };
|
|
2
|
+
import type { DoryConfig } from '../types/config';
|
|
3
|
+
|
|
4
|
+
const config = docsConfig as DoryConfig;
|
|
5
|
+
|
|
6
|
+
export function loadTheme() {
|
|
7
|
+
const root = document.documentElement;
|
|
8
|
+
const { theme, colors } = config;
|
|
9
|
+
|
|
10
|
+
// Apply forced dark/light mode
|
|
11
|
+
if (theme?.mode === 'dark') {
|
|
12
|
+
root.classList.add('dark');
|
|
13
|
+
root.style.colorScheme = 'dark';
|
|
14
|
+
} else if (theme?.mode === 'light') {
|
|
15
|
+
root.classList.remove('dark');
|
|
16
|
+
root.style.colorScheme = 'light';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Apply border radius
|
|
20
|
+
if (theme?.radius) {
|
|
21
|
+
root.style.setProperty('--radius', theme.radius);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Mark as themed if custom background colors are provided
|
|
25
|
+
const hasCustomColors = colors?.background || colors?.surface || colors?.foreground;
|
|
26
|
+
if (hasCustomColors) {
|
|
27
|
+
root.setAttribute('data-dory-themed', 'true');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -13,16 +13,8 @@ export default defineConfig({
|
|
|
13
13
|
},
|
|
14
14
|
outDir: 'dist',
|
|
15
15
|
emptyOutDir: false,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
drop_console: false, // Keep console for user feedback
|
|
20
|
-
drop_debugger: true,
|
|
21
|
-
pure_funcs: ['console.debug'],
|
|
22
|
-
},
|
|
23
|
-
mangle: {
|
|
24
|
-
safari10: true,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
16
|
+
// esbuild minification: works from the published package (terser is never
|
|
17
|
+
// installed for consumers since it's only an optional peer of vite)
|
|
18
|
+
minify: 'esbuild',
|
|
27
19
|
},
|
|
28
20
|
});
|
|
@@ -11,16 +11,9 @@ export default defineConfig({
|
|
|
11
11
|
},
|
|
12
12
|
outDir: 'dist',
|
|
13
13
|
emptyOutDir: false,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
drop_console: false,
|
|
18
|
-
drop_debugger: true,
|
|
19
|
-
},
|
|
20
|
-
mangle: {
|
|
21
|
-
safari10: true,
|
|
22
|
-
},
|
|
23
|
-
},
|
|
14
|
+
// esbuild minification: works from the published package (terser is never
|
|
15
|
+
// installed for consumers since it's only an optional peer of vite)
|
|
16
|
+
minify: 'esbuild',
|
|
24
17
|
rollupOptions: {
|
|
25
18
|
output: {
|
|
26
19
|
format: 'es',
|
package/vite.config.ts
CHANGED
|
@@ -46,18 +46,22 @@ export default defineConfig(({ command }) => ({
|
|
|
46
46
|
build: {
|
|
47
47
|
rollupOptions: {
|
|
48
48
|
output: {
|
|
49
|
-
manualChunks
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'vendor-
|
|
53
|
-
'
|
|
54
|
-
'
|
|
49
|
+
// Vite 8 (Rolldown) only supports the function form of manualChunks
|
|
50
|
+
manualChunks(id: string) {
|
|
51
|
+
if (!id.includes('node_modules')) return;
|
|
52
|
+
if (/node_modules\/preact($|\/)/.test(id)) return 'vendor-preact';
|
|
53
|
+
if (id.includes('node_modules/@clidey/ux') || id.includes('node_modules/@headlessui/react')) return 'vendor-ui';
|
|
54
|
+
if (id.includes('node_modules/lucide-react')) return 'vendor-icons';
|
|
55
|
+
if (id.includes('node_modules/katex')) return 'katex';
|
|
56
|
+
if (id.includes('node_modules/flexsearch')) return 'flexsearch';
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
},
|
|
59
61
|
server: {
|
|
60
|
-
|
|
62
|
+
// Vite's default host allowlist (DNS-rebinding protection) applies.
|
|
63
|
+
// To serve behind a proxy with a custom hostname, add `server.allowedHosts`
|
|
64
|
+
// in a local override or via Vite CLI flags.
|
|
61
65
|
port: 3000,
|
|
62
66
|
strictPort: false,
|
|
63
67
|
},
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
|
-
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
const __dirname = path.dirname(__filename);
|
|
9
|
-
|
|
10
|
-
// Read dory.json to get navigation order (flat list, in order)
|
|
11
|
-
function getNavigationOrder(docsDir) {
|
|
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 = [];
|
|
16
|
-
|
|
17
|
-
function walkPages(pages) {
|
|
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) {
|
|
43
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
44
|
-
if (!frontmatterMatch) return { data: {}, content };
|
|
45
|
-
|
|
46
|
-
const frontmatter = frontmatterMatch[1];
|
|
47
|
-
const data = {};
|
|
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, docsDir) {
|
|
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, baseDir) {
|
|
72
|
-
const files = {};
|
|
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
|
-
// Main function to generate frontmatter JSON
|
|
91
|
-
async function generateFrontmatterJson() {
|
|
92
|
-
const docsDir = path.join(__dirname, '..', 'docs');
|
|
93
|
-
const outputDir = path.join(__dirname, '..', 'dist');
|
|
94
|
-
|
|
95
|
-
// Ensure output directory exists
|
|
96
|
-
if (!fs.existsSync(outputDir)) {
|
|
97
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const navOrder = getNavigationOrder(docsDir);
|
|
101
|
-
const mdxFilesMap = findMdxFilesMap(docsDir, docsDir);
|
|
102
|
-
const usedKeys = new Set();
|
|
103
|
-
const frontmatterArray = [];
|
|
104
|
-
|
|
105
|
-
// Process files in navigation order first
|
|
106
|
-
for (const navKey of navOrder) {
|
|
107
|
-
// Try both with and without "index"
|
|
108
|
-
let fileKey = navKey;
|
|
109
|
-
if (mdxFilesMap[fileKey]) {
|
|
110
|
-
usedKeys.add(fileKey);
|
|
111
|
-
} else if (mdxFilesMap[`${fileKey}/index`]) {
|
|
112
|
-
fileKey = `${fileKey}/index`;
|
|
113
|
-
usedKeys.add(fileKey);
|
|
114
|
-
} else {
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const filePath = mdxFilesMap[fileKey];
|
|
119
|
-
const rawContent = fs.readFileSync(filePath, 'utf-8');
|
|
120
|
-
const { data } = parseFrontmatter(rawContent);
|
|
121
|
-
const pathname = pathFromFilename(filePath, docsDir);
|
|
122
|
-
|
|
123
|
-
frontmatterArray.push({
|
|
124
|
-
...data,
|
|
125
|
-
path: pathname
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Process any extra files not in navigation order, sorted
|
|
130
|
-
const extraKeys = Object.keys(mdxFilesMap).filter(k => !usedKeys.has(k)).sort();
|
|
131
|
-
for (const fileKey of extraKeys) {
|
|
132
|
-
const filePath = mdxFilesMap[fileKey];
|
|
133
|
-
const rawContent = fs.readFileSync(filePath, 'utf-8');
|
|
134
|
-
const { data } = parseFrontmatter(rawContent);
|
|
135
|
-
const pathname = pathFromFilename(filePath, docsDir);
|
|
136
|
-
|
|
137
|
-
frontmatterArray.push({
|
|
138
|
-
...data,
|
|
139
|
-
path: pathname
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Write to output file
|
|
144
|
-
const jsonContent = JSON.stringify(frontmatterArray, null, 2);
|
|
145
|
-
const outputPath = path.join(outputDir, 'frontmatter.json');
|
|
146
|
-
fs.writeFileSync(outputPath, jsonContent);
|
|
147
|
-
|
|
148
|
-
console.log(`✅ Generated frontmatter JSON file: ${outputPath}`);
|
|
149
|
-
console.log(`📄 Processed ${frontmatterArray.length} MDX files`);
|
|
150
|
-
console.log(`📝 Total JSON size: ${jsonContent.length} characters`);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Run the script
|
|
154
|
-
generateFrontmatterJson().catch(console.error);
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
|
-
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
const __dirname = path.dirname(__filename);
|
|
9
|
-
|
|
10
|
-
// Function to parse frontmatter from MDX content
|
|
11
|
-
function parseFrontmatter(content) {
|
|
12
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
|
|
13
|
-
if (!frontmatterMatch) return { data: {}, content };
|
|
14
|
-
|
|
15
|
-
const frontmatter = frontmatterMatch[1];
|
|
16
|
-
const data = {};
|
|
17
|
-
|
|
18
|
-
frontmatter.split('\n').forEach(line => {
|
|
19
|
-
const [key, ...valueParts] = line.split(':');
|
|
20
|
-
if (key && valueParts.length) {
|
|
21
|
-
const value = valueParts.join(':').trim();
|
|
22
|
-
data[key.trim()] = value.replace(/^["']|["']$/g, '');
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
return { data, content: content.slice(frontmatterMatch[0].length) };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Function to recursively find all MDX files
|
|
30
|
-
function findMdxFiles(dir) {
|
|
31
|
-
const files = [];
|
|
32
|
-
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
33
|
-
|
|
34
|
-
for (const item of items) {
|
|
35
|
-
const fullPath = path.join(dir, item.name);
|
|
36
|
-
if (item.isDirectory()) {
|
|
37
|
-
files.push(...findMdxFiles(fullPath));
|
|
38
|
-
} else if (item.isFile() && item.name.endsWith('.mdx')) {
|
|
39
|
-
files.push(fullPath);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return files;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Function to clean MDX content for LLM consumption
|
|
47
|
-
function cleanMdxContent(content) {
|
|
48
|
-
// Remove JSX components but keep their content
|
|
49
|
-
let cleaned = content
|
|
50
|
-
// Remove JSX opening/closing tags but keep content
|
|
51
|
-
.replace(/<(\w+)([^>]*?)>/g, '')
|
|
52
|
-
.replace(/<\/\w+>/g, '')
|
|
53
|
-
// Remove code block language specifiers
|
|
54
|
-
.replace(/```(\w+)/g, '```')
|
|
55
|
-
// Remove HTML comments
|
|
56
|
-
.replace(/<!--[\s\S]*?-->/g, '')
|
|
57
|
-
// Remove excessive whitespace
|
|
58
|
-
.replace(/\n\s*\n\s*\n/g, '\n\n')
|
|
59
|
-
// Remove leading/trailing whitespace
|
|
60
|
-
.trim();
|
|
61
|
-
|
|
62
|
-
return cleaned;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Main function to generate LLM text
|
|
66
|
-
async function generateLlmTxt() {
|
|
67
|
-
const docsDir = path.join(__dirname, '..', 'docs');
|
|
68
|
-
const outputDir = path.join(__dirname, '..', 'dist');
|
|
69
|
-
|
|
70
|
-
// Ensure output directory exists
|
|
71
|
-
if (!fs.existsSync(outputDir)) {
|
|
72
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const mdxFiles = findMdxFiles(docsDir);
|
|
76
|
-
let aggregatedContent = '';
|
|
77
|
-
|
|
78
|
-
// Sort files for consistent output
|
|
79
|
-
mdxFiles.sort();
|
|
80
|
-
|
|
81
|
-
for (const filePath of mdxFiles) {
|
|
82
|
-
const rawContent = fs.readFileSync(filePath, 'utf-8');
|
|
83
|
-
const { data, content } = parseFrontmatter(rawContent);
|
|
84
|
-
const cleanedContent = cleanMdxContent(content);
|
|
85
|
-
|
|
86
|
-
// Add file header
|
|
87
|
-
const relativePath = path.relative(docsDir, filePath);
|
|
88
|
-
aggregatedContent += `\n# ${relativePath}\n`;
|
|
89
|
-
|
|
90
|
-
// Add title if available
|
|
91
|
-
if (data.title) {
|
|
92
|
-
aggregatedContent += `## ${data.title}\n\n`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Add description if available
|
|
96
|
-
if (data.description) {
|
|
97
|
-
aggregatedContent += `${data.description}\n\n`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Add cleaned content
|
|
101
|
-
aggregatedContent += cleanedContent + '\n\n';
|
|
102
|
-
aggregatedContent += '---\n\n';
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Write to output file
|
|
106
|
-
const outputPath = path.join(outputDir, 'llms.txt');
|
|
107
|
-
fs.writeFileSync(outputPath, aggregatedContent.trim());
|
|
108
|
-
|
|
109
|
-
console.log(`✅ Generated LLM text file: ${outputPath}`);
|
|
110
|
-
console.log(`📄 Processed ${mdxFiles.length} MDX files`);
|
|
111
|
-
console.log(`📝 Total content size: ${aggregatedContent.length} characters`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Run the script
|
|
115
|
-
generateLlmTxt().catch(console.error);
|