@bfra.me/doc-sync 0.1.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 (50) hide show
  1. package/README.md +288 -0
  2. package/lib/chunk-6NKAJT2M.js +1233 -0
  3. package/lib/chunk-DR6UG237.js +1027 -0
  4. package/lib/chunk-G5KKGJYO.js +1560 -0
  5. package/lib/chunk-ROLA7SBB.js +12 -0
  6. package/lib/cli/index.d.ts +1 -0
  7. package/lib/cli/index.js +397 -0
  8. package/lib/generators/index.d.ts +170 -0
  9. package/lib/generators/index.js +76 -0
  10. package/lib/index.d.ts +141 -0
  11. package/lib/index.js +118 -0
  12. package/lib/parsers/index.d.ts +264 -0
  13. package/lib/parsers/index.js +113 -0
  14. package/lib/types.d.ts +388 -0
  15. package/lib/types.js +7 -0
  16. package/package.json +99 -0
  17. package/src/cli/commands/index.ts +3 -0
  18. package/src/cli/commands/sync.ts +146 -0
  19. package/src/cli/commands/validate.ts +151 -0
  20. package/src/cli/commands/watch.ts +74 -0
  21. package/src/cli/index.ts +71 -0
  22. package/src/cli/types.ts +19 -0
  23. package/src/cli/ui.ts +123 -0
  24. package/src/generators/api-reference-generator.ts +268 -0
  25. package/src/generators/code-example-formatter.ts +313 -0
  26. package/src/generators/component-mapper.ts +383 -0
  27. package/src/generators/content-merger.ts +295 -0
  28. package/src/generators/frontmatter-generator.ts +277 -0
  29. package/src/generators/index.ts +56 -0
  30. package/src/generators/mdx-generator.ts +289 -0
  31. package/src/index.ts +131 -0
  32. package/src/orchestrator/index.ts +21 -0
  33. package/src/orchestrator/package-scanner.ts +276 -0
  34. package/src/orchestrator/sync-orchestrator.ts +382 -0
  35. package/src/orchestrator/validation-pipeline.ts +328 -0
  36. package/src/parsers/export-analyzer.ts +335 -0
  37. package/src/parsers/guards.ts +350 -0
  38. package/src/parsers/index.ts +82 -0
  39. package/src/parsers/jsdoc-extractor.ts +313 -0
  40. package/src/parsers/package-info.ts +267 -0
  41. package/src/parsers/readme-parser.ts +334 -0
  42. package/src/parsers/typescript-parser.ts +299 -0
  43. package/src/types.ts +423 -0
  44. package/src/utils/index.ts +13 -0
  45. package/src/utils/safe-patterns.ts +280 -0
  46. package/src/utils/sanitization.ts +164 -0
  47. package/src/watcher/change-detector.ts +138 -0
  48. package/src/watcher/debouncer.ts +168 -0
  49. package/src/watcher/file-watcher.ts +164 -0
  50. package/src/watcher/index.ts +27 -0
@@ -0,0 +1,295 @@
1
+ /**
2
+ * @bfra.me/doc-sync/generators/content-merger - Preserve manual sections using sentinel markers
3
+ */
4
+
5
+ import type {Result} from '@bfra.me/es/result'
6
+ import type {SyncError} from '../types'
7
+
8
+ import {err, ok} from '@bfra.me/es/result'
9
+ import {SENTINEL_MARKERS} from '../types'
10
+
11
+ /**
12
+ * A section of content extracted from MDX
13
+ */
14
+ export interface ContentSection {
15
+ /** Section type */
16
+ readonly type: 'auto' | 'manual' | 'unchanged'
17
+ /** Section content */
18
+ readonly content: string
19
+ /** Start position in source */
20
+ readonly startIndex: number
21
+ /** End position in source */
22
+ readonly endIndex: number
23
+ }
24
+
25
+ /**
26
+ * Merge options
27
+ */
28
+ export interface MergeOptions {
29
+ /** Strategy for handling conflicts */
30
+ readonly conflictStrategy?: 'keep-manual' | 'keep-auto' | 'merge'
31
+ /** Preserve empty manual sections */
32
+ readonly preserveEmptyManual?: boolean
33
+ }
34
+
35
+ /**
36
+ * Result of a merge operation
37
+ */
38
+ export interface MergeResult {
39
+ /** Merged content */
40
+ readonly content: string
41
+ /** Number of manual sections preserved */
42
+ readonly preservedCount: number
43
+ /** Number of auto-generated sections updated */
44
+ readonly updatedCount: number
45
+ /** Whether content changed */
46
+ readonly hasChanges: boolean
47
+ }
48
+
49
+ export function mergeContent(
50
+ existingContent: string,
51
+ newContent: string,
52
+ options: MergeOptions = {},
53
+ ): Result<MergeResult, SyncError> {
54
+ const {conflictStrategy = 'keep-manual', preserveEmptyManual = true} = options
55
+
56
+ try {
57
+ const existingManualSections = extractManualSections(existingContent)
58
+ const newAutoSections = extractAutoSections(newContent)
59
+
60
+ if (existingManualSections.length === 0 && newAutoSections.length === 0) {
61
+ const hasChanges = existingContent.trim() !== newContent.trim()
62
+ return ok({
63
+ content: newContent,
64
+ preservedCount: 0,
65
+ updatedCount: hasChanges ? 1 : 0,
66
+ hasChanges,
67
+ })
68
+ }
69
+
70
+ let merged = newContent
71
+ let preservedCount = 0
72
+
73
+ for (const manual of existingManualSections) {
74
+ if (!preserveEmptyManual && manual.content.trim().length === 0) {
75
+ continue
76
+ }
77
+
78
+ const manualMarker = `${SENTINEL_MARKERS.MANUAL_START}\n${manual.content}\n${SENTINEL_MARKERS.MANUAL_END}`
79
+
80
+ if (!merged.includes(SENTINEL_MARKERS.MANUAL_START)) {
81
+ const autoEndIndex = merged.indexOf(SENTINEL_MARKERS.AUTO_END)
82
+ if (autoEndIndex === -1) {
83
+ merged = `${merged}\n\n${manualMarker}`
84
+ } else {
85
+ merged = `${merged.slice(0, autoEndIndex + SENTINEL_MARKERS.AUTO_END.length)}\n\n${manualMarker}\n${merged.slice(autoEndIndex + SENTINEL_MARKERS.AUTO_END.length)}`
86
+ }
87
+ } else if (conflictStrategy === 'keep-manual') {
88
+ const existingManualInNew = extractManualSections(merged)
89
+ if (existingManualInNew.length > 0) {
90
+ const firstManualInNew = existingManualInNew[0]
91
+ if (firstManualInNew !== undefined) {
92
+ merged = replaceSection(merged, firstManualInNew, manual.content)
93
+ }
94
+ }
95
+ }
96
+
97
+ preservedCount++
98
+ }
99
+
100
+ const hasChanges = existingContent.trim() !== merged.trim()
101
+
102
+ return ok({
103
+ content: merged,
104
+ preservedCount,
105
+ updatedCount: newAutoSections.length,
106
+ hasChanges,
107
+ })
108
+ } catch (error) {
109
+ return err({
110
+ code: 'GENERATION_ERROR',
111
+ message: `Failed to merge content: ${error instanceof Error ? error.message : String(error)}`,
112
+ cause: error,
113
+ })
114
+ }
115
+ }
116
+
117
+ export function extractManualSections(content: string): ContentSection[] {
118
+ const sections: ContentSection[] = []
119
+ let searchStart = 0
120
+
121
+ while (searchStart < content.length) {
122
+ const startIndex = content.indexOf(SENTINEL_MARKERS.MANUAL_START, searchStart)
123
+ if (startIndex === -1) break
124
+
125
+ const endIndex = content.indexOf(SENTINEL_MARKERS.MANUAL_END, startIndex)
126
+ if (endIndex === -1) break
127
+
128
+ const contentStart = startIndex + SENTINEL_MARKERS.MANUAL_START.length
129
+ const sectionContent = content.slice(contentStart, endIndex).trim()
130
+
131
+ sections.push({
132
+ type: 'manual',
133
+ content: sectionContent,
134
+ startIndex,
135
+ endIndex: endIndex + SENTINEL_MARKERS.MANUAL_END.length,
136
+ })
137
+
138
+ searchStart = endIndex + SENTINEL_MARKERS.MANUAL_END.length
139
+ }
140
+
141
+ return sections
142
+ }
143
+
144
+ export function extractAutoSections(content: string): ContentSection[] {
145
+ const sections: ContentSection[] = []
146
+ let searchStart = 0
147
+
148
+ while (searchStart < content.length) {
149
+ const startIndex = content.indexOf(SENTINEL_MARKERS.AUTO_START, searchStart)
150
+ if (startIndex === -1) break
151
+
152
+ const endIndex = content.indexOf(SENTINEL_MARKERS.AUTO_END, startIndex)
153
+ if (endIndex === -1) break
154
+
155
+ const contentStart = startIndex + SENTINEL_MARKERS.AUTO_START.length
156
+ const sectionContent = content.slice(contentStart, endIndex).trim()
157
+
158
+ sections.push({
159
+ type: 'auto',
160
+ content: sectionContent,
161
+ startIndex,
162
+ endIndex: endIndex + SENTINEL_MARKERS.AUTO_END.length,
163
+ })
164
+
165
+ searchStart = endIndex + SENTINEL_MARKERS.AUTO_END.length
166
+ }
167
+
168
+ return sections
169
+ }
170
+
171
+ function replaceSection(content: string, section: ContentSection, newContent: string): string {
172
+ const marker =
173
+ section.type === 'manual' ? SENTINEL_MARKERS.MANUAL_START : SENTINEL_MARKERS.AUTO_START
174
+ const endMarker =
175
+ section.type === 'manual' ? SENTINEL_MARKERS.MANUAL_END : SENTINEL_MARKERS.AUTO_END
176
+
177
+ const before = content.slice(0, section.startIndex)
178
+ const after = content.slice(section.endIndex)
179
+
180
+ return `${before}${marker}\n${newContent}\n${endMarker}${after}`
181
+ }
182
+
183
+ export function hasManualContent(content: string): boolean {
184
+ return content.includes(SENTINEL_MARKERS.MANUAL_START)
185
+ }
186
+
187
+ export function hasAutoContent(content: string): boolean {
188
+ return content.includes(SENTINEL_MARKERS.AUTO_START)
189
+ }
190
+
191
+ export function wrapManualSection(content: string): string {
192
+ return `${SENTINEL_MARKERS.MANUAL_START}\n${content}\n${SENTINEL_MARKERS.MANUAL_END}`
193
+ }
194
+
195
+ export function wrapAutoSection(content: string): string {
196
+ return `${SENTINEL_MARKERS.AUTO_START}\n${content}\n${SENTINEL_MARKERS.AUTO_END}`
197
+ }
198
+
199
+ export function stripSentinelMarkers(content: string): string {
200
+ return content
201
+ .replaceAll(SENTINEL_MARKERS.AUTO_START, '')
202
+ .replaceAll(SENTINEL_MARKERS.AUTO_END, '')
203
+ .replaceAll(SENTINEL_MARKERS.MANUAL_START, '')
204
+ .replaceAll(SENTINEL_MARKERS.MANUAL_END, '')
205
+ .replaceAll(/\n{3,}/g, '\n\n')
206
+ .trim()
207
+ }
208
+
209
+ export function validateMarkerPairing(content: string): Result<true, SyncError> {
210
+ const autoStartCount = countOccurrences(content, SENTINEL_MARKERS.AUTO_START)
211
+ const autoEndCount = countOccurrences(content, SENTINEL_MARKERS.AUTO_END)
212
+ const manualStartCount = countOccurrences(content, SENTINEL_MARKERS.MANUAL_START)
213
+ const manualEndCount = countOccurrences(content, SENTINEL_MARKERS.MANUAL_END)
214
+
215
+ if (autoStartCount !== autoEndCount) {
216
+ return err({
217
+ code: 'VALIDATION_ERROR',
218
+ message: `Mismatched AUTO-GENERATED markers: ${autoStartCount} starts, ${autoEndCount} ends`,
219
+ })
220
+ }
221
+
222
+ if (manualStartCount !== manualEndCount) {
223
+ return err({
224
+ code: 'VALIDATION_ERROR',
225
+ message: `Mismatched MANUAL-CONTENT markers: ${manualStartCount} starts, ${manualEndCount} ends`,
226
+ })
227
+ }
228
+
229
+ const autoNesting = checkNesting(content, SENTINEL_MARKERS.AUTO_START, SENTINEL_MARKERS.AUTO_END)
230
+ if (!autoNesting) {
231
+ return err({
232
+ code: 'VALIDATION_ERROR',
233
+ message: 'AUTO-GENERATED markers are improperly nested',
234
+ })
235
+ }
236
+
237
+ const manualNesting = checkNesting(
238
+ content,
239
+ SENTINEL_MARKERS.MANUAL_START,
240
+ SENTINEL_MARKERS.MANUAL_END,
241
+ )
242
+ if (!manualNesting) {
243
+ return err({
244
+ code: 'VALIDATION_ERROR',
245
+ message: 'MANUAL-CONTENT markers are improperly nested',
246
+ })
247
+ }
248
+
249
+ return ok(true)
250
+ }
251
+
252
+ function countOccurrences(content: string, substring: string): number {
253
+ let count = 0
254
+ let position = content.indexOf(substring, 0)
255
+
256
+ while (position !== -1) {
257
+ count++
258
+ position = content.indexOf(substring, position + substring.length)
259
+ }
260
+
261
+ return count
262
+ }
263
+
264
+ function checkNesting(content: string, startMarker: string, endMarker: string): boolean {
265
+ let depth = 0
266
+ let position = 0
267
+
268
+ while (position < content.length) {
269
+ const startPos = content.indexOf(startMarker, position)
270
+ const endPos = content.indexOf(endMarker, position)
271
+
272
+ if (startPos === -1 && endPos === -1) break
273
+
274
+ if (startPos !== -1 && (endPos === -1 || startPos < endPos)) {
275
+ depth++
276
+ position = startPos + startMarker.length
277
+ } else {
278
+ depth--
279
+ if (depth < 0) return false
280
+ position = endPos + endMarker.length
281
+ }
282
+ }
283
+
284
+ return depth === 0
285
+ }
286
+
287
+ export function createDiffSummary(oldContent: string, newContent: string): string {
288
+ const oldLines = oldContent.split('\n')
289
+ const newLines = newContent.split('\n')
290
+
291
+ const added = newLines.filter(line => !oldLines.includes(line)).length
292
+ const removed = oldLines.filter(line => !newLines.includes(line)).length
293
+
294
+ return `${added} lines added, ${removed} lines removed`
295
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * @bfra.me/doc-sync/generators/frontmatter-generator - Starlight-compatible frontmatter generation
3
+ */
4
+
5
+ import type {MDXFrontmatter, PackageInfo, ReadmeContent} from '../types'
6
+
7
+ /**
8
+ * Mutable frontmatter for internal construction before freezing
9
+ */
10
+ interface MutableFrontmatter {
11
+ title: string
12
+ description?: string
13
+ sidebar?: MDXFrontmatter['sidebar']
14
+ tableOfContents?: MDXFrontmatter['tableOfContents']
15
+ template?: MDXFrontmatter['template']
16
+ hero?: MDXFrontmatter['hero']
17
+ }
18
+
19
+ export function generateFrontmatter(
20
+ packageInfo: PackageInfo,
21
+ readme: ReadmeContent | undefined,
22
+ overrides?: Partial<MDXFrontmatter>,
23
+ ): MDXFrontmatter {
24
+ const title = resolveTitle(packageInfo, readme, overrides?.title)
25
+ const description = resolveDescription(packageInfo, readme, overrides?.description)
26
+
27
+ const frontmatter: MutableFrontmatter = {
28
+ title,
29
+ ...(description !== undefined && {description}),
30
+ ...(overrides?.sidebar !== undefined && {sidebar: overrides.sidebar}),
31
+ ...(overrides?.tableOfContents !== undefined && {tableOfContents: overrides.tableOfContents}),
32
+ ...(overrides?.template !== undefined && {template: overrides.template}),
33
+ ...(overrides?.hero !== undefined && {hero: overrides.hero}),
34
+ }
35
+
36
+ if (packageInfo.docsConfig?.sidebar !== undefined) {
37
+ frontmatter.sidebar = {
38
+ ...packageInfo.docsConfig.sidebar,
39
+ ...frontmatter.sidebar,
40
+ }
41
+ }
42
+
43
+ if (packageInfo.docsConfig?.frontmatter !== undefined) {
44
+ const customFrontmatter = packageInfo.docsConfig.frontmatter
45
+ return {...customFrontmatter, ...frontmatter} as MDXFrontmatter
46
+ }
47
+
48
+ return frontmatter as MDXFrontmatter
49
+ }
50
+
51
+ function resolveTitle(
52
+ packageInfo: PackageInfo,
53
+ readme: ReadmeContent | undefined,
54
+ override?: string,
55
+ ): string {
56
+ if (override !== undefined) {
57
+ return override
58
+ }
59
+
60
+ if (packageInfo.docsConfig?.title !== undefined) {
61
+ return packageInfo.docsConfig.title
62
+ }
63
+
64
+ if (readme?.title !== undefined) {
65
+ return readme.title
66
+ }
67
+
68
+ return packageInfo.name
69
+ }
70
+
71
+ function resolveDescription(
72
+ packageInfo: PackageInfo,
73
+ readme: ReadmeContent | undefined,
74
+ override?: string,
75
+ ): string | undefined {
76
+ if (override !== undefined) {
77
+ return override
78
+ }
79
+
80
+ if (packageInfo.docsConfig?.description !== undefined) {
81
+ return packageInfo.docsConfig.description
82
+ }
83
+
84
+ if (packageInfo.description !== undefined) {
85
+ return packageInfo.description
86
+ }
87
+
88
+ if (readme?.preamble !== undefined) {
89
+ return extractFirstSentence(readme.preamble)
90
+ }
91
+
92
+ return undefined
93
+ }
94
+
95
+ function extractFirstSentence(text: string): string {
96
+ const cleaned = text.trim().replaceAll(/\s+/g, ' ')
97
+
98
+ const sentenceEnd = cleaned.search(/[.!?](?:\s|$)/)
99
+ if (sentenceEnd > 0) {
100
+ return cleaned.slice(0, sentenceEnd + 1)
101
+ }
102
+
103
+ if (cleaned.length > 160) {
104
+ const truncated = cleaned.slice(0, 157)
105
+ const lastSpace = truncated.lastIndexOf(' ')
106
+ if (lastSpace > 100) {
107
+ return `${truncated.slice(0, lastSpace)}...`
108
+ }
109
+ return `${truncated}...`
110
+ }
111
+
112
+ return cleaned
113
+ }
114
+
115
+ export function stringifyFrontmatter(frontmatter: MDXFrontmatter): string {
116
+ const lines: string[] = []
117
+
118
+ lines.push(`title: ${yamlString(frontmatter.title)}`)
119
+
120
+ if (frontmatter.description !== undefined) {
121
+ lines.push(`description: ${yamlString(frontmatter.description)}`)
122
+ }
123
+
124
+ if (frontmatter.sidebar !== undefined) {
125
+ lines.push('sidebar:')
126
+ const sidebar = frontmatter.sidebar
127
+
128
+ if (sidebar.label !== undefined) {
129
+ lines.push(` label: ${yamlString(sidebar.label)}`)
130
+ }
131
+
132
+ if (sidebar.order !== undefined) {
133
+ lines.push(` order: ${sidebar.order}`)
134
+ }
135
+
136
+ if (sidebar.hidden !== undefined) {
137
+ lines.push(` hidden: ${sidebar.hidden}`)
138
+ }
139
+
140
+ if (sidebar.badge !== undefined) {
141
+ if (typeof sidebar.badge === 'string') {
142
+ lines.push(` badge: ${yamlString(sidebar.badge)}`)
143
+ } else {
144
+ lines.push(' badge:')
145
+ lines.push(` text: ${yamlString(sidebar.badge.text)}`)
146
+ if (sidebar.badge.variant !== undefined) {
147
+ lines.push(` variant: ${sidebar.badge.variant}`)
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ if (frontmatter.tableOfContents !== undefined) {
154
+ if (typeof frontmatter.tableOfContents === 'boolean') {
155
+ lines.push(`tableOfContents: ${frontmatter.tableOfContents}`)
156
+ } else {
157
+ lines.push('tableOfContents:')
158
+ if (frontmatter.tableOfContents.minHeadingLevel !== undefined) {
159
+ lines.push(` minHeadingLevel: ${frontmatter.tableOfContents.minHeadingLevel}`)
160
+ }
161
+ if (frontmatter.tableOfContents.maxHeadingLevel !== undefined) {
162
+ lines.push(` maxHeadingLevel: ${frontmatter.tableOfContents.maxHeadingLevel}`)
163
+ }
164
+ }
165
+ }
166
+
167
+ if (frontmatter.template !== undefined) {
168
+ lines.push(`template: ${frontmatter.template}`)
169
+ }
170
+
171
+ if (frontmatter.hero !== undefined) {
172
+ lines.push('hero:')
173
+ const hero = frontmatter.hero
174
+
175
+ if (hero.title !== undefined) {
176
+ lines.push(` title: ${yamlString(hero.title)}`)
177
+ }
178
+
179
+ if (hero.tagline !== undefined) {
180
+ lines.push(` tagline: ${yamlString(hero.tagline)}`)
181
+ }
182
+
183
+ if (hero.image !== undefined) {
184
+ lines.push(' image:')
185
+ lines.push(` src: ${yamlString(hero.image.src)}`)
186
+ lines.push(` alt: ${yamlString(hero.image.alt)}`)
187
+ }
188
+
189
+ if (hero.actions !== undefined && hero.actions.length > 0) {
190
+ lines.push(' actions:')
191
+ for (const action of hero.actions) {
192
+ lines.push(` - text: ${yamlString(action.text)}`)
193
+ lines.push(` link: ${yamlString(action.link)}`)
194
+ if (action.icon !== undefined) {
195
+ lines.push(` icon: ${yamlString(action.icon)}`)
196
+ }
197
+ if (action.variant !== undefined) {
198
+ lines.push(` variant: ${action.variant}`)
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ return lines.join('\n')
205
+ }
206
+
207
+ function yamlString(value: string): string {
208
+ if (needsQuoting(value)) {
209
+ return `'${value.replaceAll("'", "''")}'`
210
+ }
211
+ return value
212
+ }
213
+
214
+ function needsQuoting(value: string): boolean {
215
+ if (value.length === 0) {
216
+ return true
217
+ }
218
+
219
+ if (value.startsWith(' ') || value.endsWith(' ')) {
220
+ return true
221
+ }
222
+
223
+ const specialChars = /[:#{}[\]&*?|<>=!%@`"',]/
224
+ if (specialChars.test(value)) {
225
+ return true
226
+ }
227
+
228
+ const reserved = ['true', 'false', 'null', 'yes', 'no', 'on', 'off']
229
+ if (reserved.includes(value.toLowerCase())) {
230
+ return true
231
+ }
232
+
233
+ if (/^-?\d+(?:\.\d+)?$/.test(value)) {
234
+ return true
235
+ }
236
+
237
+ return false
238
+ }
239
+
240
+ /**
241
+ * Mutable frontmatter for internal parsing
242
+ */
243
+ interface MutableParsedFrontmatter {
244
+ title?: string
245
+ description?: string
246
+ }
247
+
248
+ export function parseFrontmatter(yaml: string): Partial<MDXFrontmatter> {
249
+ const result: MutableParsedFrontmatter = {}
250
+ const lines = yaml.split('\n')
251
+
252
+ for (const line of lines) {
253
+ const trimmedLine = line.trim()
254
+
255
+ if (trimmedLine.startsWith('title:')) {
256
+ const value = trimmedLine.slice(6).trim()
257
+ result.title = stripQuotes(value)
258
+ }
259
+
260
+ if (trimmedLine.startsWith('description:')) {
261
+ const value = trimmedLine.slice(12).trim()
262
+ result.description = stripQuotes(value)
263
+ }
264
+ }
265
+
266
+ return result as Partial<MDXFrontmatter>
267
+ }
268
+
269
+ function stripQuotes(value: string): string {
270
+ if (
271
+ (value.startsWith("'") && value.endsWith("'")) ||
272
+ (value.startsWith('"') && value.endsWith('"'))
273
+ ) {
274
+ return value.slice(1, -1)
275
+ }
276
+ return value
277
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @bfra.me/doc-sync/generators - Unified export of all generator modules
3
+ */
4
+
5
+ export {
6
+ generateAPICompact,
7
+ generateAPIReference,
8
+ generateCategoryReference,
9
+ } from './api-reference-generator'
10
+
11
+ export {
12
+ cleanCodeExample,
13
+ detectLanguage,
14
+ formatCodeBlock,
15
+ formatCodeExamples,
16
+ formatFunctionExamples,
17
+ formatGroupedExamples,
18
+ formatTypeExamples,
19
+ formatUsageExample,
20
+ groupExamplesByCategory,
21
+ } from './code-example-formatter'
22
+ export type {CodeExampleOptions} from './code-example-formatter'
23
+
24
+ export {
25
+ createBadge,
26
+ createCard,
27
+ createCardGrid,
28
+ createTabs,
29
+ generateInstallTabs,
30
+ mapToStarlightComponents,
31
+ } from './component-mapper'
32
+ export type {ComponentMapperConfig, SectionMapper} from './component-mapper'
33
+
34
+ export {
35
+ createDiffSummary,
36
+ extractAutoSections,
37
+ extractManualSections,
38
+ hasAutoContent,
39
+ hasManualContent,
40
+ mergeContent,
41
+ stripSentinelMarkers,
42
+ validateMarkerPairing,
43
+ wrapAutoSection,
44
+ wrapManualSection,
45
+ } from './content-merger'
46
+ export type {ContentSection, MergeOptions, MergeResult} from './content-merger'
47
+
48
+ export {generateFrontmatter, parseFrontmatter, stringifyFrontmatter} from './frontmatter-generator'
49
+
50
+ export {
51
+ generateMDXDocument,
52
+ sanitizeContent,
53
+ sanitizeTextContent,
54
+ validateMDXSyntax,
55
+ } from './mdx-generator'
56
+ export type {MDXGeneratorOptions} from './mdx-generator'