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.
Files changed (124) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/bin/boltdocs.js +2 -2
  3. package/dist/base-ui/index.d.mts +4 -4
  4. package/dist/base-ui/index.d.ts +4 -4
  5. package/dist/base-ui/index.js +1 -1
  6. package/dist/base-ui/index.mjs +1 -1
  7. package/dist/{cache-CRAZ55X7.mjs → cache-P6WK424C.mjs} +1 -1
  8. package/dist/chunk-2DI3OGHV.mjs +1 -0
  9. package/dist/chunk-2Z5T6EAU.mjs +1 -0
  10. package/dist/chunk-64AJ5QLT.mjs +1 -0
  11. package/dist/chunk-DDX52BX4.mjs +1 -0
  12. package/dist/chunk-HRZDSFR5.mjs +1 -0
  13. package/dist/chunk-PPVDMDEL.mjs +1 -0
  14. package/dist/chunk-UBE4CKOA.mjs +1 -0
  15. package/dist/chunk-UWT4AJTH.mjs +73 -0
  16. package/dist/chunk-WWJ7WKDI.mjs +1 -0
  17. package/dist/chunk-Y4RRHPXC.mjs +1 -0
  18. package/dist/client/index.d.mts +15 -21
  19. package/dist/client/index.d.ts +15 -21
  20. package/dist/client/index.js +1 -1
  21. package/dist/client/index.mjs +1 -1
  22. package/dist/client/ssr.js +1 -1
  23. package/dist/client/ssr.mjs +1 -1
  24. package/dist/client/types.d.mts +1 -1
  25. package/dist/client/types.d.ts +1 -1
  26. package/dist/client/types.js +1 -1
  27. package/dist/{copy-markdown-CbS8X-qe.d.mts → copy-markdown--9yjpbyy.d.mts} +1 -1
  28. package/dist/{copy-markdown-C-90ixSe.d.ts → copy-markdown-l2MYkcG7.d.ts} +1 -1
  29. package/dist/hooks/index.d.mts +8 -16
  30. package/dist/hooks/index.d.ts +8 -16
  31. package/dist/hooks/index.js +1 -1
  32. package/dist/hooks/index.mjs +1 -1
  33. package/dist/integrations/index.d.mts +1 -1
  34. package/dist/integrations/index.d.ts +1 -1
  35. package/dist/{loading-chS3pm9W.d.ts → loading-BwUos0wZ.d.mts} +5 -16
  36. package/dist/{loading-BqGrFWO5.d.mts → loading-nlnUD01v.d.ts} +5 -16
  37. package/dist/mdx/index.d.mts +4 -2
  38. package/dist/mdx/index.d.ts +4 -2
  39. package/dist/mdx/index.js +1 -1
  40. package/dist/mdx/index.mjs +1 -1
  41. package/dist/node/cli-entry.js +25 -22
  42. package/dist/node/cli-entry.mjs +5 -1
  43. package/dist/node/index.d.mts +0 -9
  44. package/dist/node/index.d.ts +0 -9
  45. package/dist/node/index.js +14 -15
  46. package/dist/node/index.mjs +1 -1
  47. package/dist/primitives/index.d.mts +13 -22
  48. package/dist/primitives/index.d.ts +13 -22
  49. package/dist/primitives/index.js +1 -1
  50. package/dist/primitives/index.mjs +1 -1
  51. package/dist/search-dialog-OONKKC5H.mjs +1 -0
  52. package/dist/{types-j7jvWsJj.d.ts → types-opDA2E9-.d.mts} +4 -11
  53. package/dist/{types-j7jvWsJj.d.mts → types-opDA2E9-.d.ts} +4 -11
  54. package/dist/{use-routes-Cd806kGw.d.ts → use-routes-DNwgTRpU.d.ts} +1 -1
  55. package/dist/{use-routes-DDL0_jkQ.d.mts → use-routes-DrT80Eom.d.mts} +1 -1
  56. package/package.json +2 -1
  57. package/src/client/app/index.tsx +20 -9
  58. package/src/client/app/mdx-components-context.tsx +2 -2
  59. package/src/client/app/mdx-page.tsx +0 -1
  60. package/src/client/app/scroll-handler.tsx +21 -10
  61. package/src/client/app/theme-context.tsx +14 -7
  62. package/src/client/components/default-layout.tsx +6 -4
  63. package/src/client/components/docs-layout.tsx +34 -4
  64. package/src/client/components/icons-dev.tsx +154 -0
  65. package/src/client/components/mdx/code-block.tsx +57 -5
  66. package/src/client/components/mdx/component-preview.tsx +1 -0
  67. package/src/client/components/mdx/file-tree.tsx +35 -0
  68. package/src/client/components/primitives/helpers/observer.ts +30 -39
  69. package/src/client/components/primitives/index.ts +1 -0
  70. package/src/client/components/primitives/menu.tsx +18 -12
  71. package/src/client/components/primitives/navbar.tsx +34 -93
  72. package/src/client/components/primitives/on-this-page.tsx +7 -161
  73. package/src/client/components/primitives/popover.tsx +1 -2
  74. package/src/client/components/primitives/search-dialog.tsx +4 -4
  75. package/src/client/components/primitives/sidebar.tsx +3 -2
  76. package/src/client/components/primitives/skeleton.tsx +26 -0
  77. package/src/client/components/ui-base/copy-markdown.tsx +4 -10
  78. package/src/client/components/ui-base/index.ts +0 -1
  79. package/src/client/components/ui-base/loading.tsx +43 -73
  80. package/src/client/components/ui-base/navbar.tsx +18 -15
  81. package/src/client/components/ui-base/page-nav.tsx +2 -1
  82. package/src/client/components/ui-base/powered-by.tsx +4 -1
  83. package/src/client/components/ui-base/search-dialog.tsx +16 -5
  84. package/src/client/components/ui-base/sidebar.tsx +4 -2
  85. package/src/client/hooks/use-i18n.ts +3 -2
  86. package/src/client/hooks/use-localized-to.ts +6 -5
  87. package/src/client/hooks/use-navbar.ts +37 -6
  88. package/src/client/hooks/use-page-nav.ts +27 -6
  89. package/src/client/hooks/use-routes.ts +2 -1
  90. package/src/client/hooks/use-search.ts +81 -59
  91. package/src/client/hooks/use-sidebar.ts +2 -1
  92. package/src/client/index.ts +0 -1
  93. package/src/client/store/use-boltdocs-store.ts +6 -5
  94. package/src/client/theme/neutral.css +31 -3
  95. package/src/client/types.ts +2 -2
  96. package/src/node/{cli.ts → cli/build.ts} +17 -23
  97. package/src/node/cli/dev.ts +22 -0
  98. package/src/node/cli/doctor.ts +243 -0
  99. package/src/node/cli/index.ts +9 -0
  100. package/src/node/cli/ui.ts +54 -0
  101. package/src/node/cli-entry.ts +16 -16
  102. package/src/node/config.ts +1 -15
  103. package/src/node/mdx/cache.ts +1 -1
  104. package/src/node/mdx/index.ts +2 -0
  105. package/src/node/mdx/rehype-shiki.ts +9 -0
  106. package/src/node/mdx/remark-code-meta.ts +35 -0
  107. package/src/node/mdx/remark-shiki.ts +1 -1
  108. package/src/node/plugin/entry.ts +22 -15
  109. package/src/node/plugin/index.ts +46 -14
  110. package/src/node/routes/parser.ts +12 -9
  111. package/src/node/search/index.ts +55 -0
  112. package/src/node/ssg/index.ts +83 -15
  113. package/src/node/ssg/robots.ts +7 -4
  114. package/dist/chunk-5D6XPYQ3.mjs +0 -74
  115. package/dist/chunk-6QXCKZAT.mjs +0 -1
  116. package/dist/chunk-H4M6P3DM.mjs +0 -1
  117. package/dist/chunk-JXHNX2WN.mjs +0 -1
  118. package/dist/chunk-MZBG4N4W.mjs +0 -1
  119. package/dist/chunk-Q3MLYTIQ.mjs +0 -1
  120. package/dist/chunk-RSII2UPE.mjs +0 -1
  121. package/dist/chunk-ZK2266IZ.mjs +0 -1
  122. package/dist/chunk-ZRJ55GGF.mjs +0 -1
  123. package/dist/search-dialog-MA5AISC7.mjs +0 -1
  124. 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) => set({ currentVersion: version }),
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: 48rem;
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 {
@@ -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 { createServer, build, preview } from 'vite'
2
- import { createViteConfig, resolveConfig } from './index'
3
- import { getHtmlTemplate } from './plugin/html'
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
- * Core logic for the Boltdocs CLI commands.
9
- * These functions wrap Vite's JS API to provide a seamless experience.
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
- console.log('[boltdocs] Build completed successfully.')
27
+ ui.success('Build completed successfully.')
39
28
  } catch (e) {
40
- console.error('[boltdocs] Build failed:', e)
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
- console.error('[boltdocs] Failed to start preview server:', e)
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,9 @@
1
+ /**
2
+ * Boltdocs CLI Actions Module.
3
+ * This module exports all command-line actions used by the Boltdocs tool.
4
+ */
5
+
6
+ export * from './dev'
7
+ export * from './build'
8
+ export * from './doctor'
9
+ export * from './ui'
@@ -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
+ }
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env node
2
- import cac from "cac";
3
- import { devAction, buildAction, previewAction } from "./cli";
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("boltdocs");
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.help();
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("2.0.0");
22
+ cli.version('2.0.0')
23
23
 
24
- cli.parse();
24
+ cli.parse()
@@ -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,
@@ -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 = 'v3'
6
+ export const MDX_PLUGIN_VERSION = 'v4'
7
7
 
8
8
  /**
9
9
  * Persistent cache for MDX transformations.
@@ -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(/^[`'"](.+)[`'"]$/)?.[1] ?? expr
35
+ code = expr.match(/^[`'"]([\s\S]+)[`'"]$/)?.[1] ?? expr
36
36
  }
37
37
  }
38
38