@bfra.me/doc-sync 0.1.0 → 0.1.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/lib/chunk-45NROJIG.js +327 -0
- package/lib/chunk-DRBRT57F.js +1 -0
- package/lib/chunk-GZ2MP3VN.js +261 -0
- package/lib/{chunk-G5KKGJYO.js → chunk-LOB73H77.js} +18 -260
- package/lib/{chunk-DR6UG237.js → chunk-NC7YTZAL.js} +20 -334
- package/lib/chunk-SQSYXPIF.js +1 -0
- package/lib/cli/index.js +4 -2
- package/lib/generators/index.d.ts +1 -3
- package/lib/generators/index.js +2 -1
- package/lib/index.d.ts +7 -139
- package/lib/index.js +146 -13
- package/lib/orchestrator/index.d.ts +82 -0
- package/lib/orchestrator/index.js +27 -0
- package/lib/utils/index.d.ts +140 -0
- package/lib/utils/index.js +24 -0
- package/lib/watcher/index.d.ts +62 -0
- package/lib/watcher/index.js +25 -0
- package/package.json +17 -2
- package/src/generators/mdx-generator.ts +18 -17
- package/src/index.ts +82 -0
- package/src/utils/safe-patterns.ts +6 -2
|
@@ -169,15 +169,12 @@ function renderMDXDocument(frontmatter: MDXFrontmatter, content: string): string
|
|
|
169
169
|
* Prevents XSS by escaping potentially dangerous content
|
|
170
170
|
*/
|
|
171
171
|
export function sanitizeContent(content: string): string {
|
|
172
|
-
// Use comprehensive sanitization from utils
|
|
173
172
|
return sanitizeForMDX(content)
|
|
174
173
|
}
|
|
175
174
|
|
|
176
175
|
/**
|
|
177
176
|
* Sanitizes content within MDX while preserving JSX components
|
|
178
|
-
*
|
|
179
|
-
* Now includes sanitization of JSX component attributes to prevent XSS
|
|
180
|
-
* Uses safe, non-backtracking parsing to prevent ReDoS
|
|
177
|
+
* Sanitizes JSX component attributes to prevent XSS while leaving closing tags unchanged
|
|
181
178
|
*/
|
|
182
179
|
export function sanitizeTextContent(content: string): string {
|
|
183
180
|
const jsxTags = parseJSXTags(content)
|
|
@@ -189,12 +186,7 @@ export function sanitizeTextContent(content: string): string {
|
|
|
189
186
|
parts.push(sanitizeContent(content.slice(lastIndex, index)))
|
|
190
187
|
}
|
|
191
188
|
|
|
192
|
-
|
|
193
|
-
parts.push(tag)
|
|
194
|
-
} else {
|
|
195
|
-
parts.push(sanitizeJSXTag(tag))
|
|
196
|
-
}
|
|
197
|
-
|
|
189
|
+
parts.push(isClosing ? tag : sanitizeJSXTag(tag))
|
|
198
190
|
lastIndex = index + tag.length
|
|
199
191
|
}
|
|
200
192
|
|
|
@@ -225,23 +217,32 @@ export function validateMDXSyntax(mdx: string): Result<true, SyncError> {
|
|
|
225
217
|
return ok(true)
|
|
226
218
|
}
|
|
227
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Checks if a tag name is likely a TypeScript generic parameter rather than a JSX component
|
|
222
|
+
* Single uppercase letters (T, E, K, V, etc.) are common generic type parameters
|
|
223
|
+
*/
|
|
224
|
+
function isTypeScriptGeneric(tag: string): boolean {
|
|
225
|
+
const tagNameMatch = tag.match(/<\/?([A-Z][a-zA-Z0-9]*)/)
|
|
226
|
+
const tagName = tagNameMatch?.[1]
|
|
227
|
+
return tagName !== undefined && tagName.length === 1
|
|
228
|
+
}
|
|
229
|
+
|
|
228
230
|
function checkForUnclosedTags(mdx: string): string[] {
|
|
229
231
|
const unclosed: string[] = []
|
|
230
232
|
const tagStack: string[] = []
|
|
231
233
|
|
|
232
|
-
// Remove code blocks
|
|
233
|
-
//
|
|
234
|
-
// misinterpreted as unclosed JSX tags
|
|
234
|
+
// Remove code blocks and inline code to prevent TypeScript generics like Result<T, E>
|
|
235
|
+
// from being misinterpreted as JSX tags
|
|
235
236
|
const codeBlocks = extractCodeBlocks(mdx)
|
|
236
|
-
let
|
|
237
|
+
let contentWithoutCode = mdx
|
|
237
238
|
for (const block of codeBlocks) {
|
|
238
|
-
// Replace code block with empty lines to preserve line numbers
|
|
239
239
|
const lineCount = block.split('\n').length
|
|
240
240
|
const placeholder = '\n'.repeat(lineCount)
|
|
241
|
-
|
|
241
|
+
contentWithoutCode = contentWithoutCode.replace(block, placeholder)
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
const
|
|
244
|
+
const allJSXTags = parseJSXTags(contentWithoutCode)
|
|
245
|
+
const jsxTags = allJSXTags.filter(({tag}) => !isTypeScriptGeneric(tag))
|
|
245
246
|
|
|
246
247
|
for (const {tag, isClosing, isSelfClosing} of jsxTags) {
|
|
247
248
|
const tagNameMatch = isClosing
|
package/src/index.ts
CHANGED
|
@@ -71,6 +71,75 @@ export type {
|
|
|
71
71
|
ValidationWarning,
|
|
72
72
|
} from './orchestrator'
|
|
73
73
|
|
|
74
|
+
// Re-export parsers
|
|
75
|
+
export {
|
|
76
|
+
analyzePublicAPI,
|
|
77
|
+
analyzeTypeScriptContent,
|
|
78
|
+
analyzeTypeScriptFile,
|
|
79
|
+
assertPackageAPI,
|
|
80
|
+
assertPackageInfo,
|
|
81
|
+
assertParseError,
|
|
82
|
+
buildDocSlug,
|
|
83
|
+
createProject,
|
|
84
|
+
extractDocsConfig,
|
|
85
|
+
extractExportedFunctions,
|
|
86
|
+
extractExportedTypes,
|
|
87
|
+
extractJSDocInfo,
|
|
88
|
+
extractPackageAPI,
|
|
89
|
+
extractReExports,
|
|
90
|
+
findEntryPoint,
|
|
91
|
+
findExportedSymbols,
|
|
92
|
+
findReadmePath,
|
|
93
|
+
findSection,
|
|
94
|
+
flattenSections,
|
|
95
|
+
getExportedSymbolInfo,
|
|
96
|
+
getExportsByKind,
|
|
97
|
+
getPackageScope,
|
|
98
|
+
getSectionsByLevel,
|
|
99
|
+
getTableOfContents,
|
|
100
|
+
getUnscopedName,
|
|
101
|
+
hasJSDoc,
|
|
102
|
+
isDocConfigSource,
|
|
103
|
+
isExportedFunction,
|
|
104
|
+
isExportedType,
|
|
105
|
+
isJSDocInfo,
|
|
106
|
+
isJSDocParam,
|
|
107
|
+
isJSDocTag,
|
|
108
|
+
isMDXFrontmatter,
|
|
109
|
+
isPackageAPI,
|
|
110
|
+
isPackageInfo,
|
|
111
|
+
isParseError,
|
|
112
|
+
isReadmeContent,
|
|
113
|
+
isReadmeSection,
|
|
114
|
+
isReExport,
|
|
115
|
+
isSafeContent,
|
|
116
|
+
isSafeFilePath,
|
|
117
|
+
isSymbolExported,
|
|
118
|
+
isSyncError,
|
|
119
|
+
isValidHeadingLevel,
|
|
120
|
+
isValidPackageName,
|
|
121
|
+
isValidSemver,
|
|
122
|
+
parseJSDoc,
|
|
123
|
+
parsePackageComplete,
|
|
124
|
+
parsePackageJson,
|
|
125
|
+
parsePackageJsonContent,
|
|
126
|
+
parseReadme,
|
|
127
|
+
parseReadmeFile,
|
|
128
|
+
parseSourceContent,
|
|
129
|
+
parseSourceFile,
|
|
130
|
+
} from './parsers'
|
|
131
|
+
|
|
132
|
+
export type {
|
|
133
|
+
ExportAnalyzerOptions,
|
|
134
|
+
JSDocableDeclaration,
|
|
135
|
+
PackageInfoOptions,
|
|
136
|
+
PackageJsonSchema,
|
|
137
|
+
PublicAPIAnalysis,
|
|
138
|
+
ReadmeParserOptions,
|
|
139
|
+
ResolvedExport,
|
|
140
|
+
TypeScriptParserOptions,
|
|
141
|
+
} from './parsers'
|
|
142
|
+
|
|
74
143
|
export type {
|
|
75
144
|
CLIOptions,
|
|
76
145
|
DocConfig,
|
|
@@ -102,6 +171,19 @@ export type {
|
|
|
102
171
|
|
|
103
172
|
export {SENTINEL_MARKERS} from './types'
|
|
104
173
|
|
|
174
|
+
// Re-export utils
|
|
175
|
+
export {
|
|
176
|
+
createHeadingPattern,
|
|
177
|
+
extractCodeBlocks,
|
|
178
|
+
findEmptyMarkdownLinks,
|
|
179
|
+
hasComponent,
|
|
180
|
+
parseJSXAttributes,
|
|
181
|
+
parseJSXTags,
|
|
182
|
+
sanitizeAttribute,
|
|
183
|
+
sanitizeForMDX,
|
|
184
|
+
sanitizeJSXTag,
|
|
185
|
+
} from './utils'
|
|
186
|
+
|
|
105
187
|
// Re-export watcher
|
|
106
188
|
export {
|
|
107
189
|
categorizeFile,
|
|
@@ -51,11 +51,11 @@ export function hasComponent(content: string, componentName: string): boolean {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* Extract code blocks from markdown content using unified/remark (safe, no regex)
|
|
54
|
+
* Extract code blocks and inline code from markdown content using unified/remark (safe, no regex)
|
|
55
55
|
* This approach uses AST parsing instead of regex to avoid ReDoS vulnerabilities
|
|
56
56
|
*
|
|
57
57
|
* @param content - The markdown content to parse
|
|
58
|
-
* @returns Array of code block strings with
|
|
58
|
+
* @returns Array of code block strings (fenced blocks with backticks, inline code with backticks)
|
|
59
59
|
*
|
|
60
60
|
* @example
|
|
61
61
|
* ```ts
|
|
@@ -91,6 +91,10 @@ export function extractCodeBlocks(content: string): readonly string[] {
|
|
|
91
91
|
const value = node.value ?? ''
|
|
92
92
|
blocks.push(`\`\`\`${lang}\n${value}\n\`\`\``)
|
|
93
93
|
}
|
|
94
|
+
if (node.type === 'inlineCode') {
|
|
95
|
+
const value = node.value ?? ''
|
|
96
|
+
blocks.push(`\`${value}\``)
|
|
97
|
+
}
|
|
94
98
|
if (Array.isArray(node.children)) {
|
|
95
99
|
for (const child of node.children) {
|
|
96
100
|
visit(child)
|