boltdocs 1.11.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/dist/cache-7G6D532T.mjs +1 -0
  4. package/dist/chunk-A4HQPEPU.mjs +1 -0
  5. package/dist/chunk-BA5NH5HU.mjs +1 -0
  6. package/dist/chunk-BQCD3DWG.mjs +1 -0
  7. package/dist/chunk-H63UMKYF.mjs +1 -0
  8. package/dist/chunk-IWHRQHS7.mjs +1 -0
  9. package/dist/chunk-JZXLCA2E.mjs +1 -0
  10. package/dist/chunk-MFU7Q6WF.mjs +1 -0
  11. package/dist/chunk-QYPNX5UN.mjs +1 -0
  12. package/dist/chunk-XEAPSFMB.mjs +1 -0
  13. package/dist/client/components/mdx/index.d.mts +209 -0
  14. package/dist/client/components/mdx/index.d.ts +209 -0
  15. package/dist/client/components/mdx/index.js +1 -0
  16. package/dist/client/components/mdx/index.mjs +1 -0
  17. package/dist/client/hooks/index.d.mts +133 -0
  18. package/dist/client/hooks/index.d.ts +133 -0
  19. package/dist/client/hooks/index.js +1 -0
  20. package/dist/client/hooks/index.mjs +1 -0
  21. package/dist/client/index.d.mts +212 -0
  22. package/dist/client/index.d.ts +212 -0
  23. package/dist/client/index.js +1 -0
  24. package/dist/client/index.mjs +1 -0
  25. package/dist/client/ssr.d.mts +31 -0
  26. package/dist/client/ssr.d.ts +31 -0
  27. package/dist/client/ssr.js +1 -0
  28. package/dist/client/ssr.mjs +1 -0
  29. package/dist/config-CX4l-ZNp.d.mts +166 -0
  30. package/dist/config-CX4l-ZNp.d.ts +166 -0
  31. package/dist/node/index.d.mts +89 -0
  32. package/dist/node/index.d.ts +89 -0
  33. package/dist/node/index.js +57 -0
  34. package/dist/node/index.mjs +57 -0
  35. package/dist/search-dialog-EB3N4TYM.mjs +1 -0
  36. package/dist/types-BuZWFT7r.d.ts +159 -0
  37. package/dist/types-CvT-SGbK.d.mts +159 -0
  38. package/dist/use-routes-5bAtAAYX.d.mts +30 -0
  39. package/dist/use-routes-BefRXY3v.d.ts +30 -0
  40. package/package.json +10 -10
  41. package/src/client/app/index.tsx +9 -6
  42. package/src/client/types.ts +1 -1
  43. package/src/node/plugin/index.ts +0 -1
  44. package/src/node/routes/parser.ts +27 -32
  45. package/src/node/ssg/index.ts +29 -3
  46. package/src/node/utils.ts +23 -0
@@ -9,6 +9,8 @@ import {
9
9
  capitalize,
10
10
  stripNumberPrefix,
11
11
  extractNumberPrefix,
12
+ sanitizeHtml,
13
+ stripHtmlTags,
12
14
  } from '../utils'
13
15
 
14
16
  /**
@@ -118,47 +120,47 @@ export function parseDocFile(
118
120
 
119
121
  const isGroupIndex = parts.length >= 2 && /^index\.mdx?$/.test(cleanFileName)
120
122
 
121
- const headings: { level: number; text: string; id: string }[] = []
122
123
  const slugger = new GithubSlugger()
124
+ const headings: { level: number; text: string; id: string }[] = []
123
125
  const headingsRegex = /^(#{2,4})\s+(.+)$/gm
126
+
124
127
  let match
125
128
  while ((match = headingsRegex.exec(content)) !== null) {
126
129
  const level = match[1].length
127
- // Strip simple markdown formatting specifically for the plain-text search index
128
- const text = match[2]
129
- .replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
130
- .replace(/[_*`]/g, '')
131
- .trim()
132
- const id = slugger.slug(text)
133
- // Security: Sanitize heading text for XSS
134
- const sanitizedText = text
135
- .replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gim, '')
136
- .replace(/<[^>]+on\w+="[^"]*"/gim, '')
137
- .replace(/<img[^>]+>/gim, '')
130
+ const rawText = match[2]
131
+ .replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1') // Strip markdown links
132
+ .replace(/[_*`]/g, '') // Strip markdown formatting
138
133
  .trim()
134
+
135
+ const sanitizedText = sanitizeHtml(rawText).trim()
136
+ const id = slugger.slug(sanitizedText)
137
+
139
138
  headings.push({ level, text: sanitizedText, id })
140
139
  }
141
140
 
142
- const sanitize = (str: string) =>
143
- str.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gim, '').trim()
144
-
145
- const sanitizedTitle = data.title ? sanitize(data.title) : inferredTitle
146
- let sanitizedDescription = data.description ? sanitize(data.description) : ''
141
+ const sanitizedTitle = data.title
142
+ ? sanitizeHtml(String(data.title))
143
+ : inferredTitle
144
+ let sanitizedDescription = data.description
145
+ ? sanitizeHtml(String(data.description))
146
+ : ''
147
147
 
148
148
  // If no description is provided, extract a summary from the content
149
149
  if (!sanitizedDescription && content) {
150
- sanitizedDescription = sanitize(
150
+ const plainExcerpt = stripHtmlTags(
151
151
  content
152
152
  .replace(/^#+.*$/gm, '') // Remove headers
153
153
  .replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1') // Simplify links
154
154
  .replace(/[_*`]/g, '') // Remove formatting
155
- .replace(/\s+/g, ' ') // Normalize whitespace
156
- .trim()
157
- .slice(0, 160),
155
+ .replace(/\s+/g, ' '), // Normalize whitespace
158
156
  )
157
+ .trim()
158
+ .slice(0, 160)
159
+
160
+ sanitizedDescription = plainExcerpt
159
161
  }
160
162
 
161
- const sanitizedBadge = data.badge ? sanitize(data.badge) : undefined
163
+ const sanitizedBadge = data.badge ? sanitizeHtml(String(data.badge)) : undefined
162
164
  const icon = data.icon ? String(data.icon) : undefined
163
165
 
164
166
  // Extract full content as plain text for search indexing
@@ -203,24 +205,17 @@ export function parseDocFile(
203
205
  }
204
206
  }
205
207
 
206
- /**
207
- * Sanitizes a string by removing script tags for basic XSS protection.
208
- */
209
- function sanitize(str: string): string {
210
- return str.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gim, '').trim()
211
- }
212
-
213
208
  /**
214
209
  * Converts markdown content to plain text for search indexing.
215
210
  * Strips headers, links, tags, and formatting.
216
211
  */
217
212
  function parseContentToPlainText(content: string): string {
218
- return content
213
+ const plainText = content
219
214
  .replace(/^#+.*$/gm, '') // Remove headers
220
215
  .replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1') // Simplify links
221
- .replace(/<[^>]+>/g, '') // Remove HTML/JSX tags
222
216
  .replace(/\{[^\}]+\}/g, '') // Remove JS expressions/curly braces
223
217
  .replace(/[_*`]/g, '') // Remove formatting
224
218
  .replace(/\s+/g, ' ') // Normalize whitespace
225
- .trim()
219
+
220
+ return stripHtmlTags(plainText).trim()
226
221
  }
@@ -39,8 +39,34 @@ export async function generateStaticPages(options: SSGOptions): Promise<void> {
39
39
  )
40
40
  return
41
41
  }
42
+
43
+ // Mock require so Node doesn't choke on virtual modules compiled externally
44
+ const Module = _require('module')
45
+ const originalRequire = Module.prototype.require
46
+ ;(Module.prototype as any).require = function (id: string, ...args: any[]) {
47
+ if (id === 'virtual:boltdocs-layout') {
48
+ return {
49
+ __esModule: true,
50
+ default: function SSG_Virtual_Layout(props: any) {
51
+ try {
52
+ const client = originalRequire.apply(this, [path.resolve(_dirname, '../client/index.js')])
53
+ const Comp = client.DefaultLayout || (({ children }: any) => children)
54
+ const React = originalRequire.apply(this, ['react'])
55
+ return React.createElement(Comp, props)
56
+ } catch (e) {
57
+ return props.children
58
+ }
59
+ }
60
+ }
61
+ }
62
+ return originalRequire.apply(this, [id, ...args])
63
+ }
64
+
42
65
  const { render } = _require(ssrModulePath)
43
66
 
67
+ // Restore require after loading the module
68
+ ;(Module.prototype as any).require = originalRequire
69
+
44
70
  // Read the built index.html as template
45
71
  const templatePath = path.join(outDir, 'index.html')
46
72
  if (!fs.existsSync(templatePath)) {
@@ -57,7 +83,7 @@ export async function generateStaticPages(options: SSGOptions): Promise<void> {
57
83
 
58
84
  // We mock the modules for SSR so it doesn't crash trying to dynamically import
59
85
  const fakeModules: Record<string, any> = {}
60
- fakeModules[route.componentPath] = { default: () => {} } // Mock MDX component
86
+ fakeModules[`/${docsDirName}/${route.filePath}`] = { default: () => null } // Mock MDX component
61
87
 
62
88
  try {
63
89
  const appHtml = await render({
@@ -83,8 +109,8 @@ export async function generateStaticPages(options: SSGOptions): Promise<void> {
83
109
  html,
84
110
  'utf-8',
85
111
  )
86
- } catch (e) {
87
- console.error(`[boltdocs] Error SSR rendering route ${route.path}:`, e)
112
+ } catch (e: any) {
113
+ console.error(`[boltdocs] Error SSR rendering route ${route.path}:`, e ? e.stack || e : e)
88
114
  }
89
115
  }),
90
116
  )
package/src/node/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs'
2
2
  import matter from 'gray-matter'
3
+ import DOMPurify from 'isomorphic-dompurify'
3
4
 
4
5
  /**
5
6
  * Normalizes a file path by replacing Windows backslashes with forward slashes.
@@ -136,6 +137,28 @@ export function fileToRoutePath(relativePath: string): string {
136
137
  return routePath
137
138
  }
138
139
 
140
+ /**
141
+ * Sanitizes an HTML string using DOMPurify to prevent XSS.
142
+ * By default, it allows a safe subset of HTML tags.
143
+ *
144
+ * @param html - The raw HTML string
145
+ * @returns The sanitized HTML
146
+ */
147
+ export function sanitizeHtml(html: string): string {
148
+ return DOMPurify.sanitize(html)
149
+ }
150
+
151
+ /**
152
+ * Strips all HTML tags from a string, returning only the text content.
153
+ * Uses DOMPurify for secure and complete tag removal.
154
+ *
155
+ * @param html - The string containing HTML tags
156
+ * @returns The plain text content
157
+ */
158
+ export function stripHtmlTags(html: string): string {
159
+ return DOMPurify.sanitize(html, { ALLOWED_TAGS: [], KEEP_CONTENT: true })
160
+ }
161
+
139
162
  /**
140
163
  * Capitalizes the first letter of a given string.
141
164
  * Used primarily for generating default group titles.