skrypt-ai 0.3.4 → 0.4.1
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/README.md +1 -1
- package/dist/auth/index.d.ts +0 -1
- package/dist/auth/index.js +3 -5
- package/dist/autofix/index.js +15 -3
- package/dist/cli.js +19 -4
- package/dist/commands/check-links.js +164 -174
- package/dist/commands/deploy.js +5 -2
- package/dist/commands/generate.js +206 -199
- package/dist/commands/i18n.js +3 -20
- package/dist/commands/init.js +47 -40
- package/dist/commands/lint.js +3 -20
- package/dist/commands/mcp.js +125 -122
- package/dist/commands/monitor.js +125 -108
- package/dist/commands/review-pr.js +1 -1
- package/dist/commands/sdk.js +1 -1
- package/dist/config/loader.js +21 -2
- package/dist/generator/organizer.d.ts +3 -0
- package/dist/generator/organizer.js +4 -9
- package/dist/generator/writer.js +2 -10
- package/dist/github/pr-comments.js +21 -8
- package/dist/plugins/index.js +1 -0
- package/dist/scanner/index.js +8 -2
- package/dist/template/docs.json +2 -1
- package/dist/template/next.config.mjs +2 -1
- package/dist/template/package.json +17 -15
- package/dist/template/public/favicon.svg +4 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +120 -25
- package/dist/template/src/app/api/chat/route.ts +11 -3
- package/dist/template/src/app/docs/README.md +28 -0
- package/dist/template/src/app/docs/[...slug]/page.tsx +139 -16
- package/dist/template/src/app/docs/auth/page.mdx +589 -0
- package/dist/template/src/app/docs/autofix/page.mdx +624 -0
- package/dist/template/src/app/docs/cli/page.mdx +217 -0
- package/dist/template/src/app/docs/config/page.mdx +428 -0
- package/dist/template/src/app/docs/configuration/page.mdx +86 -0
- package/dist/template/src/app/docs/deployment/page.mdx +112 -0
- package/dist/template/src/app/docs/error.tsx +20 -0
- package/dist/template/src/app/docs/generator/generator.md +504 -0
- package/dist/template/src/app/docs/generator/organizer.md +779 -0
- package/dist/template/src/app/docs/generator/page.mdx +613 -0
- package/dist/template/src/app/docs/github/page.mdx +502 -0
- package/dist/template/src/app/docs/llm/anthropic-client.md +549 -0
- package/dist/template/src/app/docs/llm/index.md +471 -0
- package/dist/template/src/app/docs/llm/page.mdx +428 -0
- package/dist/template/src/app/docs/llms-full.md +256 -0
- package/dist/template/src/app/docs/llms.txt +2971 -0
- package/dist/template/src/app/docs/not-found.tsx +23 -0
- package/dist/template/src/app/docs/page.mdx +0 -3
- package/dist/template/src/app/docs/plugins/page.mdx +1793 -0
- package/dist/template/src/app/docs/pro/page.mdx +121 -0
- package/dist/template/src/app/docs/quickstart/page.mdx +93 -0
- package/dist/template/src/app/docs/scanner/content-type.md +599 -0
- package/dist/template/src/app/docs/scanner/index.md +212 -0
- package/dist/template/src/app/docs/scanner/page.mdx +307 -0
- package/dist/template/src/app/docs/scanner/python.md +469 -0
- package/dist/template/src/app/docs/scanner/python_parser.md +1056 -0
- package/dist/template/src/app/docs/scanner/rust.md +325 -0
- package/dist/template/src/app/docs/scanner/typescript.md +201 -0
- package/dist/template/src/app/error.tsx +3 -3
- package/dist/template/src/app/icon.tsx +29 -0
- package/dist/template/src/app/layout.tsx +42 -0
- package/dist/template/src/app/not-found.tsx +35 -0
- package/dist/template/src/app/page.tsx +62 -28
- package/dist/template/src/components/ai-chat.tsx +26 -21
- package/dist/template/src/components/breadcrumbs.tsx +46 -2
- package/dist/template/src/components/copy-button.tsx +17 -3
- package/dist/template/src/components/docs-layout.tsx +142 -8
- package/dist/template/src/components/feedback.tsx +4 -2
- package/dist/template/src/components/footer.tsx +42 -0
- package/dist/template/src/components/header.tsx +29 -5
- package/dist/template/src/components/mdx/accordion.tsx +7 -6
- package/dist/template/src/components/mdx/card.tsx +19 -7
- package/dist/template/src/components/mdx/code-block.tsx +17 -3
- package/dist/template/src/components/mdx/code-group.tsx +65 -18
- package/dist/template/src/components/mdx/code-playground.tsx +3 -0
- package/dist/template/src/components/mdx/go-playground.tsx +3 -0
- package/dist/template/src/components/mdx/highlighted-code.tsx +171 -76
- package/dist/template/src/components/mdx/python-playground.tsx +2 -0
- package/dist/template/src/components/mdx/tabs.tsx +74 -6
- package/dist/template/src/components/page-header.tsx +19 -0
- package/dist/template/src/components/scroll-to-top.tsx +33 -0
- package/dist/template/src/components/search-dialog.tsx +206 -52
- package/dist/template/src/components/sidebar.tsx +136 -77
- package/dist/template/src/components/table-of-contents.tsx +23 -7
- package/dist/template/src/lib/highlight.ts +90 -31
- package/dist/template/src/lib/search.ts +14 -4
- package/dist/template/src/lib/theme-utils.ts +140 -0
- package/dist/template/src/styles/globals.css +307 -166
- package/dist/template/src/types/remark-gfm.d.ts +2 -0
- package/dist/utils/files.d.ts +9 -0
- package/dist/utils/files.js +33 -0
- package/dist/utils/validation.d.ts +4 -0
- package/dist/utils/validation.js +38 -0
- package/package.json +1 -4
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { create, insertMultiple, save } from '@orama/orama'
|
|
2
|
-
import { readdir, readFile, writeFile, mkdir } from 'fs/promises'
|
|
3
|
-
import { join, relative } from 'path'
|
|
2
|
+
import { readdir, readFile, writeFile, mkdir, access } from 'fs/promises'
|
|
3
|
+
import { join, relative, dirname, basename } from 'path'
|
|
4
4
|
import matter from 'gray-matter'
|
|
5
5
|
|
|
6
6
|
const CONTENT_DIR = join(process.cwd(), 'content', 'docs')
|
|
7
|
+
const APP_DOCS_DIR = join(process.cwd(), 'src', 'app', 'docs')
|
|
7
8
|
const OUTPUT_DIR = join(process.cwd(), 'public')
|
|
8
9
|
|
|
10
|
+
async function dirExists(dir) {
|
|
11
|
+
try {
|
|
12
|
+
await access(dir)
|
|
13
|
+
return true
|
|
14
|
+
} catch {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
async function getAllMDXFiles(dir) {
|
|
10
20
|
const files = []
|
|
11
21
|
|
|
@@ -31,6 +41,10 @@ async function getAllMDXFiles(dir) {
|
|
|
31
41
|
|
|
32
42
|
function extractPlainText(content) {
|
|
33
43
|
return content
|
|
44
|
+
// Remove import statements
|
|
45
|
+
.replace(/^import\s+.*$/gm, '')
|
|
46
|
+
// Remove export statements
|
|
47
|
+
.replace(/^export\s+.*$/gm, '')
|
|
34
48
|
// Remove MDX/JSX components
|
|
35
49
|
.replace(/<[^>]+>/g, '')
|
|
36
50
|
// Remove code blocks
|
|
@@ -46,12 +60,15 @@ function extractPlainText(content) {
|
|
|
46
60
|
.replace(/[*_]{1,2}([^*_]+)[*_]{1,2}/g, '$1')
|
|
47
61
|
// Remove horizontal rules
|
|
48
62
|
.replace(/^[-*_]{3,}$/gm, '')
|
|
63
|
+
// Remove table formatting
|
|
64
|
+
.replace(/\|/g, ' ')
|
|
65
|
+
.replace(/^[\s-:]+$/gm, '')
|
|
49
66
|
// Remove extra whitespace
|
|
50
67
|
.replace(/\s+/g, ' ')
|
|
51
68
|
.trim()
|
|
52
69
|
}
|
|
53
70
|
|
|
54
|
-
function
|
|
71
|
+
function getSlugFromContentPath(filePath) {
|
|
55
72
|
const rel = relative(CONTENT_DIR, filePath)
|
|
56
73
|
const slug = rel
|
|
57
74
|
.replace(/\.(mdx?|md)$/, '')
|
|
@@ -61,6 +78,43 @@ function getSlugFromPath(filePath) {
|
|
|
61
78
|
return slug ? `/docs/${slug}` : '/docs'
|
|
62
79
|
}
|
|
63
80
|
|
|
81
|
+
function getSlugFromAppPath(filePath) {
|
|
82
|
+
// For App Router: src/app/docs/quickstart/page.mdx -> /docs/quickstart
|
|
83
|
+
// src/app/docs/page.mdx -> /docs
|
|
84
|
+
const rel = relative(APP_DOCS_DIR, filePath)
|
|
85
|
+
const dir = dirname(rel)
|
|
86
|
+
const name = basename(rel)
|
|
87
|
+
|
|
88
|
+
if (name === 'page.mdx' || name === 'page.md') {
|
|
89
|
+
if (dir === '.') return '/docs'
|
|
90
|
+
return `/docs/${dir.replace(/\\/g, '/')}`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const slug = rel
|
|
94
|
+
.replace(/\.(mdx?|md)$/, '')
|
|
95
|
+
.replace(/\/index$/, '')
|
|
96
|
+
.replace(/\\/g, '/')
|
|
97
|
+
|
|
98
|
+
return `/docs/${slug}`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function extractTitle(content, filePath) {
|
|
102
|
+
// Try to get title from first heading
|
|
103
|
+
const h1Match = content.match(/^#\s+(.+)$/m)
|
|
104
|
+
if (h1Match) return h1Match[1].trim()
|
|
105
|
+
|
|
106
|
+
// Derive from file path
|
|
107
|
+
const dir = dirname(filePath)
|
|
108
|
+
const folderName = basename(dir)
|
|
109
|
+
if (folderName && folderName !== 'docs' && folderName !== '.') {
|
|
110
|
+
return folderName
|
|
111
|
+
.replace(/-/g, ' ')
|
|
112
|
+
.replace(/\b\w/g, c => c.toUpperCase())
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return basename(filePath).replace(/\.(mdx?|md)$/, '')
|
|
116
|
+
}
|
|
117
|
+
|
|
64
118
|
async function buildSearchIndex() {
|
|
65
119
|
console.log('Building search index...')
|
|
66
120
|
|
|
@@ -74,36 +128,77 @@ async function buildSearchIndex() {
|
|
|
74
128
|
},
|
|
75
129
|
})
|
|
76
130
|
|
|
77
|
-
const files = await getAllMDXFiles(CONTENT_DIR)
|
|
78
131
|
const documents = []
|
|
132
|
+
const seen = new Set()
|
|
133
|
+
|
|
134
|
+
// 1. Index content/docs/ (generated docs)
|
|
135
|
+
if (await dirExists(CONTENT_DIR)) {
|
|
136
|
+
const contentFiles = await getAllMDXFiles(CONTENT_DIR)
|
|
137
|
+
for (const file of contentFiles) {
|
|
138
|
+
try {
|
|
139
|
+
const raw = await readFile(file, 'utf-8')
|
|
140
|
+
const { data, content } = matter(raw)
|
|
141
|
+
|
|
142
|
+
const title = data.title || extractTitle(content, file)
|
|
143
|
+
const plainContent = extractPlainText(content)
|
|
144
|
+
const href = getSlugFromContentPath(file)
|
|
145
|
+
|
|
146
|
+
if (seen.has(href)) continue
|
|
147
|
+
seen.add(href)
|
|
148
|
+
|
|
149
|
+
documents.push({
|
|
150
|
+
id: href,
|
|
151
|
+
title,
|
|
152
|
+
content: plainContent.slice(0, 5000),
|
|
153
|
+
href,
|
|
154
|
+
section: data.section || data.category || '',
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
console.log(` Indexed: ${href} (content/)`)
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error(` Error indexing ${file}:`, err.message)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
79
163
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
164
|
+
// 2. Index src/app/docs/ (App Router MDX pages)
|
|
165
|
+
if (await dirExists(APP_DOCS_DIR)) {
|
|
166
|
+
const appFiles = await getAllMDXFiles(APP_DOCS_DIR)
|
|
167
|
+
for (const file of appFiles) {
|
|
168
|
+
// Skip catch-all route files and layout files
|
|
169
|
+
if (file.includes('[...slug]') || file.includes('layout.')) continue
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const raw = await readFile(file, 'utf-8')
|
|
173
|
+
const { data, content } = matter(raw)
|
|
174
|
+
|
|
175
|
+
const title = data.title || extractTitle(content, file)
|
|
176
|
+
const plainContent = extractPlainText(content)
|
|
177
|
+
const href = getSlugFromAppPath(file)
|
|
178
|
+
|
|
179
|
+
if (seen.has(href)) continue
|
|
180
|
+
seen.add(href)
|
|
181
|
+
|
|
182
|
+
documents.push({
|
|
183
|
+
id: href,
|
|
184
|
+
title,
|
|
185
|
+
content: plainContent.slice(0, 5000),
|
|
186
|
+
href,
|
|
187
|
+
section: data.section || data.category || '',
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
console.log(` Indexed: ${href} (app/)`)
|
|
191
|
+
} catch (err) {
|
|
192
|
+
console.error(` Error indexing ${file}:`, err.message)
|
|
193
|
+
}
|
|
101
194
|
}
|
|
102
195
|
}
|
|
103
196
|
|
|
104
197
|
if (documents.length > 0) {
|
|
105
198
|
await insertMultiple(db, documents)
|
|
106
199
|
console.log(`\nIndexed ${documents.length} documents`)
|
|
200
|
+
} else {
|
|
201
|
+
console.log('\nNo documents found to index')
|
|
107
202
|
}
|
|
108
203
|
|
|
109
204
|
// Export the index
|
|
@@ -86,6 +86,10 @@ function indexDocs(): DocChunk[] {
|
|
|
86
86
|
return chunks
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function escapeRegex(str: string): string {
|
|
90
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
91
|
+
}
|
|
92
|
+
|
|
89
93
|
function searchDocs(query: string, chunks: DocChunk[]): DocChunk[] {
|
|
90
94
|
const queryLower = query.toLowerCase()
|
|
91
95
|
const queryWords = queryLower.split(/\s+/).filter(w => w.length > 2)
|
|
@@ -113,7 +117,7 @@ function searchDocs(query: string, chunks: DocChunk[]): DocChunk[] {
|
|
|
113
117
|
|
|
114
118
|
// Content matches (lower weight)
|
|
115
119
|
queryWords.forEach(word => {
|
|
116
|
-
const matches = (contentLower.match(new RegExp(word, 'g')) || []).length
|
|
120
|
+
const matches = (contentLower.match(new RegExp(escapeRegex(word), 'g')) || []).length
|
|
117
121
|
score += Math.min(matches, 5) // Cap at 5 matches per word
|
|
118
122
|
})
|
|
119
123
|
|
|
@@ -231,11 +235,15 @@ export async function POST(request: Request) {
|
|
|
231
235
|
const body = await request.json()
|
|
232
236
|
const { messages } = body as { messages: Message[] }
|
|
233
237
|
|
|
234
|
-
if (!messages || messages.length === 0) {
|
|
235
|
-
return NextResponse.json({ error: '
|
|
238
|
+
if (!Array.isArray(messages) || messages.length === 0 || messages.length > 50) {
|
|
239
|
+
return NextResponse.json({ error: 'Invalid request' }, { status: 400 })
|
|
236
240
|
}
|
|
237
241
|
|
|
238
242
|
const lastMessage = messages[messages.length - 1]
|
|
243
|
+
if (typeof lastMessage?.content !== 'string' || lastMessage.content.length > 10000) {
|
|
244
|
+
return NextResponse.json({ error: 'Message too long' }, { status: 400 })
|
|
245
|
+
}
|
|
246
|
+
|
|
239
247
|
if (lastMessage.role !== 'user') {
|
|
240
248
|
return NextResponse.json({ error: 'Last message must be from user' }, { status: 400 })
|
|
241
249
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# API Documentation
|
|
2
|
+
|
|
3
|
+
Generated by [skrypt](https://github.com/debgotwired/skrypt)
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
- **Total elements:** 81
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
- [auth/index.ts](./auth/index.md) (6 elements)
|
|
12
|
+
- [autofix/index.ts](./autofix/index.md) (4 elements)
|
|
13
|
+
- [config/loader.ts](./config/loader.md) (4 elements)
|
|
14
|
+
- [generator/generator.ts](./generator/generator.md) (3 elements)
|
|
15
|
+
- [generator/organizer.ts](./generator/organizer.md) (6 elements)
|
|
16
|
+
- [generator/writer.ts](./generator/writer.md) (4 elements)
|
|
17
|
+
- [github/pr-comments.ts](./github/pr-comments.md) (3 elements)
|
|
18
|
+
- [llm/anthropic-client.ts](./llm/anthropic-client.md) (4 elements)
|
|
19
|
+
- [llm/index.ts](./llm/index.md) (3 elements)
|
|
20
|
+
- [llm/openai-client.ts](./llm/openai-client.md) (4 elements)
|
|
21
|
+
- [plugins/index.ts](./plugins/index.md) (14 elements)
|
|
22
|
+
- [scanner/content-type.ts](./scanner/content-type.md) (4 elements)
|
|
23
|
+
- [scanner/go.ts](./scanner/go.md) (3 elements)
|
|
24
|
+
- [scanner/index.ts](./scanner/index.md) (2 elements)
|
|
25
|
+
- [scanner/python.ts](./scanner/python.md) (3 elements)
|
|
26
|
+
- [scanner/python_parser.py](./scanner/python_parser.md) (8 elements)
|
|
27
|
+
- [scanner/rust.ts](./scanner/rust.md) (3 elements)
|
|
28
|
+
- [scanner/typescript.ts](./scanner/typescript.md) (3 elements)
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import { notFound } from 'next/navigation'
|
|
2
|
-
import { readFile, readdir
|
|
2
|
+
import { readFile, readdir } from 'fs/promises'
|
|
3
3
|
import { join } from 'path'
|
|
4
4
|
import { compileMDX } from 'next-mdx-remote/rsc'
|
|
5
5
|
import remarkGfm from 'remark-gfm'
|
|
6
6
|
import * as components from '@/components/mdx'
|
|
7
|
+
import type { Metadata } from 'next'
|
|
7
8
|
|
|
8
9
|
interface PageProps {
|
|
9
10
|
params: Promise<{ slug: string[] }>
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
interface Frontmatter {
|
|
14
|
+
title?: string
|
|
15
|
+
description?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getDocsConfig() {
|
|
19
|
+
const fs = require('fs')
|
|
20
|
+
const configPath = join(process.cwd(), 'docs.json')
|
|
21
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
async function getContent(slug: string[]) {
|
|
13
25
|
const contentDir = join(process.cwd(), 'content', 'docs')
|
|
14
26
|
const filePath = join(contentDir, ...slug)
|
|
@@ -27,6 +39,74 @@ async function getContent(slug: string[]) {
|
|
|
27
39
|
return null
|
|
28
40
|
}
|
|
29
41
|
|
|
42
|
+
async function getFrontmatter(slug: string[]): Promise<Frontmatter> {
|
|
43
|
+
const result = await getContent(slug)
|
|
44
|
+
if (!result) return {}
|
|
45
|
+
|
|
46
|
+
// Simple frontmatter extraction without full MDX compilation
|
|
47
|
+
const fmMatch = result.content.match(/^---\s*\n([\s\S]*?)\n---/)
|
|
48
|
+
if (!fmMatch) return {}
|
|
49
|
+
|
|
50
|
+
const fm: Frontmatter = {}
|
|
51
|
+
const lines = fmMatch[1].split('\n')
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
const match = line.match(/^(\w+)\s*:\s*(.+)$/)
|
|
54
|
+
if (match) {
|
|
55
|
+
const key = match[1].trim()
|
|
56
|
+
let value = match[2].trim()
|
|
57
|
+
// Strip quotes
|
|
58
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
59
|
+
value = value.slice(1, -1)
|
|
60
|
+
}
|
|
61
|
+
if (key === 'title') fm.title = value
|
|
62
|
+
if (key === 'description') fm.description = value
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return fm
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
69
|
+
const { slug } = await params
|
|
70
|
+
const docsConfig = getDocsConfig()
|
|
71
|
+
const siteName = docsConfig.name || 'Documentation'
|
|
72
|
+
|
|
73
|
+
const frontmatter = await getFrontmatter(slug)
|
|
74
|
+
|
|
75
|
+
// Fall back to nav config title
|
|
76
|
+
if (!frontmatter.title) {
|
|
77
|
+
const path = '/docs/' + slug.join('/')
|
|
78
|
+
for (const group of docsConfig.navigation || []) {
|
|
79
|
+
for (const page of group.pages || []) {
|
|
80
|
+
if (page.path === path) {
|
|
81
|
+
frontmatter.title = page.title
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const title = frontmatter.title || slug[slug.length - 1].replace(/-/g, ' ').replace(/\b\w/g, (c: string) => c.toUpperCase())
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
title,
|
|
92
|
+
description: frontmatter.description || `${title} - ${siteName} documentation`,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Look up page title from docs.json navigation by pathname */
|
|
97
|
+
function getNavTitle(slug: string[]): string | undefined {
|
|
98
|
+
const docsConfig = getDocsConfig()
|
|
99
|
+
const path = '/docs/' + slug.join('/')
|
|
100
|
+
for (const group of docsConfig.navigation || []) {
|
|
101
|
+
for (const page of group.pages || []) {
|
|
102
|
+
if (page.path === path) {
|
|
103
|
+
return page.title
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return undefined
|
|
108
|
+
}
|
|
109
|
+
|
|
30
110
|
export default async function DocPage({ params }: PageProps) {
|
|
31
111
|
const { slug } = await params
|
|
32
112
|
const result = await getContent(slug)
|
|
@@ -35,22 +115,65 @@ export default async function DocPage({ params }: PageProps) {
|
|
|
35
115
|
notFound()
|
|
36
116
|
}
|
|
37
117
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
118
|
+
// Resolve page title from docs.json navigation (preferred) and description from frontmatter
|
|
119
|
+
const navTitle = getNavTitle(slug)
|
|
120
|
+
const frontmatter = await getFrontmatter(slug)
|
|
121
|
+
const pageDescription = frontmatter.description
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const { content } = await compileMDX({
|
|
125
|
+
source: result.content,
|
|
126
|
+
components: components as any,
|
|
127
|
+
options: {
|
|
128
|
+
parseFrontmatter: true,
|
|
129
|
+
mdxOptions: {
|
|
130
|
+
remarkPlugins: [remarkGfm],
|
|
131
|
+
},
|
|
45
132
|
},
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Resolve metadata for JSON-LD
|
|
136
|
+
const docsConfig = getDocsConfig()
|
|
137
|
+
const siteUrl = docsConfig.siteUrl || ''
|
|
138
|
+
const siteName = docsConfig.name || 'Documentation'
|
|
139
|
+
const pageTitle = navTitle || frontmatter.title || slug[slug.length - 1].replace(/-/g, ' ').replace(/\b\w/g, (c: string) => c.toUpperCase())
|
|
140
|
+
|
|
141
|
+
const jsonLd = {
|
|
142
|
+
'@context': 'https://schema.org',
|
|
143
|
+
'@type': 'TechArticle',
|
|
144
|
+
headline: pageTitle,
|
|
145
|
+
description: pageDescription || `${pageTitle} - ${siteName} documentation`,
|
|
146
|
+
url: `${siteUrl}/docs/${slug.join('/')}`,
|
|
147
|
+
author: { '@type': 'Organization', name: siteName },
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<>
|
|
152
|
+
<script
|
|
153
|
+
type="application/ld+json"
|
|
154
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
155
|
+
/>
|
|
156
|
+
{/*
|
|
157
|
+
Page title is resolved by DocsLayout from docs.json navigation via usePathname().
|
|
158
|
+
Description from MDX frontmatter is passed via data attribute for DocsLayout to read.
|
|
159
|
+
*/}
|
|
160
|
+
{pageDescription && (
|
|
161
|
+
<div data-page-description={pageDescription} className="hidden" />
|
|
162
|
+
)}
|
|
163
|
+
<div className="prose max-w-none">
|
|
164
|
+
{content}
|
|
165
|
+
</div>
|
|
166
|
+
</>
|
|
167
|
+
)
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error(`MDX compilation error for ${slug.join('/')}:`, error)
|
|
170
|
+
return (
|
|
171
|
+
<article className="prose">
|
|
172
|
+
<h1>Error loading page</h1>
|
|
173
|
+
<p>This documentation page could not be rendered. The MDX content may contain syntax errors.</p>
|
|
174
|
+
</article>
|
|
175
|
+
)
|
|
176
|
+
}
|
|
54
177
|
}
|
|
55
178
|
|
|
56
179
|
export async function generateStaticParams() {
|