@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.
- package/bin/dist/dory.js +1 -8
- package/bin/dist/dory.js.map +1 -1
- package/index.html +54 -0
- package/package.json +12 -4
- package/src/app.css +5 -0
- package/src/app.tsx +13 -0
- package/src/components/dropdown.tsx +53 -0
- package/src/components/hooks.tsx +36 -0
- package/src/components/loading.tsx +21 -0
- package/src/components/mdx-provider.tsx +13 -0
- package/src/components/navigation.tsx +58 -0
- package/src/components/notification.tsx +13 -0
- package/src/components/routes.tsx +92 -0
- package/src/components/search-index.ts +43 -0
- package/src/components/search.tsx +448 -0
- package/src/components/store.ts +216 -0
- package/src/config/mdx.js +12 -0
- package/src/config/mdx.ts +12 -0
- package/src/embed/loader.ts +200 -0
- package/src/embed/types.ts +54 -0
- package/src/embed/widget-iframe.ts +302 -0
- package/src/main.tsx +22 -0
- package/src/mdx/accordion.tsx +41 -0
- package/src/mdx/api-playground.tsx +659 -0
- package/src/mdx/api.tsx +354 -0
- package/src/mdx/async-api.tsx +353 -0
- package/src/mdx/callout.tsx +64 -0
- package/src/mdx/card.tsx +54 -0
- package/src/mdx/code.tsx +289 -0
- package/src/mdx/expandable.tsx +36 -0
- package/src/mdx/fence.tsx +74 -0
- package/src/mdx/icon.tsx +73 -0
- package/src/mdx/latex.tsx +26 -0
- package/src/mdx/list.tsx +7 -0
- package/src/mdx/mdx.tsx +47 -0
- package/src/mdx/mermaid.tsx +147 -0
- package/src/mdx/open-api.tsx +311 -0
- package/src/mdx/processor.ts +174 -0
- package/src/mdx/source.tsx +58 -0
- package/src/mdx/step.tsx +51 -0
- package/src/mdx/table.tsx +20 -0
- package/src/mdx/tag.tsx +53 -0
- package/src/mdx/text.tsx +99 -0
- package/src/mdx/websocket-playground.tsx +824 -0
- package/src/plugins/docs-assets.ts +79 -0
- package/src/plugins/frontmatter-dev-server.ts +158 -0
- package/src/plugins/frontmatter-generator.ts +157 -0
- package/src/plugins/html-files-middleware.ts +108 -0
- package/src/plugins/html-metadata-injector.ts +76 -0
- package/src/plugins/llm-txt-dev-server.ts +183 -0
- package/src/plugins/llm-txt-generator.ts +186 -0
- package/src/plugins/sanitize.ts +163 -0
- package/src/plugins/unified-mdx-plugin.ts +35 -0
- package/src/sdk/index.ts +89 -0
- package/src/sdk/react.tsx +139 -0
- package/src/sdk/vue.ts +107 -0
- package/src/theme.scss +72 -0
- package/src/types/config.ts +89 -0
- package/src/ui/header.tsx +96 -0
- package/src/ui/layout.tsx +237 -0
- package/src/ui/mobile-navigation.tsx +72 -0
- package/src/ui/prev-next-link.tsx +74 -0
- package/src/ui/table-of-content.tsx +167 -0
- package/src/utils/colors.ts +54 -0
- package/src/utils/fonts.ts +23 -0
- package/src/utils/functions.ts +12 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.app.json +33 -0
- package/tsconfig.bin.json +25 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +26 -0
- package/vite.config.embed-loader.ts +28 -0
- package/vite.config.embed-widget.ts +31 -0
- 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
|
+
}
|