boltdocs 2.2.0 → 2.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/CHANGELOG.md +24 -0
- package/bin/boltdocs.js +2 -2
- package/dist/base-ui/index.d.mts +4 -4
- package/dist/base-ui/index.d.ts +4 -4
- package/dist/base-ui/index.js +1 -1
- package/dist/base-ui/index.mjs +1 -1
- package/dist/{cache-CRAZ55X7.mjs → cache-P6WK424C.mjs} +1 -1
- package/dist/chunk-2DI3OGHV.mjs +1 -0
- package/dist/chunk-2Z5T6EAU.mjs +1 -0
- package/dist/chunk-64AJ5QLT.mjs +1 -0
- package/dist/chunk-DDX52BX4.mjs +1 -0
- package/dist/chunk-HRZDSFR5.mjs +1 -0
- package/dist/chunk-PPVDMDEL.mjs +1 -0
- package/dist/chunk-UBE4CKOA.mjs +1 -0
- package/dist/chunk-UWT4AJTH.mjs +73 -0
- package/dist/chunk-WWJ7WKDI.mjs +1 -0
- package/dist/chunk-Y4RRHPXC.mjs +1 -0
- package/dist/client/index.d.mts +15 -21
- package/dist/client/index.d.ts +15 -21
- package/dist/client/index.js +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/client/ssr.js +1 -1
- package/dist/client/ssr.mjs +1 -1
- package/dist/client/types.d.mts +1 -1
- package/dist/client/types.d.ts +1 -1
- package/dist/client/types.js +1 -1
- package/dist/{copy-markdown-CbS8X-qe.d.mts → copy-markdown--9yjpbyy.d.mts} +1 -1
- package/dist/{copy-markdown-C-90ixSe.d.ts → copy-markdown-l2MYkcG7.d.ts} +1 -1
- package/dist/hooks/index.d.mts +8 -16
- package/dist/hooks/index.d.ts +8 -16
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/index.d.ts +1 -1
- package/dist/{loading-chS3pm9W.d.ts → loading-BwUos0wZ.d.mts} +5 -16
- package/dist/{loading-BqGrFWO5.d.mts → loading-nlnUD01v.d.ts} +5 -16
- package/dist/mdx/index.d.mts +4 -2
- package/dist/mdx/index.d.ts +4 -2
- package/dist/mdx/index.js +1 -1
- package/dist/mdx/index.mjs +1 -1
- package/dist/node/cli-entry.js +25 -22
- package/dist/node/cli-entry.mjs +5 -1
- package/dist/node/index.d.mts +0 -9
- package/dist/node/index.d.ts +0 -9
- package/dist/node/index.js +14 -15
- package/dist/node/index.mjs +1 -1
- package/dist/primitives/index.d.mts +13 -22
- package/dist/primitives/index.d.ts +13 -22
- package/dist/primitives/index.js +1 -1
- package/dist/primitives/index.mjs +1 -1
- package/dist/search-dialog-OONKKC5H.mjs +1 -0
- package/dist/{types-j7jvWsJj.d.ts → types-opDA2E9-.d.mts} +4 -11
- package/dist/{types-j7jvWsJj.d.mts → types-opDA2E9-.d.ts} +4 -11
- package/dist/{use-routes-Cd806kGw.d.ts → use-routes-DNwgTRpU.d.ts} +1 -1
- package/dist/{use-routes-DDL0_jkQ.d.mts → use-routes-DrT80Eom.d.mts} +1 -1
- package/package.json +2 -1
- package/src/client/app/index.tsx +20 -9
- package/src/client/app/mdx-components-context.tsx +2 -2
- package/src/client/app/mdx-page.tsx +0 -1
- package/src/client/app/scroll-handler.tsx +21 -10
- package/src/client/app/theme-context.tsx +14 -7
- package/src/client/components/default-layout.tsx +6 -4
- package/src/client/components/docs-layout.tsx +34 -4
- package/src/client/components/icons-dev.tsx +154 -0
- package/src/client/components/mdx/code-block.tsx +57 -5
- package/src/client/components/mdx/component-preview.tsx +1 -0
- package/src/client/components/mdx/file-tree.tsx +35 -0
- package/src/client/components/primitives/helpers/observer.ts +30 -39
- package/src/client/components/primitives/index.ts +1 -0
- package/src/client/components/primitives/menu.tsx +18 -12
- package/src/client/components/primitives/navbar.tsx +34 -93
- package/src/client/components/primitives/on-this-page.tsx +7 -161
- package/src/client/components/primitives/popover.tsx +1 -2
- package/src/client/components/primitives/search-dialog.tsx +4 -4
- package/src/client/components/primitives/sidebar.tsx +3 -2
- package/src/client/components/primitives/skeleton.tsx +26 -0
- package/src/client/components/ui-base/copy-markdown.tsx +4 -10
- package/src/client/components/ui-base/index.ts +0 -1
- package/src/client/components/ui-base/loading.tsx +43 -73
- package/src/client/components/ui-base/navbar.tsx +18 -15
- package/src/client/components/ui-base/page-nav.tsx +2 -1
- package/src/client/components/ui-base/powered-by.tsx +4 -1
- package/src/client/components/ui-base/search-dialog.tsx +16 -5
- package/src/client/components/ui-base/sidebar.tsx +4 -2
- package/src/client/hooks/use-i18n.ts +3 -2
- package/src/client/hooks/use-localized-to.ts +6 -5
- package/src/client/hooks/use-navbar.ts +37 -6
- package/src/client/hooks/use-page-nav.ts +27 -6
- package/src/client/hooks/use-routes.ts +2 -1
- package/src/client/hooks/use-search.ts +81 -59
- package/src/client/hooks/use-sidebar.ts +2 -1
- package/src/client/index.ts +0 -1
- package/src/client/store/use-boltdocs-store.ts +6 -5
- package/src/client/theme/neutral.css +31 -3
- package/src/client/types.ts +2 -2
- package/src/node/{cli.ts → cli/build.ts} +17 -23
- package/src/node/cli/dev.ts +22 -0
- package/src/node/cli/doctor.ts +243 -0
- package/src/node/cli/index.ts +9 -0
- package/src/node/cli/ui.ts +54 -0
- package/src/node/cli-entry.ts +16 -16
- package/src/node/config.ts +1 -15
- package/src/node/mdx/cache.ts +1 -1
- package/src/node/mdx/index.ts +2 -0
- package/src/node/mdx/rehype-shiki.ts +9 -0
- package/src/node/mdx/remark-code-meta.ts +35 -0
- package/src/node/mdx/remark-shiki.ts +1 -1
- package/src/node/plugin/entry.ts +22 -15
- package/src/node/plugin/index.ts +46 -14
- package/src/node/routes/parser.ts +12 -9
- package/src/node/search/index.ts +55 -0
- package/src/node/ssg/index.ts +83 -15
- package/src/node/ssg/robots.ts +7 -4
- package/dist/chunk-5D6XPYQ3.mjs +0 -74
- package/dist/chunk-6QXCKZAT.mjs +0 -1
- package/dist/chunk-H4M6P3DM.mjs +0 -1
- package/dist/chunk-JXHNX2WN.mjs +0 -1
- package/dist/chunk-MZBG4N4W.mjs +0 -1
- package/dist/chunk-Q3MLYTIQ.mjs +0 -1
- package/dist/chunk-RSII2UPE.mjs +0 -1
- package/dist/chunk-ZK2266IZ.mjs +0 -1
- package/dist/chunk-ZRJ55GGF.mjs +0 -1
- package/dist/search-dialog-MA5AISC7.mjs +0 -1
- package/src/client/components/ui-base/progress-bar.tsx +0 -67
|
@@ -5,7 +5,7 @@ interface BoltdocsState {
|
|
|
5
5
|
currentLocale: string | undefined
|
|
6
6
|
currentVersion: string | undefined
|
|
7
7
|
hasHydrated: boolean
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
// Actions
|
|
10
10
|
setLocale: (locale: string | undefined) => void
|
|
11
11
|
setVersion: (version: string | undefined) => void
|
|
@@ -22,9 +22,10 @@ export const useBoltdocsStore = create<BoltdocsState>()(
|
|
|
22
22
|
currentLocale: undefined,
|
|
23
23
|
currentVersion: undefined,
|
|
24
24
|
hasHydrated: false,
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
setLocale: (locale: string | undefined) => set({ currentLocale: locale }),
|
|
27
|
-
setVersion: (version: string | undefined) =>
|
|
27
|
+
setVersion: (version: string | undefined) =>
|
|
28
|
+
set({ currentVersion: version }),
|
|
28
29
|
setHasHydrated: (val: boolean) => set({ hasHydrated: val }),
|
|
29
30
|
}),
|
|
30
31
|
{
|
|
@@ -38,6 +39,6 @@ export const useBoltdocsStore = create<BoltdocsState>()(
|
|
|
38
39
|
onRehydrateStorage: () => (state?: BoltdocsState) => {
|
|
39
40
|
state?.setHasHydrated(true)
|
|
40
41
|
},
|
|
41
|
-
}
|
|
42
|
-
)
|
|
42
|
+
},
|
|
43
|
+
),
|
|
43
44
|
)
|
|
@@ -66,7 +66,36 @@
|
|
|
66
66
|
--spacing-navbar: 3.5rem;
|
|
67
67
|
--spacing-sidebar: 16rem;
|
|
68
68
|
--spacing-toc: 14rem;
|
|
69
|
-
--spacing-content-max:
|
|
69
|
+
--spacing-content-max: 54rem;
|
|
70
|
+
|
|
71
|
+
@keyframes pulse {
|
|
72
|
+
0%,
|
|
73
|
+
100% {
|
|
74
|
+
opacity: 1;
|
|
75
|
+
}
|
|
76
|
+
50% {
|
|
77
|
+
opacity: 0.5;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@keyframes fade-in {
|
|
82
|
+
from {
|
|
83
|
+
opacity: 0;
|
|
84
|
+
transform: translateY(10px);
|
|
85
|
+
}
|
|
86
|
+
to {
|
|
87
|
+
opacity: 1;
|
|
88
|
+
transform: translateY(0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.animate-pulse {
|
|
94
|
+
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.animate-fade-in {
|
|
98
|
+
animation: fade-in 0.5s ease-out forwards;
|
|
70
99
|
}
|
|
71
100
|
|
|
72
101
|
:root[data-theme="dark"],
|
|
@@ -94,9 +123,8 @@
|
|
|
94
123
|
body {
|
|
95
124
|
margin: 0;
|
|
96
125
|
padding: 0;
|
|
97
|
-
height: 100%;
|
|
126
|
+
min-height: 100%;
|
|
98
127
|
overflow-x: hidden;
|
|
99
|
-
overflow-y: hidden;
|
|
100
128
|
}
|
|
101
129
|
|
|
102
130
|
body {
|
package/src/client/types.ts
CHANGED
|
@@ -74,6 +74,8 @@ export interface CreateBoltdocsAppOptions {
|
|
|
74
74
|
homePage?: React.ComponentType
|
|
75
75
|
/** Custom external pages mapped by their route path */
|
|
76
76
|
externalPages?: Record<string, React.ComponentType>
|
|
77
|
+
/** Optional custom layout for external pages */
|
|
78
|
+
externalLayout?: React.ComponentType<{ children: React.ReactNode }>
|
|
77
79
|
/** Optional custom MDX components provided by plugins */
|
|
78
80
|
components?: Record<string, React.ComponentType>
|
|
79
81
|
}
|
|
@@ -167,6 +169,4 @@ export interface NavbarLink {
|
|
|
167
169
|
active: boolean
|
|
168
170
|
/** Optional icon or string for external link indication */
|
|
169
171
|
to?: string
|
|
170
|
-
/** Nested items for NavigationMenu */
|
|
171
|
-
items?: NavbarLink[]
|
|
172
172
|
}
|
|
@@ -1,27 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createViteConfig, resolveConfig } from '
|
|
3
|
-
import { getHtmlTemplate } from '
|
|
1
|
+
import { build, preview } from 'vite'
|
|
2
|
+
import { createViteConfig, resolveConfig } from '../index'
|
|
3
|
+
import { getHtmlTemplate } from '../plugin/html'
|
|
4
4
|
import path from 'path'
|
|
5
5
|
import fs from 'fs'
|
|
6
|
+
import * as ui from './ui'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* Logic for the `boltdocs build` command.
|
|
10
|
+
* Prepares the production bundle and handles dynamic index.html generation.
|
|
11
|
+
*
|
|
12
|
+
* @param root - The project root directory
|
|
10
13
|
*/
|
|
11
|
-
|
|
12
|
-
export async function devAction(root: string = process.cwd()) {
|
|
13
|
-
try {
|
|
14
|
-
const viteConfig = await createViteConfig(root, 'development')
|
|
15
|
-
const server = await createServer(viteConfig)
|
|
16
|
-
await server.listen()
|
|
17
|
-
server.printUrls()
|
|
18
|
-
server.bindCLIShortcuts({ print: true })
|
|
19
|
-
} catch (e) {
|
|
20
|
-
console.error('[boltdocs] Failed to start dev server:', e)
|
|
21
|
-
process.exit(1)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
14
|
export async function buildAction(root: string = process.cwd()) {
|
|
26
15
|
let createdIndexHtml = false
|
|
27
16
|
const indexPath = path.resolve(root, 'index.html')
|
|
@@ -35,9 +24,9 @@ export async function buildAction(root: string = process.cwd()) {
|
|
|
35
24
|
|
|
36
25
|
const viteConfig = await createViteConfig(root, 'production')
|
|
37
26
|
await build(viteConfig)
|
|
38
|
-
|
|
27
|
+
ui.success('Build completed successfully.')
|
|
39
28
|
} catch (e) {
|
|
40
|
-
|
|
29
|
+
ui.error('Build failed:', e)
|
|
41
30
|
process.exit(1)
|
|
42
31
|
} finally {
|
|
43
32
|
if (createdIndexHtml && fs.existsSync(indexPath)) {
|
|
@@ -46,14 +35,19 @@ export async function buildAction(root: string = process.cwd()) {
|
|
|
46
35
|
}
|
|
47
36
|
}
|
|
48
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Logic for the `boltdocs preview` command.
|
|
40
|
+
* Serves the production build from the disk.
|
|
41
|
+
*
|
|
42
|
+
* @param root - The project root directory
|
|
43
|
+
*/
|
|
49
44
|
export async function previewAction(root: string = process.cwd()) {
|
|
50
45
|
try {
|
|
51
46
|
const viteConfig = await createViteConfig(root, 'production')
|
|
52
47
|
const previewServer = await preview(viteConfig)
|
|
53
48
|
previewServer.printUrls()
|
|
54
49
|
} catch (e) {
|
|
55
|
-
|
|
50
|
+
ui.error('Failed to start preview server:', e)
|
|
56
51
|
process.exit(1)
|
|
57
52
|
}
|
|
58
53
|
}
|
|
59
|
-
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createServer } from 'vite'
|
|
2
|
+
import { createViteConfig } from '../index'
|
|
3
|
+
import * as ui from './ui'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Logic for the `boltdocs dev` command.
|
|
7
|
+
* Starts a Vite development server and sets up HMR.
|
|
8
|
+
*
|
|
9
|
+
* @param root - The project root directory
|
|
10
|
+
*/
|
|
11
|
+
export async function devAction(root: string = process.cwd()) {
|
|
12
|
+
try {
|
|
13
|
+
const viteConfig = await createViteConfig(root, 'development')
|
|
14
|
+
const server = await createServer(viteConfig)
|
|
15
|
+
await server.listen()
|
|
16
|
+
server.printUrls()
|
|
17
|
+
server.bindCLIShortcuts({ print: true })
|
|
18
|
+
} catch (e) {
|
|
19
|
+
ui.error('Failed to start dev server:', e)
|
|
20
|
+
process.exit(1)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import fastGlob from 'fast-glob'
|
|
4
|
+
import { resolveConfig } from '../config'
|
|
5
|
+
import { parseFrontmatter, normalizePath } from '../utils'
|
|
6
|
+
import * as ui from './ui'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Interface representing a documentation hygiene issue.
|
|
10
|
+
*/
|
|
11
|
+
interface Issue {
|
|
12
|
+
level: 'high' | 'warning' | 'low'
|
|
13
|
+
message: string
|
|
14
|
+
suggestion?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Logic for the `boltdocs doctor` command.
|
|
19
|
+
* Scans the documentation directory for broken links, missing frontmatter,
|
|
20
|
+
* and orphaned translations.
|
|
21
|
+
*
|
|
22
|
+
* @param root - The project root directory
|
|
23
|
+
*/
|
|
24
|
+
export async function doctorAction(root: string = process.cwd()) {
|
|
25
|
+
const { colors } = ui
|
|
26
|
+
ui.info(
|
|
27
|
+
`${colors.bold}Running documentation health check...${colors.reset}\n`,
|
|
28
|
+
)
|
|
29
|
+
const start = performance.now()
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const config = await resolveConfig('docs', root)
|
|
33
|
+
const docsDir = path.resolve(root, 'docs')
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(docsDir)) {
|
|
36
|
+
ui.error(`Documentation directory not found at ${docsDir}`)
|
|
37
|
+
process.exit(1)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const files = await fastGlob(['**/*.md', '**/*.mdx'], {
|
|
41
|
+
cwd: docsDir,
|
|
42
|
+
absolute: true,
|
|
43
|
+
suppressErrors: true,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
let highCount = 0
|
|
47
|
+
let warningCount = 0
|
|
48
|
+
let lowCount = 0
|
|
49
|
+
const issuesMap = new Map<string, Issue[]>()
|
|
50
|
+
|
|
51
|
+
const addIssue = (file: string, issue: Issue) => {
|
|
52
|
+
const relPath = path.relative(docsDir, file)
|
|
53
|
+
let issues = issuesMap.get(relPath)
|
|
54
|
+
if (!issues) {
|
|
55
|
+
issues = []
|
|
56
|
+
issuesMap.set(relPath, issues)
|
|
57
|
+
}
|
|
58
|
+
issues.push(issue)
|
|
59
|
+
if (issue.level === 'high') highCount++
|
|
60
|
+
else if (issue.level === 'warning') warningCount++
|
|
61
|
+
else if (issue.level === 'low') lowCount++
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const basePath = '/docs'
|
|
65
|
+
|
|
66
|
+
// 1. Scan for Frontmatter, Links, and Content Issues
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
const { data, content } = parseFrontmatter(file)
|
|
69
|
+
|
|
70
|
+
// Frontmatter Validation
|
|
71
|
+
if (!data.title) {
|
|
72
|
+
addIssue(file, {
|
|
73
|
+
level: 'warning',
|
|
74
|
+
message: 'Missing "title" in frontmatter.',
|
|
75
|
+
suggestion:
|
|
76
|
+
'Add `title: Your Title` to the YAML frontmatter at the top of the file.',
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!data.description) {
|
|
81
|
+
addIssue(file, {
|
|
82
|
+
level: 'low',
|
|
83
|
+
message: 'Missing "description" in frontmatter.',
|
|
84
|
+
suggestion:
|
|
85
|
+
'Adding a description helps with SEO and search previews.',
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Link Validation
|
|
90
|
+
const linkRegex = /\[.*?\]\((.*?)\)/g
|
|
91
|
+
const htmlLinkRegex = /<a\s+[^>]*href=["']([^"']+)["'][^>]*>/g
|
|
92
|
+
const links = [
|
|
93
|
+
...content.matchAll(linkRegex),
|
|
94
|
+
...content.matchAll(htmlLinkRegex),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
for (const match of links) {
|
|
98
|
+
let link = match[1]
|
|
99
|
+
if (
|
|
100
|
+
!link ||
|
|
101
|
+
link.startsWith('http') ||
|
|
102
|
+
link.startsWith('https') ||
|
|
103
|
+
link.startsWith('#') ||
|
|
104
|
+
link.startsWith('mailto:') ||
|
|
105
|
+
link.startsWith('tel:')
|
|
106
|
+
) {
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
link = link.split('#')[0]
|
|
111
|
+
if (!link) continue
|
|
112
|
+
|
|
113
|
+
let targetPath: string
|
|
114
|
+
if (link.startsWith('/')) {
|
|
115
|
+
let pathAfterBase = link
|
|
116
|
+
if (link.startsWith(basePath + '/') || link === basePath) {
|
|
117
|
+
pathAfterBase = link.substring(basePath.length)
|
|
118
|
+
}
|
|
119
|
+
targetPath = path.join(docsDir, pathAfterBase)
|
|
120
|
+
} else {
|
|
121
|
+
targetPath = path.resolve(path.dirname(file), link)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const extensions = ['', '.md', '.mdx', '/index.md', '/index.mdx']
|
|
125
|
+
let exists = false
|
|
126
|
+
for (const ext of extensions) {
|
|
127
|
+
const finalPath = targetPath + ext
|
|
128
|
+
if (fs.existsSync(finalPath) && fs.statSync(finalPath).isFile()) {
|
|
129
|
+
exists = true
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!exists) {
|
|
135
|
+
addIssue(file, {
|
|
136
|
+
level: 'high',
|
|
137
|
+
message: `Broken internal link: "${link}"`,
|
|
138
|
+
suggestion: `Ensure the file exists at "${targetPath}". If it's a directory, ensure it has an "index.md" or "index.mdx".`,
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 2. Scan for Orphaned Translations
|
|
145
|
+
if (config.i18n) {
|
|
146
|
+
const { defaultLocale, locales } = config.i18n
|
|
147
|
+
const otherLocales = Object.keys(locales).filter(
|
|
148
|
+
(l) => l !== defaultLocale,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
const relPath = normalizePath(path.relative(docsDir, file))
|
|
153
|
+
const parts = relPath.split('/')
|
|
154
|
+
|
|
155
|
+
if (parts[0] === defaultLocale) {
|
|
156
|
+
const pathAfterLocale = parts.slice(1).join('/')
|
|
157
|
+
for (const locale of otherLocales) {
|
|
158
|
+
const localeParts = [locale, ...parts.slice(1)]
|
|
159
|
+
const targetLocaleFile = path.join(docsDir, ...localeParts)
|
|
160
|
+
|
|
161
|
+
if (!fs.existsSync(targetLocaleFile)) {
|
|
162
|
+
addIssue(file, {
|
|
163
|
+
level: 'warning',
|
|
164
|
+
message: `Missing translation for locale "${locale}"`,
|
|
165
|
+
suggestion: `Create a translated version of this file at "${locale}/${pathAfterLocale}".`,
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Final Reporting
|
|
174
|
+
if (issuesMap.size === 0) {
|
|
175
|
+
ui.success('All documentation files are healthy!\n')
|
|
176
|
+
} else {
|
|
177
|
+
for (const [file, issues] of issuesMap.entries()) {
|
|
178
|
+
console.log(`📄 ${colors.bold}${file}${colors.reset}`)
|
|
179
|
+
|
|
180
|
+
const sortedIssues = issues.sort((a, b) => {
|
|
181
|
+
const order = { high: 1, warning: 2, low: 3 }
|
|
182
|
+
return order[a.level] - order[b.level]
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
for (const issue of sortedIssues) {
|
|
186
|
+
let prefix = ''
|
|
187
|
+
let color = ''
|
|
188
|
+
if (issue.level === 'high') {
|
|
189
|
+
prefix = '❌'
|
|
190
|
+
color = colors.red
|
|
191
|
+
} else if (issue.level === 'warning') {
|
|
192
|
+
prefix = '⚠️'
|
|
193
|
+
color = colors.yellow
|
|
194
|
+
} else {
|
|
195
|
+
prefix = 'ℹ️'
|
|
196
|
+
color = colors.cyan
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log(
|
|
200
|
+
` ${color}${prefix} ${issue.level.toUpperCase()}:${colors.reset} ${issue.message}`,
|
|
201
|
+
)
|
|
202
|
+
if (issue.suggestion) {
|
|
203
|
+
console.log(
|
|
204
|
+
` ${colors.gray}💡 Suggestion: ${issue.suggestion}${colors.reset}`,
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
console.log('')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(`${colors.bold}Summary:${colors.reset}`)
|
|
212
|
+
console.log(
|
|
213
|
+
` ${colors.red}${highCount} high-level errors${colors.reset}`,
|
|
214
|
+
)
|
|
215
|
+
console.log(` ${colors.yellow}${warningCount} warnings${colors.reset}`)
|
|
216
|
+
console.log(
|
|
217
|
+
` ${colors.cyan}${lowCount} minor improvements${colors.reset}\n`,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if (highCount > 0) {
|
|
221
|
+
ui.error(
|
|
222
|
+
'HIGH ERROR: Fix these to ensure your documentation builds correctly.',
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
if (warningCount > 0 || lowCount > 0) {
|
|
226
|
+
ui.info(
|
|
227
|
+
'TIP: Address warnings and suggestions for premium quality docs.',
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
console.log('')
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const duration = performance.now() - start
|
|
234
|
+
ui.info(`Finished in ${duration.toFixed(2)}ms\n`)
|
|
235
|
+
|
|
236
|
+
if (highCount > 0) {
|
|
237
|
+
process.exit(1)
|
|
238
|
+
}
|
|
239
|
+
} catch (e) {
|
|
240
|
+
ui.error('Failed to run doctor check:', e)
|
|
241
|
+
process.exit(1)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI Escape sequences for terminal coloring and styling.
|
|
3
|
+
* Used to provide a premium and consistent CLI experience.
|
|
4
|
+
*/
|
|
5
|
+
export const colors = {
|
|
6
|
+
reset: '\x1b[0m',
|
|
7
|
+
bold: '\x1b[1m',
|
|
8
|
+
red: '\x1b[31m',
|
|
9
|
+
green: '\x1b[32m',
|
|
10
|
+
yellow: '\x1b[33m',
|
|
11
|
+
blue: '\x1b[34m',
|
|
12
|
+
cyan: '\x1b[36m',
|
|
13
|
+
gray: '\x1b[90m',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Formats a message with the boltdocs prefix and provided styling.
|
|
18
|
+
*
|
|
19
|
+
* @param message - The content to log
|
|
20
|
+
* @param style - Optional ANSI style prefix
|
|
21
|
+
* @returns The formatted string
|
|
22
|
+
*/
|
|
23
|
+
export function formatLog(message: string, style: string = ''): string {
|
|
24
|
+
return `${style}${colors.bold}[boltdocs]${colors.reset} ${message}${colors.reset}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Logs a standard informational message to the console.
|
|
29
|
+
*
|
|
30
|
+
* @param message - The message to display
|
|
31
|
+
*/
|
|
32
|
+
export function info(message: string) {
|
|
33
|
+
console.log(formatLog(message))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Logs an error message to the console with red styling.
|
|
38
|
+
*
|
|
39
|
+
* @param message - The error description
|
|
40
|
+
* @param error - Optional error object for stack tracing
|
|
41
|
+
*/
|
|
42
|
+
export function error(message: string, error?: any) {
|
|
43
|
+
console.error(formatLog(message, colors.red))
|
|
44
|
+
if (error) console.error(error)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Logs a success message to the console with green styling.
|
|
49
|
+
*
|
|
50
|
+
* @param message - The success description
|
|
51
|
+
*/
|
|
52
|
+
export function success(message: string) {
|
|
53
|
+
console.log(formatLog(message, colors.green))
|
|
54
|
+
}
|
package/src/node/cli-entry.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import cac from
|
|
3
|
-
import {
|
|
2
|
+
import cac from 'cac'
|
|
3
|
+
import {
|
|
4
|
+
devAction,
|
|
5
|
+
buildAction,
|
|
6
|
+
previewAction,
|
|
7
|
+
doctorAction,
|
|
8
|
+
} from './cli/index'
|
|
4
9
|
|
|
5
|
-
const cli = cac(
|
|
10
|
+
const cli = cac('boltdocs')
|
|
6
11
|
|
|
7
|
-
cli
|
|
8
|
-
.command("[root]", "Start development server")
|
|
9
|
-
.alias("dev")
|
|
10
|
-
.action(devAction);
|
|
12
|
+
cli.command('[root]', 'Start development server').alias('dev').action(devAction)
|
|
11
13
|
|
|
12
|
-
cli
|
|
13
|
-
.command("build [root]", "Build for production")
|
|
14
|
-
.action(buildAction);
|
|
14
|
+
cli.command('build [root]', 'Build for production').action(buildAction)
|
|
15
15
|
|
|
16
|
-
cli
|
|
17
|
-
.command("preview [root]", "Preview production build")
|
|
18
|
-
.action(previewAction);
|
|
16
|
+
cli.command('preview [root]', 'Preview production build').action(previewAction)
|
|
19
17
|
|
|
20
|
-
cli.
|
|
18
|
+
cli.command('doctor [root]', 'Check documentation health').action(doctorAction)
|
|
19
|
+
|
|
20
|
+
cli.help()
|
|
21
21
|
// This will be replaced at build time or package publishing, but hardcoded to 2.0.0 for now
|
|
22
|
-
cli.version(
|
|
22
|
+
cli.version('2.0.0')
|
|
23
23
|
|
|
24
|
-
cli.parse()
|
|
24
|
+
cli.parse()
|
package/src/node/config.ts
CHANGED
|
@@ -44,13 +44,6 @@ export interface BoltdocsThemeConfig {
|
|
|
44
44
|
label: string | Record<string, string>
|
|
45
45
|
/** URL path or external link */
|
|
46
46
|
href: string
|
|
47
|
-
/** Nested items for NavigationMenu */
|
|
48
|
-
items?: Array<{
|
|
49
|
-
/** Text to display (can be a string or a map of translations) */
|
|
50
|
-
label: string | Record<string, string>
|
|
51
|
-
/** URL path or external link */
|
|
52
|
-
href: string
|
|
53
|
-
}>
|
|
54
47
|
}>
|
|
55
48
|
/** Items to display in the sidebar, organized optionally by group URLs */
|
|
56
49
|
sidebar?: Record<string, Array<{ text: string; link: string }>>
|
|
@@ -164,7 +157,7 @@ export interface BoltdocsVersionConfig {
|
|
|
164
157
|
export interface BoltdocsVersionsConfig {
|
|
165
158
|
/** The default version path (e.g., 'v2') */
|
|
166
159
|
defaultVersion: string
|
|
167
|
-
/**
|
|
160
|
+
/**
|
|
168
161
|
* Optional prefix for all version paths (e.g., 'v').
|
|
169
162
|
* If set to 'v', version '1.1' will be available at '/docs/v1.1'.
|
|
170
163
|
*/
|
|
@@ -222,8 +215,6 @@ export interface BoltdocsConfig {
|
|
|
222
215
|
versions?: BoltdocsVersionsConfig
|
|
223
216
|
/** Custom plugins for extending functionality */
|
|
224
217
|
plugins?: BoltdocsPlugin[]
|
|
225
|
-
/** Map of custom external route paths to component file paths */
|
|
226
|
-
external?: Record<string, string>
|
|
227
218
|
/** External integrations configuration */
|
|
228
219
|
integrations?: BoltdocsIntegrationsConfig
|
|
229
220
|
/** Configuration for the robots.txt file */
|
|
@@ -344,10 +335,6 @@ export async function resolveConfig(
|
|
|
344
335
|
cleanThemeConfig.navbar = cleanThemeConfig.navbar.map((item: any) => ({
|
|
345
336
|
label: item.label || item.text || '',
|
|
346
337
|
href: item.href || item.link || item.to || '',
|
|
347
|
-
items: item.items?.map((sub: any) => ({
|
|
348
|
-
label: sub.label || sub.text || '',
|
|
349
|
-
href: sub.href || sub.link || sub.to || '',
|
|
350
|
-
})),
|
|
351
338
|
}))
|
|
352
339
|
}
|
|
353
340
|
|
|
@@ -362,7 +349,6 @@ export async function resolveConfig(
|
|
|
362
349
|
versions: userConfig.versions,
|
|
363
350
|
siteUrl: userConfig.siteUrl,
|
|
364
351
|
plugins: userConfig.plugins || [],
|
|
365
|
-
external: userConfig.external,
|
|
366
352
|
integrations: userConfig.integrations,
|
|
367
353
|
robots: userConfig.robots,
|
|
368
354
|
vite: userConfig.vite,
|
package/src/node/mdx/cache.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { TransformCache } from '../cache'
|
|
|
3
3
|
/**
|
|
4
4
|
* Version identifier for the MDX plugin to invalidate cache if logic changes.
|
|
5
5
|
*/
|
|
6
|
-
export const MDX_PLUGIN_VERSION = '
|
|
6
|
+
export const MDX_PLUGIN_VERSION = 'v4'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Persistent cache for MDX transformations.
|
package/src/node/mdx/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { BoltdocsConfig } from '../config'
|
|
|
9
9
|
import { mdxCache, MDX_PLUGIN_VERSION } from './cache'
|
|
10
10
|
import { remarkShiki } from './remark-shiki'
|
|
11
11
|
import { rehypeShiki } from './rehype-shiki'
|
|
12
|
+
import { remarkCodeMeta } from './remark-code-meta'
|
|
12
13
|
|
|
13
14
|
let mdxCacheLoaded = false
|
|
14
15
|
let hits = 0
|
|
@@ -38,6 +39,7 @@ export function boltdocsMdxPlugin(
|
|
|
38
39
|
remarkPlugins: [
|
|
39
40
|
remarkGfm,
|
|
40
41
|
remarkFrontmatter,
|
|
42
|
+
remarkCodeMeta,
|
|
41
43
|
[remarkShiki, config],
|
|
42
44
|
...(extraRemarkPlugins as any[]),
|
|
43
45
|
],
|
|
@@ -31,6 +31,11 @@ export function rehypeShiki(config?: BoltdocsConfig) {
|
|
|
31
31
|
const lang = langMatch ? langMatch.slice(9) : 'text'
|
|
32
32
|
const code = codeNode.children[0]?.value || ''
|
|
33
33
|
|
|
34
|
+
// Extract title from meta string (e.g., ```ts title="app.ts")
|
|
35
|
+
const meta: string = codeNode.data?.meta || codeNode.properties?.metastring || ''
|
|
36
|
+
const titleMatch = meta.match(/title\s*=\s*"([^"]*)"/)
|
|
37
|
+
const title = titleMatch ? titleMatch[1] : undefined
|
|
38
|
+
|
|
34
39
|
const options: any = { lang }
|
|
35
40
|
if (typeof codeTheme === 'object') {
|
|
36
41
|
options.themes = {
|
|
@@ -46,6 +51,10 @@ export function rehypeShiki(config?: BoltdocsConfig) {
|
|
|
46
51
|
// Inject highlighted HTML and mark as highlighted for CodeBlock component
|
|
47
52
|
node.properties.dataHighlighted = 'true'
|
|
48
53
|
node.properties.highlightedHtml = html
|
|
54
|
+
node.properties['data-lang'] = lang
|
|
55
|
+
if (title) {
|
|
56
|
+
node.properties.title = title
|
|
57
|
+
}
|
|
49
58
|
node.children = []
|
|
50
59
|
}
|
|
51
60
|
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Remark plugin that preserves code fence meta strings (e.g., title="file.ts")
|
|
5
|
+
* and the language identifier by copying them to hProperties so they survive
|
|
6
|
+
* the remark → rehype conversion and are accessible as props on the `<pre>` element.
|
|
7
|
+
*
|
|
8
|
+
* Usage in MDX: ```ts title="app.ts"
|
|
9
|
+
*/
|
|
10
|
+
export function remarkCodeMeta() {
|
|
11
|
+
return (tree: any) => {
|
|
12
|
+
visit(tree, 'code', (node: any) => {
|
|
13
|
+
node.data = node.data || {}
|
|
14
|
+
node.data.hProperties = node.data.hProperties || {}
|
|
15
|
+
|
|
16
|
+
// Always pass the lang through
|
|
17
|
+
if (node.lang) {
|
|
18
|
+
node.data.hProperties['data-lang'] = node.lang
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!node.meta) return
|
|
22
|
+
|
|
23
|
+
const meta: string = node.meta
|
|
24
|
+
|
|
25
|
+
// Extract title="..." from the meta string
|
|
26
|
+
const titleMatch = meta.match(/title\s*=\s*"([^"]*)"/)
|
|
27
|
+
if (titleMatch) {
|
|
28
|
+
node.data.hProperties.title = titleMatch[1]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Preserve the full meta string for other plugins
|
|
32
|
+
node.data.hProperties.metastring = meta
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -32,7 +32,7 @@ export function remarkShiki(config?: BoltdocsConfig) {
|
|
|
32
32
|
code = codeAttr.value
|
|
33
33
|
} else if (codeAttr.value?.type === 'mdxJsxAttributeValueExpression') {
|
|
34
34
|
const expr = codeAttr.value.value ?? ''
|
|
35
|
-
code = expr.match(/^[`'"](
|
|
35
|
+
code = expr.match(/^[`'"]([\s\S]+)[`'"]$/)?.[1] ?? expr
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|