@clidey/dory 0.21.1 → 0.21.2
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/.claude/settings.local.json +12 -0
- package/CLAUDE.md +17 -0
- package/README.md +22 -9
- package/bin/{dory.js → dory.ts} +93 -23
- package/k8s/Dockerfile +1 -1
- package/package.json +9 -5
- package/public/demo.mov +0 -0
- package/src/config/mdx.js +12 -0
- package/src/config/mdx.ts +12 -0
- package/src/mdx/processor.ts +174 -0
- package/src/plugins/sanitize.ts +18 -4
- package/src/plugins/unified-mdx-plugin.ts +35 -0
- package/tsconfig.bin.json +20 -0
- package/vite.config.ts +7 -9
package/CLAUDE.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
4
|
|
|
5
|
+
## Communication Style
|
|
6
|
+
- Maintain a professional, neutral tone in all communications
|
|
7
|
+
- Use exclamation points sparingly and only when genuinely necessary
|
|
8
|
+
- Approach problems with the measured perspective of an experienced software engineer
|
|
9
|
+
- Keep a level head when discussing technical challenges and solutions
|
|
10
|
+
- Focus on clear, factual explanations without unnecessary enthusiasm
|
|
11
|
+
|
|
12
|
+
## Development requirements
|
|
13
|
+
To develop Dory, follow the below requirements every time you do a task:
|
|
14
|
+
1. Clean code is paramount—make sure it is easy to understand and follow
|
|
15
|
+
2. Do not overengineer if you can help it—only add what is required.
|
|
16
|
+
3. Do not remove or modify existing functionally UNLESS you have to and UNLESS you can justify it.
|
|
17
|
+
4. Do not change existing variable names UNLESS absolutely necessary.
|
|
18
|
+
5. Do not leave unused code lying around.
|
|
19
|
+
6. Ask as many questions as you have to in order to understand your task.
|
|
20
|
+
7. You MUST use multiple subagents wherever possible to help you accomplish your task faster.
|
|
21
|
+
|
|
5
22
|
## Project Overview
|
|
6
23
|
|
|
7
24
|
Dory is a lightweight static site generator for technical documentation, published as `@clidey/dory` on npm. It uses Preact, Vite, TypeScript, and Tailwind CSS.
|
package/README.md
CHANGED
|
@@ -10,7 +10,6 @@ Dory is a lightweight static site generator built for developers who want fast,
|
|
|
10
10
|
|
|
11
11
|
Built with [Preact](https://preactjs.com/), [Vite](https://vitejs.dev/), [Tailwind CSS](https://tailwindcss.com/), [Mermaid](https://mermaid.js.org/), and [TypeScript](https://www.typescriptlang.org/).
|
|
12
12
|
|
|
13
|
-
---
|
|
14
13
|
|
|
15
14
|
## 🚀 Why Dory?
|
|
16
15
|
|
|
@@ -23,7 +22,10 @@ Dory is:
|
|
|
23
22
|
- 🌐 **Portable** — No SSR, no lock-in, deploy anywhere.
|
|
24
23
|
- 🧩 **Flexible** — Hackable theme, readable codebase, minimal magic.
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
|
|
26
|
+
## 🎬 Quick Demo
|
|
27
|
+
|
|
28
|
+
https://github.com/user-attachments/assets/5a2840ce-a0b9-41fd-8d15-3ee1d2356f07
|
|
27
29
|
|
|
28
30
|
## ✨ Features
|
|
29
31
|
|
|
@@ -36,7 +38,6 @@ Dory is:
|
|
|
36
38
|
- 🌍 Deploy to Netlify, Vercel, S3, GitHub Pages — your call
|
|
37
39
|
- 🌐 HTTP client for testing API endpoints (automatic inference from openapi.json)
|
|
38
40
|
|
|
39
|
-
---
|
|
40
41
|
|
|
41
42
|
## 📦 CLI Installation
|
|
42
43
|
|
|
@@ -71,6 +72,24 @@ Previews the built documentation site:
|
|
|
71
72
|
dory preview
|
|
72
73
|
```
|
|
73
74
|
|
|
75
|
+
#### `dory verify:content`
|
|
76
|
+
Verifies that MDX content compiles without errors:
|
|
77
|
+
- Silent on success (no output means no errors)
|
|
78
|
+
- Shows detailed error messages on failure
|
|
79
|
+
- Uses the same preprocessing as the main build
|
|
80
|
+
- Perfect for automated testing and debugging
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Verify content directly
|
|
84
|
+
dory verify:content --content "# Hello World\n\nThis is a test."
|
|
85
|
+
|
|
86
|
+
# Verify content from file (recommended for complex content)
|
|
87
|
+
dory verify:content --file content.mdx
|
|
88
|
+
|
|
89
|
+
# Development repository usage:
|
|
90
|
+
pnpm exec tsx bin/dory.ts verify:content --file content.mdx
|
|
91
|
+
```
|
|
92
|
+
|
|
74
93
|
#### `dory help`
|
|
75
94
|
Shows CLI usage information:
|
|
76
95
|
|
|
@@ -78,7 +97,6 @@ Shows CLI usage information:
|
|
|
78
97
|
dory help
|
|
79
98
|
```
|
|
80
99
|
|
|
81
|
-
---
|
|
82
100
|
|
|
83
101
|
## 🧑💻 Development Setup
|
|
84
102
|
|
|
@@ -93,7 +111,6 @@ git clone https://github.com/clidey/dory.git
|
|
|
93
111
|
cd dory
|
|
94
112
|
````
|
|
95
113
|
|
|
96
|
-
---
|
|
97
114
|
|
|
98
115
|
### 2️⃣ Install Dependencies
|
|
99
116
|
|
|
@@ -146,7 +163,6 @@ pnpm run build
|
|
|
146
163
|
|
|
147
164
|
This will create a `dist` directory containing the fully built static site, ready to be served.
|
|
148
165
|
|
|
149
|
-
---
|
|
150
166
|
|
|
151
167
|
## 🐳 Docker Deployment (Optional)
|
|
152
168
|
|
|
@@ -176,8 +192,6 @@ docker run -it -p 8080:80 docs:1.0.0
|
|
|
176
192
|
|
|
177
193
|
> The site will be available at `http://localhost:8080` inside your browser.
|
|
178
194
|
|
|
179
|
-
---
|
|
180
|
-
|
|
181
195
|
## 📂 Notes
|
|
182
196
|
|
|
183
197
|
* `pnpm run build` creates the static site inside the `dist` folder, which can be served using any static file server.
|
|
@@ -192,4 +206,3 @@ We’re actively improving Dory. Here’s what’s on deck:
|
|
|
192
206
|
* [ ] 🎨 **Themes** — full theming support with a flexible theme API
|
|
193
207
|
* [ ] 🌐 **Multi-language Support** — internationalization (i18n) & localization (l10n)
|
|
194
208
|
* [ ] 🚀 **GraphQL Client** — integrated GraphQL playground and client support
|
|
195
|
-
* [ ] 🔄 **WebSocket Client** — built-in WebSocket utilities for real-time API demos
|
package/bin/{dory.js → dory.ts}
RENAMED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
2
|
|
|
3
|
+
// @ts-nocheck - gang
|
|
3
4
|
import { execSync } from 'child_process';
|
|
4
5
|
import { existsSync, rmSync, mkdirSync, cpSync, readFileSync, readdirSync } from 'fs';
|
|
5
6
|
import { resolve, dirname } from 'path';
|
|
6
|
-
import { fileURLToPath } from 'url'
|
|
7
|
-
import
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { createServer } from 'http';
|
|
8
9
|
import sirv from 'sirv';
|
|
9
10
|
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -108,7 +109,7 @@ const commands = {
|
|
|
108
109
|
brotli: true,
|
|
109
110
|
});
|
|
110
111
|
|
|
111
|
-
const server =
|
|
112
|
+
const server = createServer((req, res) => {
|
|
112
113
|
serve(req, res);
|
|
113
114
|
});
|
|
114
115
|
|
|
@@ -117,6 +118,61 @@ const commands = {
|
|
|
117
118
|
});
|
|
118
119
|
},
|
|
119
120
|
|
|
121
|
+
'verify:content': async () => {
|
|
122
|
+
const args = process.argv.slice(3);
|
|
123
|
+
let content = '';
|
|
124
|
+
let fileName = '';
|
|
125
|
+
|
|
126
|
+
// Parse arguments
|
|
127
|
+
for (let i = 0; i < args.length; i++) {
|
|
128
|
+
if (args[i] === '--content' && i + 1 < args.length) {
|
|
129
|
+
content = args[i + 1];
|
|
130
|
+
break;
|
|
131
|
+
} else if (args[i] === '--file' && i + 1 < args.length) {
|
|
132
|
+
const filePath = args[i + 1];
|
|
133
|
+
if (!existsSync(filePath)) {
|
|
134
|
+
console.error(`❌ Error: File not found: ${filePath}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
content = readFileSync(filePath, 'utf8');
|
|
138
|
+
fileName = filePath;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!content) {
|
|
144
|
+
console.error('❌ Error: --content or --file argument is required');
|
|
145
|
+
console.log('Usage: dory verify:content --content "<mdx-content>" | --file <path-to-mdx-file>');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
// Use the shared MDX processor that matches the main build exactly
|
|
151
|
+
const { verifyMdxContent } = await import('../src/mdx/processor.ts');
|
|
152
|
+
const result = await verifyMdxContent(content, fileName);
|
|
153
|
+
|
|
154
|
+
if (!result.valid) {
|
|
155
|
+
throw result.error;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Silent success - no output means no errors
|
|
159
|
+
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('❌ MDX compilation failed:');
|
|
162
|
+
console.error(error.message);
|
|
163
|
+
|
|
164
|
+
// Provide more detailed error information if available
|
|
165
|
+
if (error.line !== undefined && error.column !== undefined) {
|
|
166
|
+
console.error(` at line ${error.line}, column ${error.column}`);
|
|
167
|
+
}
|
|
168
|
+
if (error.source) {
|
|
169
|
+
console.error(' Source:', error.source);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
120
176
|
help: () => {
|
|
121
177
|
console.log(`
|
|
122
178
|
🐟 Dory CLI - Your Friendly Documentation Builder
|
|
@@ -125,19 +181,26 @@ Usage:
|
|
|
125
181
|
dory <command>
|
|
126
182
|
|
|
127
183
|
Commands:
|
|
128
|
-
build
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
184
|
+
build Build your documentation site
|
|
185
|
+
- Needs dory.json in your project
|
|
186
|
+
- Creates a beautiful dist folder
|
|
187
|
+
|
|
188
|
+
verify:content Verify that MDX content compiles without errors
|
|
189
|
+
- Silent on success, shows errors on failure
|
|
190
|
+
- Uses the same preprocessing as the main build
|
|
191
|
+
- Accepts --content or --file arguments
|
|
192
|
+
|
|
193
|
+
preview Preview your built documentation
|
|
194
|
+
- Shows your docs in the browser
|
|
195
|
+
- Run build first!
|
|
196
|
+
|
|
197
|
+
help Show this help message
|
|
137
198
|
|
|
138
199
|
Examples:
|
|
139
|
-
dory build
|
|
140
|
-
dory
|
|
200
|
+
dory build # Build your docs
|
|
201
|
+
dory verify:content --content "# Hello World" # Verify MDX content
|
|
202
|
+
dory verify:content --file content.mdx # Verify MDX from file
|
|
203
|
+
dory preview # Preview your docs
|
|
141
204
|
`);
|
|
142
205
|
}
|
|
143
206
|
};
|
|
@@ -145,12 +208,19 @@ Examples:
|
|
|
145
208
|
// Parse command line arguments
|
|
146
209
|
const command = process.argv[2];
|
|
147
210
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
commands[command]
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
211
|
+
async function runCommand() {
|
|
212
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
213
|
+
commands.help();
|
|
214
|
+
} else if (commands[command]) {
|
|
215
|
+
await commands[command]();
|
|
216
|
+
} else {
|
|
217
|
+
console.error(`❌ Unknown command: ${command}`);
|
|
218
|
+
console.log('Run "dory help" for usage information');
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
runCommand().catch((error) => {
|
|
224
|
+
console.error('❌ Command failed:', error.message);
|
|
155
225
|
process.exit(1);
|
|
156
|
-
}
|
|
226
|
+
});
|
package/k8s/Dockerfile
CHANGED
|
@@ -8,7 +8,7 @@ RUN apk add --no-cache git
|
|
|
8
8
|
COPY ./docs/logo/logo.svg /app/docs/logo/logo.svg
|
|
9
9
|
COPY ./k8s/config/nginx-default.conf /app/k8s/config/nginx-default.conf
|
|
10
10
|
|
|
11
|
-
RUN npm install -g @clidey/dory@0.21.
|
|
11
|
+
RUN npm install -g @clidey/dory@0.21.1
|
|
12
12
|
EXPOSE 3000
|
|
13
13
|
|
|
14
14
|
CMD ["dory", "preview"]
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clidey/dory",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.21.
|
|
4
|
+
"version": "0.21.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A CLI tool for building and previewing documentation sites",
|
|
7
7
|
"keywords": [
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"static-site"
|
|
13
13
|
],
|
|
14
14
|
"bin": {
|
|
15
|
-
"dory": "./bin/dory.
|
|
15
|
+
"dory": "./bin/dory.ts"
|
|
16
16
|
},
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
|
40
40
|
"@headlessui/react": "^2.2.4",
|
|
41
41
|
"@heroicons/react": "^2.2.0",
|
|
42
|
+
"@mdx-js/mdx": "^3.1.0",
|
|
42
43
|
"@mdx-js/preact": "^3.1.0",
|
|
43
44
|
"@mdx-js/rollup": "^3.1.0",
|
|
44
45
|
"@preact/preset-vite": "^2.10.1",
|
|
@@ -60,12 +61,15 @@
|
|
|
60
61
|
"sass-embedded": "^1.89.2",
|
|
61
62
|
"sirv": "^3.0.1",
|
|
62
63
|
"tailwindcss": "^4.1.8",
|
|
64
|
+
"tsx": "^4.20.4",
|
|
63
65
|
"typescript": "~5.8.3",
|
|
64
66
|
"unist-util-visit": "^5.0.0",
|
|
65
67
|
"vite": "^6.3.5",
|
|
66
68
|
"vite-plugin-mdx": "^3.6.1",
|
|
67
|
-
"wouter-preact": "^3.7.1"
|
|
68
|
-
"@types/node": "^24.0.12"
|
|
69
|
+
"wouter-preact": "^3.7.1"
|
|
69
70
|
},
|
|
70
|
-
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
|
|
71
|
+
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/node": "^24.0.12"
|
|
74
|
+
}
|
|
71
75
|
}
|
package/public/demo.mov
ADDED
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import remarkGfm from 'remark-gfm';
|
|
2
|
+
import { remarkSafeVars } from '../plugins/sanitize.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared MDX configuration used by both main build (vite.config.ts)
|
|
6
|
+
* and CLI build:file command (bin/dory.js)
|
|
7
|
+
*/
|
|
8
|
+
export const getMdxConfig = (isDevelopment = false) => ({
|
|
9
|
+
providerImportSource: '@mdx-js/preact',
|
|
10
|
+
remarkPlugins: [remarkGfm, remarkSafeVars],
|
|
11
|
+
development: isDevelopment,
|
|
12
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import remarkGfm from 'remark-gfm';
|
|
2
|
+
import { remarkSafeVars } from '../plugins/sanitize';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared MDX configuration used by both main build (vite.config.ts)
|
|
6
|
+
* and CLI build:file command (bin/dory.js)
|
|
7
|
+
*/
|
|
8
|
+
export const getMdxConfig = (isDevelopment: boolean = false) => ({
|
|
9
|
+
providerImportSource: '@mdx-js/preact',
|
|
10
|
+
remarkPlugins: [remarkGfm, remarkSafeVars],
|
|
11
|
+
development: isDevelopment,
|
|
12
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { compile } from '@mdx-js/mdx';
|
|
2
|
+
import { preprocessMdxTags } from '../plugins/sanitize';
|
|
3
|
+
import { getMdxConfig } from '../config/mdx';
|
|
4
|
+
|
|
5
|
+
export interface ProcessMdxOptions {
|
|
6
|
+
isDevelopment?: boolean;
|
|
7
|
+
fileName?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SINGLE SOURCE OF TRUTH for MDX preprocessing.
|
|
12
|
+
*
|
|
13
|
+
* This processor is used by:
|
|
14
|
+
* - CLI verify:content command (bin/dory.ts) - full processing + compilation
|
|
15
|
+
* - Vite build via unified-mdx-plugin.ts - preprocessing only
|
|
16
|
+
*
|
|
17
|
+
* ALL MDX processing steps should be added here to ensure consistency.
|
|
18
|
+
*/
|
|
19
|
+
export async function preprocessMdxContent(
|
|
20
|
+
content: string,
|
|
21
|
+
fileName: string = 'content.mdx'
|
|
22
|
+
): Promise<{
|
|
23
|
+
success: boolean;
|
|
24
|
+
processedContent?: string;
|
|
25
|
+
error?: Error;
|
|
26
|
+
}> {
|
|
27
|
+
try {
|
|
28
|
+
// Step 1: Apply preprocessor (handles MDX tags, code blocks, etc.)
|
|
29
|
+
const preprocessor = preprocessMdxTags();
|
|
30
|
+
let processedContent = content;
|
|
31
|
+
|
|
32
|
+
if (preprocessor.transform) {
|
|
33
|
+
const result = preprocessor.transform(content, fileName);
|
|
34
|
+
if (result && typeof result === 'object' && result.code) {
|
|
35
|
+
processedContent = result.code;
|
|
36
|
+
} else if (typeof result === 'string') {
|
|
37
|
+
processedContent = result;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Step 2: Convert URLs in angle brackets to markdown links
|
|
42
|
+
// Handles cases like <https://example.com> which MDX would interpret as JSX
|
|
43
|
+
// But preserves them inside code blocks
|
|
44
|
+
|
|
45
|
+
// Process line by line to handle code blocks correctly
|
|
46
|
+
const lines = processedContent.split('\n');
|
|
47
|
+
let inCodeBlock = false;
|
|
48
|
+
|
|
49
|
+
processedContent = lines.map(line => {
|
|
50
|
+
// Check if we're entering or leaving a code block (with optional indentation)
|
|
51
|
+
if (line.trim().startsWith('```')) {
|
|
52
|
+
inCodeBlock = !inCodeBlock;
|
|
53
|
+
return line;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Don't process lines inside code blocks
|
|
57
|
+
if (inCodeBlock) {
|
|
58
|
+
return line;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// For lines outside code blocks, preserve inline code
|
|
62
|
+
// More robust regex that handles inline code with potential spaces
|
|
63
|
+
// Matches: `...` including multi-line content
|
|
64
|
+
let result = '';
|
|
65
|
+
let lastIndex = 0;
|
|
66
|
+
|
|
67
|
+
// Find all inline code spans in the line
|
|
68
|
+
const inlineCodeRegex = /`([^`]+)`/g;
|
|
69
|
+
let match;
|
|
70
|
+
|
|
71
|
+
while ((match = inlineCodeRegex.exec(line)) !== null) {
|
|
72
|
+
// Process text before the inline code
|
|
73
|
+
const beforeCode = line.substring(lastIndex, match.index);
|
|
74
|
+
result += beforeCode.replace(/<(https?:\/\/[^\s>]+)>/g, '[$1]($1)');
|
|
75
|
+
|
|
76
|
+
// Keep inline code as-is
|
|
77
|
+
result += match[0];
|
|
78
|
+
|
|
79
|
+
lastIndex = match.index + match[0].length;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Process remaining text after last inline code (or entire line if no inline code)
|
|
83
|
+
const remainingText = line.substring(lastIndex);
|
|
84
|
+
result += remainingText.replace(/<(https?:\/\/[^\s>]+)>/g, '[$1]($1)');
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}).join('\n');
|
|
88
|
+
|
|
89
|
+
// Step 3: Add any future preprocessing steps here
|
|
90
|
+
// Example: processedContent = await yourNewPreprocessor(processedContent);
|
|
91
|
+
// Example: processedContent = removeHTMLComments(processedContent);
|
|
92
|
+
// Example: processedContent = expandCustomShortcodes(processedContent);
|
|
93
|
+
// ALL preprocessing added here will automatically apply to:
|
|
94
|
+
// - CLI commands (verify:content)
|
|
95
|
+
// - Vite build (pnpm run build)
|
|
96
|
+
// - Vite dev server (pnpm run dev)
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
success: true,
|
|
100
|
+
processedContent
|
|
101
|
+
};
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
error: error as Error
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Process and compile MDX content through the full pipeline.
|
|
112
|
+
* Used by CLI commands that need the compiled output.
|
|
113
|
+
*/
|
|
114
|
+
export async function processMdxContent(
|
|
115
|
+
content: string,
|
|
116
|
+
options: ProcessMdxOptions = {}
|
|
117
|
+
): Promise<{
|
|
118
|
+
success: boolean;
|
|
119
|
+
compiled?: any;
|
|
120
|
+
error?: Error;
|
|
121
|
+
processedContent?: string;
|
|
122
|
+
}> {
|
|
123
|
+
const { isDevelopment = false, fileName = 'content.mdx' } = options;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// Step 1: Preprocess
|
|
127
|
+
const preprocessResult = await preprocessMdxContent(content, fileName);
|
|
128
|
+
if (!preprocessResult.success) {
|
|
129
|
+
return {
|
|
130
|
+
success: false,
|
|
131
|
+
error: preprocessResult.error
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const processedContent = preprocessResult.processedContent || content;
|
|
136
|
+
|
|
137
|
+
// Step 2: Compile MDX with the same config as main build
|
|
138
|
+
const mdxConfig = getMdxConfig(isDevelopment);
|
|
139
|
+
const compiled = await compile(processedContent, {
|
|
140
|
+
...mdxConfig,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
success: true,
|
|
145
|
+
compiled,
|
|
146
|
+
processedContent
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
error: error as Error
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Verify MDX content can be compiled without errors.
|
|
158
|
+
* Uses the same processing pipeline as processMdxContent.
|
|
159
|
+
*/
|
|
160
|
+
export async function verifyMdxContent(
|
|
161
|
+
content: string,
|
|
162
|
+
fileName?: string
|
|
163
|
+
): Promise<{ valid: boolean; error?: Error }> {
|
|
164
|
+
const result = await processMdxContent(content, { fileName });
|
|
165
|
+
|
|
166
|
+
if (result.success) {
|
|
167
|
+
return { valid: true };
|
|
168
|
+
} else {
|
|
169
|
+
return {
|
|
170
|
+
valid: false,
|
|
171
|
+
error: result.error
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
package/src/plugins/sanitize.ts
CHANGED
|
@@ -83,9 +83,23 @@ export function preprocessMdxTags() {
|
|
|
83
83
|
// Remove leading whitespace from code fence blocks
|
|
84
84
|
let processed = code.replace(/^(\s+)(```\w*)/gm, '$2');
|
|
85
85
|
|
|
86
|
+
// Handle self-closing tags first (like <VideoPlayer />)
|
|
87
|
+
processed = processed.replace(/<([A-Za-z][A-Za-z0-9]*)[^>]*\/>/g, (match, tag, offset) => {
|
|
88
|
+
// If inside a code block, do not touch
|
|
89
|
+
if (isInsideCodeBlock(processed, offset)) {
|
|
90
|
+
return match;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// If unknown component, treat as text
|
|
94
|
+
if (!KNOWN_COMPONENTS.includes(tag)) {
|
|
95
|
+
return `\`${match}\``;
|
|
96
|
+
}
|
|
97
|
+
return match;
|
|
98
|
+
});
|
|
99
|
+
|
|
86
100
|
// Only replace unrecognized <someTag> with `"<someTag>"` if NOT inside a code block
|
|
87
|
-
// This regex finds all <tag> occurrences
|
|
88
|
-
processed = processed.replace(/<([
|
|
101
|
+
// This regex finds all <tag> occurrences (fixed to handle uppercase)
|
|
102
|
+
processed = processed.replace(/<([A-Za-z][A-Za-z0-9]*)>/g, (match, tag, offset) => {
|
|
89
103
|
// If inside a code block, do not touch
|
|
90
104
|
if (isInsideCodeBlock(processed, offset)) {
|
|
91
105
|
return match;
|
|
@@ -111,8 +125,8 @@ export function preprocessMdxTags() {
|
|
|
111
125
|
return `\`<${tag}>\``;
|
|
112
126
|
});
|
|
113
127
|
|
|
114
|
-
// This regex finds all <tag> and </tag> occurrences
|
|
115
|
-
processed = processed.replace(/<\/?([
|
|
128
|
+
// This regex finds all <tag> and </tag> occurrences (fixed to handle uppercase)
|
|
129
|
+
processed = processed.replace(/<\/?([A-Za-z][A-Za-z0-9]*)>/g, (match, tag, offset) => {
|
|
116
130
|
// If inside a code block, do not touch
|
|
117
131
|
if (isInsideCodeBlock(processed, offset)) {
|
|
118
132
|
return match;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import { preprocessMdxContent } from '../mdx/processor';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Unified MDX preprocessing plugin for Vite.
|
|
6
|
+
* This plugin ensures that ALL MDX preprocessing goes through the same pipeline
|
|
7
|
+
* as the CLI verify:content command.
|
|
8
|
+
*
|
|
9
|
+
* This is the SINGLE SOURCE OF TRUTH for MDX preprocessing.
|
|
10
|
+
* Any changes to MDX processing should be made in src/mdx/processor.ts
|
|
11
|
+
*/
|
|
12
|
+
export function unifiedMdxPreprocessor(): Plugin {
|
|
13
|
+
return {
|
|
14
|
+
name: 'unified-mdx-preprocessor',
|
|
15
|
+
enforce: 'pre', // Must run before the MDX plugin
|
|
16
|
+
async transform(code: string, id: string) {
|
|
17
|
+
if (!id.endsWith('.mdx')) return;
|
|
18
|
+
|
|
19
|
+
// Process through the unified preprocessing pipeline
|
|
20
|
+
const result = await preprocessMdxContent(code, id);
|
|
21
|
+
|
|
22
|
+
if (!result.success) {
|
|
23
|
+
// Throw the error to stop the build
|
|
24
|
+
throw result.error;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Return the preprocessed content
|
|
28
|
+
// The actual MDX compilation will be handled by @mdx-js/rollup plugin
|
|
29
|
+
return {
|
|
30
|
+
code: result.processedContent || code,
|
|
31
|
+
map: null
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2023"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"allowImportingTsExtensions": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"strict": false,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"allowSyntheticDefaultImports": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"noImplicitAny": false,
|
|
14
|
+
"noImplicitReturns": false,
|
|
15
|
+
"noImplicitThis": false
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"bin/dory.ts"
|
|
19
|
+
]
|
|
20
|
+
}
|
package/vite.config.ts
CHANGED
|
@@ -2,21 +2,19 @@ import { defineConfig } from 'vite';
|
|
|
2
2
|
import preact from '@preact/preset-vite';
|
|
3
3
|
import mdx from '@mdx-js/rollup';
|
|
4
4
|
import tailwindcss from '@tailwindcss/vite'
|
|
5
|
-
import
|
|
6
|
-
import { preprocessMdxTags, remarkSafeVars } from './src/plugins/sanitize';
|
|
5
|
+
import { unifiedMdxPreprocessor } from './src/plugins/unified-mdx-plugin';
|
|
7
6
|
import { llmTxtGenerator } from './src/plugins/llm-txt-generator';
|
|
8
7
|
import { llmTxtDevServer } from './src/plugins/llm-txt-dev-server';
|
|
9
8
|
import { frontmatterGenerator } from './src/plugins/frontmatter-generator';
|
|
10
9
|
import { frontmatterDevServer } from './src/plugins/frontmatter-dev-server';
|
|
10
|
+
import { getMdxConfig } from './src/config/mdx';
|
|
11
11
|
|
|
12
|
-
export default defineConfig({
|
|
12
|
+
export default defineConfig(({ command }) => ({
|
|
13
13
|
plugins: [
|
|
14
|
-
|
|
14
|
+
// SINGLE SOURCE OF TRUTH: All MDX preprocessing happens in src/mdx/processor.ts
|
|
15
|
+
unifiedMdxPreprocessor(),
|
|
15
16
|
preact(),
|
|
16
|
-
mdx(
|
|
17
|
-
providerImportSource: '@mdx-js/preact',
|
|
18
|
-
remarkPlugins: [remarkGfm, remarkSafeVars],
|
|
19
|
-
}),
|
|
17
|
+
mdx(getMdxConfig(command === 'serve')),
|
|
20
18
|
tailwindcss(),
|
|
21
19
|
llmTxtGenerator(),
|
|
22
20
|
llmTxtDevServer(),
|
|
@@ -33,4 +31,4 @@ export default defineConfig({
|
|
|
33
31
|
server: {
|
|
34
32
|
allowedHosts: true,
|
|
35
33
|
},
|
|
36
|
-
});
|
|
34
|
+
}));
|