@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,12 @@
1
+ // src/types.ts
2
+ var SENTINEL_MARKERS = {
3
+ AUTO_START: "{/* AUTO-GENERATED-START */}",
4
+ AUTO_END: "{/* AUTO-GENERATED-END */}",
5
+ MANUAL_START: "{/* MANUAL-CONTENT-START */}",
6
+ MANUAL_END: "{/* MANUAL-CONTENT-END */}"
7
+ };
8
+
9
+ export {
10
+ SENTINEL_MARKERS
11
+ };
12
+ //# sourceMappingURL=chunk-ROLA7SBB.js.map
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,397 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createPackageScanner,
4
+ createSyncOrchestrator,
5
+ createValidationPipeline
6
+ } from "../chunk-DR6UG237.js";
7
+ import {
8
+ generateMDXDocument,
9
+ mergeContent
10
+ } from "../chunk-G5KKGJYO.js";
11
+ import "../chunk-ROLA7SBB.js";
12
+ import {
13
+ getUnscopedName
14
+ } from "../chunk-6NKAJT2M.js";
15
+
16
+ // src/cli/index.ts
17
+ import process4 from "process";
18
+ import { cac } from "cac";
19
+
20
+ // src/cli/commands/sync.ts
21
+ import path from "path";
22
+ import process from "process";
23
+
24
+ // src/cli/ui.ts
25
+ import * as p from "@clack/prompts";
26
+ import { consola } from "consola";
27
+ function createLogger(options) {
28
+ const { verbose = false, quiet = false } = options;
29
+ return {
30
+ info(message) {
31
+ if (!quiet) {
32
+ p.log.info(message);
33
+ }
34
+ },
35
+ success(message) {
36
+ if (!quiet) {
37
+ p.log.success(message);
38
+ }
39
+ },
40
+ warn(message) {
41
+ p.log.warn(message);
42
+ },
43
+ error(message) {
44
+ p.log.error(message);
45
+ },
46
+ debug(message) {
47
+ if (verbose) {
48
+ consola.debug(message);
49
+ }
50
+ }
51
+ };
52
+ }
53
+ function showIntro(title) {
54
+ p.intro(title);
55
+ }
56
+ function showOutro(message) {
57
+ p.outro(message);
58
+ }
59
+ function createSpinner() {
60
+ return p.spinner();
61
+ }
62
+ async function selectPackages(availablePackages) {
63
+ if (availablePackages.length === 0) {
64
+ return [];
65
+ }
66
+ const options = availablePackages.map((pkg) => ({
67
+ value: pkg.value,
68
+ label: pkg.label,
69
+ hint: pkg.hint
70
+ }));
71
+ const selected = await p.multiselect({
72
+ message: "Select packages to sync",
73
+ options,
74
+ required: false
75
+ });
76
+ return selected;
77
+ }
78
+ function handleCancel(value) {
79
+ return p.isCancel(value);
80
+ }
81
+ function showCancel(message = "Operation cancelled.") {
82
+ p.cancel(message);
83
+ }
84
+ function formatDuration(ms) {
85
+ if (ms < 1e3) {
86
+ return `${ms}ms`;
87
+ }
88
+ if (ms < 6e4) {
89
+ return `${(ms / 1e3).toFixed(1)}s`;
90
+ }
91
+ const minutes = Math.floor(ms / 6e4);
92
+ const seconds = Math.round(ms % 6e4 / 1e3);
93
+ return `${minutes}m ${seconds}s`;
94
+ }
95
+ function formatPackageList(packages) {
96
+ if (packages.length === 0) {
97
+ return "no packages";
98
+ }
99
+ if (packages.length === 1) {
100
+ return packages[0] ?? "unknown";
101
+ }
102
+ if (packages.length <= 3) {
103
+ return packages.join(", ");
104
+ }
105
+ return `${packages.slice(0, 3).join(", ")} and ${packages.length - 3} more`;
106
+ }
107
+ function createProgressCallback(options) {
108
+ const logger = createLogger(options);
109
+ return (message) => {
110
+ logger.debug(message);
111
+ };
112
+ }
113
+
114
+ // src/cli/commands/sync.ts
115
+ async function runSync(packages, options) {
116
+ const logger = createLogger(options);
117
+ const rootDir = path.resolve(options.root);
118
+ const outputDir = path.join(rootDir, "docs", "src", "content", "docs", "packages");
119
+ if (options.quiet !== true) {
120
+ showIntro("\u{1F4DA} doc-sync");
121
+ }
122
+ let selectedPackages = packages;
123
+ if (options.interactive === true && packages.length === 0) {
124
+ const scanner = createPackageScanner({
125
+ rootDir,
126
+ parseSourceFiles: false,
127
+ parseReadme: false
128
+ });
129
+ const scanResult = await scanner.scan();
130
+ const availablePackages = scanResult.packages.map((pkg) => ({
131
+ value: pkg.info.name,
132
+ label: pkg.info.name,
133
+ hint: pkg.needsDocumentation ? "needs docs" : "up to date"
134
+ }));
135
+ const selection = await selectPackages(availablePackages);
136
+ if (handleCancel(selection)) {
137
+ showCancel();
138
+ process.exit(0);
139
+ }
140
+ selectedPackages = selection;
141
+ }
142
+ const spinner2 = options.quiet === true ? void 0 : createSpinner();
143
+ const errors = [];
144
+ const orchestrator = createSyncOrchestrator({
145
+ config: {
146
+ rootDir,
147
+ outputDir,
148
+ includePatterns: ["packages/*"],
149
+ excludePatterns: []
150
+ },
151
+ dryRun: options.dryRun ?? false,
152
+ verbose: options.verbose ?? false,
153
+ onProgress: createProgressCallback(options),
154
+ onError(error) {
155
+ errors.push(error);
156
+ logger.error(`Error syncing ${error.packageName ?? "unknown"}: ${error.message}`);
157
+ }
158
+ });
159
+ const actionPrefix = options.dryRun === true ? "[DRY RUN] " : "";
160
+ if (selectedPackages.length > 0) {
161
+ spinner2?.start(`${actionPrefix}Syncing ${formatPackageList(selectedPackages)}...`);
162
+ const result = await orchestrator.syncPackages(selectedPackages);
163
+ spinner2?.stop(`${actionPrefix}Sync complete`);
164
+ reportSyncResult(result, logger, options);
165
+ } else {
166
+ spinner2?.start(`${actionPrefix}Syncing all packages...`);
167
+ const result = await orchestrator.syncAll();
168
+ spinner2?.stop(`${actionPrefix}Sync complete`);
169
+ reportSyncResult(result, logger, options);
170
+ }
171
+ if (errors.length > 0) {
172
+ if (options.quiet !== true) {
173
+ showOutro(`\u26A0\uFE0F Completed with ${errors.length} error${errors.length === 1 ? "" : "s"}`);
174
+ }
175
+ process.exit(1);
176
+ }
177
+ if (options.quiet !== true) {
178
+ showOutro("\u2728 Documentation sync complete!");
179
+ }
180
+ }
181
+ function reportSyncResult(result, logger, options) {
182
+ const { successCount, failureCount, unchangedCount, durationMs, details } = result;
183
+ if (options.verbose === true) {
184
+ for (const detail of details) {
185
+ switch (detail.action) {
186
+ case "created":
187
+ logger.success(`Created: ${detail.packageName}`);
188
+ break;
189
+ case "updated":
190
+ logger.info(`Updated: ${detail.packageName}`);
191
+ break;
192
+ case "unchanged":
193
+ logger.debug(`Unchanged: ${detail.packageName}`);
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ const summary = [
199
+ successCount > 0 ? `${successCount} synced` : null,
200
+ unchangedCount > 0 ? `${unchangedCount} unchanged` : null,
201
+ failureCount > 0 ? `${failureCount} failed` : null
202
+ ].filter(Boolean).join(", ");
203
+ logger.info(`${summary} in ${formatDuration(durationMs)}`);
204
+ }
205
+
206
+ // src/cli/commands/validate.ts
207
+ import fs from "fs/promises";
208
+ import path2 from "path";
209
+ import process2 from "process";
210
+ async function runValidate(packages, options) {
211
+ const logger = createLogger(options);
212
+ const rootDir = path2.resolve(options.root);
213
+ const outputDir = path2.join(rootDir, "docs", "src", "content", "docs", "packages");
214
+ if (options.quiet !== true) {
215
+ showIntro("\u{1F50D} doc-sync validate");
216
+ }
217
+ const startTime = Date.now();
218
+ const spinner2 = options.quiet === true ? void 0 : createSpinner();
219
+ const validationResults = [];
220
+ spinner2?.start("Scanning packages...");
221
+ const scanner = createPackageScanner({
222
+ rootDir,
223
+ parseSourceFiles: true,
224
+ parseReadme: true
225
+ });
226
+ const validationPipeline = createValidationPipeline();
227
+ const scanResult = await scanner.scan();
228
+ spinner2?.stop(`Found ${scanResult.packages.length} packages`);
229
+ const packagesToValidate = packages.length > 0 ? scanResult.packages.filter((pkg) => packages.includes(pkg.info.name)) : scanResult.packages;
230
+ spinner2?.start(`Validating ${packagesToValidate.length} packages...`);
231
+ for (const pkg of packagesToValidate) {
232
+ const issues = [];
233
+ let isValid = true;
234
+ const docPath = buildDocPath(pkg.info.name, outputDir);
235
+ try {
236
+ await fs.access(docPath);
237
+ const existingContent = await fs.readFile(docPath, "utf-8");
238
+ const contentValidation = validationPipeline.validateContent(existingContent);
239
+ if (!contentValidation.valid) {
240
+ isValid = false;
241
+ for (const error of contentValidation.errors) {
242
+ issues.push(`MDX error: ${error.message}`);
243
+ }
244
+ }
245
+ const docResult = generateMDXDocument(pkg.info, pkg.readme, pkg.api);
246
+ if (docResult.success) {
247
+ const generatedContent = docResult.data.rendered;
248
+ const mergedResult = mergeContent(existingContent, generatedContent);
249
+ if (mergedResult.success) {
250
+ if (normalizeContent(existingContent) !== normalizeContent(mergedResult.data.content)) {
251
+ isValid = false;
252
+ issues.push("Documentation is out of date with source");
253
+ }
254
+ } else if (normalizeContent(existingContent) !== normalizeContent(generatedContent)) {
255
+ isValid = false;
256
+ issues.push("Documentation is out of date with source");
257
+ }
258
+ } else {
259
+ issues.push(`Generation failed: ${docResult.error.message}`);
260
+ }
261
+ } catch {
262
+ isValid = false;
263
+ issues.push("Documentation file does not exist");
264
+ }
265
+ validationResults.push({
266
+ packageName: pkg.info.name,
267
+ isValid,
268
+ issues
269
+ });
270
+ if (options.verbose === true) {
271
+ if (isValid) {
272
+ logger.success(`\u2713 ${pkg.info.name}`);
273
+ } else {
274
+ logger.warn(`\u2717 ${pkg.info.name}: ${issues.join(", ")}`);
275
+ }
276
+ }
277
+ }
278
+ spinner2?.stop("Validation complete");
279
+ const validCount = validationResults.filter((r) => r.isValid).length;
280
+ const invalidCount = validationResults.filter((r) => !r.isValid).length;
281
+ const durationMs = Date.now() - startTime;
282
+ if (invalidCount > 0) {
283
+ logger.warn(`Found ${invalidCount} package${invalidCount === 1 ? "" : "s"} with issues:`);
284
+ for (const result of validationResults) {
285
+ if (!result.isValid) {
286
+ logger.error(` ${result.packageName}:`);
287
+ for (const issue of result.issues) {
288
+ logger.error(` - ${issue}`);
289
+ }
290
+ }
291
+ }
292
+ }
293
+ logger.info(`${validCount} valid, ${invalidCount} invalid in ${formatDuration(durationMs)}`);
294
+ if (options.quiet !== true) {
295
+ if (invalidCount > 0) {
296
+ showOutro(
297
+ `\u26A0\uFE0F ${invalidCount} package${invalidCount === 1 ? "" : "s"} need${invalidCount === 1 ? "s" : ""} attention`
298
+ );
299
+ } else {
300
+ showOutro("\u2728 All documentation is up to date!");
301
+ }
302
+ }
303
+ if (invalidCount > 0) {
304
+ process2.exit(1);
305
+ }
306
+ }
307
+ function buildDocPath(packageName, outputDir) {
308
+ const slug = getUnscopedName(packageName).toLowerCase().replaceAll(/[^a-z0-9-]/g, "-");
309
+ return path2.join(outputDir, `${slug}.mdx`);
310
+ }
311
+ function normalizeContent(content) {
312
+ return content.replaceAll("\r\n", "\n").replaceAll(/\s+$/gm, "").trim();
313
+ }
314
+
315
+ // src/cli/commands/watch.ts
316
+ import path3 from "path";
317
+ import process3 from "process";
318
+ async function runWatch(packages, options) {
319
+ const logger = createLogger(options);
320
+ const rootDir = path3.resolve(options.root);
321
+ const outputDir = path3.join(rootDir, "docs", "src", "content", "docs", "packages");
322
+ if (options.quiet !== true) {
323
+ showIntro("\u{1F441}\uFE0F doc-sync watch");
324
+ }
325
+ const spinner2 = options.quiet === true ? void 0 : createSpinner();
326
+ const orchestrator = createSyncOrchestrator({
327
+ config: {
328
+ rootDir,
329
+ outputDir,
330
+ includePatterns: ["packages/*"],
331
+ excludePatterns: [],
332
+ watch: true,
333
+ debounceMs: 300
334
+ },
335
+ dryRun: options.dryRun ?? false,
336
+ verbose: options.verbose ?? false,
337
+ onProgress: createProgressCallback(options),
338
+ onError(error) {
339
+ logger.error(`Error: ${error.message}`);
340
+ }
341
+ });
342
+ if (packages.length > 0) {
343
+ logger.info(`Watching packages: ${packages.join(", ")}`);
344
+ spinner2?.start("Performing initial sync...");
345
+ await orchestrator.syncPackages(packages);
346
+ spinner2?.stop("Initial sync complete");
347
+ } else {
348
+ spinner2?.start("Performing initial sync of all packages...");
349
+ await orchestrator.syncAll();
350
+ spinner2?.stop("Initial sync complete");
351
+ }
352
+ logger.info("Starting watch mode...");
353
+ spinner2?.start("Watching for changes... (Ctrl+C to stop)");
354
+ await orchestrator.startWatching();
355
+ const shutdown = async () => {
356
+ spinner2?.stop("Stopping watch mode...");
357
+ await orchestrator.stopWatching();
358
+ if (options.quiet !== true) {
359
+ showOutro("\u{1F44B} Watch mode stopped");
360
+ }
361
+ process3.exit(0);
362
+ };
363
+ process3.on("SIGINT", () => {
364
+ shutdown().catch(() => process3.exit(1));
365
+ });
366
+ process3.on("SIGTERM", () => {
367
+ shutdown().catch(() => process3.exit(1));
368
+ });
369
+ setInterval(() => {
370
+ }, 1 << 30);
371
+ }
372
+
373
+ // src/cli/index.ts
374
+ var cli = cac("doc-sync");
375
+ cli.command(
376
+ "sync [...packages]",
377
+ "Sync documentation for specified packages (or all if none specified)"
378
+ ).option("-r, --root <dir>", "Root directory of the monorepo", { default: process4.cwd() }).option("-d, --dry-run", "Preview changes without writing files").option("-v, --verbose", "Enable verbose output").option("-q, --quiet", "Suppress non-error output").option("-i, --interactive", "Use interactive package selection").action(async (packages, options) => {
379
+ await runSync(packages, options);
380
+ });
381
+ cli.command("watch [...packages]", "Watch for changes and sync automatically").option("-r, --root <dir>", "Root directory of the monorepo", { default: process4.cwd() }).option("-d, --dry-run", "Preview changes without writing files").option("-v, --verbose", "Enable verbose output").option("-q, --quiet", "Suppress non-error output").action(async (packages, options) => {
382
+ await runWatch(packages, options);
383
+ });
384
+ cli.command("validate [...packages]", "Check documentation freshness and validate MDX syntax").option("-r, --root <dir>", "Root directory of the monorepo", { default: process4.cwd() }).option("-v, --verbose", "Enable verbose output").option("-q, --quiet", "Suppress non-error output").action(async (packages, options) => {
385
+ await runValidate(packages, options);
386
+ });
387
+ cli.command("[...packages]", "Sync documentation (default command)").option("-r, --root <dir>", "Root directory of the monorepo", { default: process4.cwd() }).option("-w, --watch", "Watch for changes and sync automatically").option("-d, --dry-run", "Preview changes without writing files").option("-v, --verbose", "Enable verbose output").option("-q, --quiet", "Suppress non-error output").option("-i, --interactive", "Use interactive package selection").action(async (packages, options) => {
388
+ if (options.watch === true) {
389
+ await runWatch(packages, options);
390
+ } else {
391
+ await runSync(packages, options);
392
+ }
393
+ });
394
+ cli.help();
395
+ cli.version("0.0.1");
396
+ cli.parse();
397
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,170 @@
1
+ import { PackageAPI, ExportedFunction, ExportedType, ReadmeContent, PackageInfo, ReadmeSection, SyncError, MDXFrontmatter, MDXDocument } from '../types.js';
2
+ import { Result } from '@bfra.me/es/result';
3
+ import 'zod';
4
+
5
+ /**
6
+ * @bfra.me/doc-sync/generators/api-reference-generator - API documentation table generation
7
+ */
8
+
9
+ declare function generateAPIReference(api: PackageAPI): string;
10
+ declare function generateAPICompact(api: PackageAPI): string;
11
+ declare function generateCategoryReference(functions: readonly ExportedFunction[], types: readonly ExportedType[], categoryName: string): string;
12
+
13
+ /**
14
+ * @bfra.me/doc-sync/generators/code-example-formatter - Syntax-highlighted code blocks from JSDoc examples
15
+ */
16
+
17
+ /**
18
+ * Options for code example formatting
19
+ */
20
+ interface CodeExampleOptions {
21
+ /** Default language for code blocks */
22
+ readonly defaultLanguage?: string;
23
+ /** Whether to add function name as title */
24
+ readonly includeTitle?: boolean;
25
+ /** Maximum number of examples per function */
26
+ readonly maxExamplesPerFunction?: number;
27
+ /** Whether to wrap examples in collapsible sections */
28
+ readonly collapsible?: boolean;
29
+ }
30
+ declare function formatCodeExamples(api: PackageAPI, options?: CodeExampleOptions): string;
31
+ declare function formatFunctionExamples(fn: ExportedFunction, options?: CodeExampleOptions): string;
32
+ declare function formatTypeExamples(type: ExportedType, options?: CodeExampleOptions): string;
33
+ declare function formatCodeBlock(code: string, language?: string): string;
34
+ declare function cleanCodeExample(code: string): string;
35
+ declare function detectLanguage(code: string): string | undefined;
36
+ declare function formatUsageExample(fn: ExportedFunction): string;
37
+ declare function groupExamplesByCategory(api: PackageAPI): Map<string, {
38
+ functions: ExportedFunction[];
39
+ types: ExportedType[];
40
+ }>;
41
+ declare function formatGroupedExamples(api: PackageAPI, options?: CodeExampleOptions): string;
42
+
43
+ /**
44
+ * @bfra.me/doc-sync/generators/component-mapper - Map content sections to Starlight components
45
+ */
46
+
47
+ /**
48
+ * Configuration for Starlight component mapping
49
+ */
50
+ interface ComponentMapperConfig {
51
+ /** Section titles that should use CardGrid for features */
52
+ readonly featureSections?: readonly string[];
53
+ /** Section titles that should use Tabs for installation */
54
+ readonly tabSections?: readonly string[];
55
+ /** Section titles to exclude from output */
56
+ readonly excludeSections?: readonly string[];
57
+ /** Custom component mappings by section title */
58
+ readonly customMappings?: Record<string, SectionMapper>;
59
+ }
60
+ /**
61
+ * A function that maps section content to MDX output
62
+ */
63
+ type SectionMapper = (section: ReadmeSection, info: PackageInfo) => string;
64
+ declare function mapToStarlightComponents(readme: ReadmeContent, packageInfo: PackageInfo, config?: ComponentMapperConfig): string;
65
+ declare function generateInstallTabs(packageName: string): string;
66
+ declare function createBadge(text: string, variant?: 'note' | 'tip' | 'caution' | 'danger' | 'success' | 'default'): string;
67
+ declare function createCard(title: string, content: string, icon?: string): string;
68
+ declare function createCardGrid(cards: {
69
+ title: string;
70
+ content: string;
71
+ icon?: string;
72
+ }[]): string;
73
+ declare function createTabs(items: {
74
+ label: string;
75
+ content: string;
76
+ }[]): string;
77
+
78
+ /**
79
+ * @bfra.me/doc-sync/generators/content-merger - Preserve manual sections using sentinel markers
80
+ */
81
+
82
+ /**
83
+ * A section of content extracted from MDX
84
+ */
85
+ interface ContentSection {
86
+ /** Section type */
87
+ readonly type: 'auto' | 'manual' | 'unchanged';
88
+ /** Section content */
89
+ readonly content: string;
90
+ /** Start position in source */
91
+ readonly startIndex: number;
92
+ /** End position in source */
93
+ readonly endIndex: number;
94
+ }
95
+ /**
96
+ * Merge options
97
+ */
98
+ interface MergeOptions {
99
+ /** Strategy for handling conflicts */
100
+ readonly conflictStrategy?: 'keep-manual' | 'keep-auto' | 'merge';
101
+ /** Preserve empty manual sections */
102
+ readonly preserveEmptyManual?: boolean;
103
+ }
104
+ /**
105
+ * Result of a merge operation
106
+ */
107
+ interface MergeResult {
108
+ /** Merged content */
109
+ readonly content: string;
110
+ /** Number of manual sections preserved */
111
+ readonly preservedCount: number;
112
+ /** Number of auto-generated sections updated */
113
+ readonly updatedCount: number;
114
+ /** Whether content changed */
115
+ readonly hasChanges: boolean;
116
+ }
117
+ declare function mergeContent(existingContent: string, newContent: string, options?: MergeOptions): Result<MergeResult, SyncError>;
118
+ declare function extractManualSections(content: string): ContentSection[];
119
+ declare function extractAutoSections(content: string): ContentSection[];
120
+ declare function hasManualContent(content: string): boolean;
121
+ declare function hasAutoContent(content: string): boolean;
122
+ declare function wrapManualSection(content: string): string;
123
+ declare function wrapAutoSection(content: string): string;
124
+ declare function stripSentinelMarkers(content: string): string;
125
+ declare function validateMarkerPairing(content: string): Result<true, SyncError>;
126
+ declare function createDiffSummary(oldContent: string, newContent: string): string;
127
+
128
+ /**
129
+ * @bfra.me/doc-sync/generators/frontmatter-generator - Starlight-compatible frontmatter generation
130
+ */
131
+
132
+ declare function generateFrontmatter(packageInfo: PackageInfo, readme: ReadmeContent | undefined, overrides?: Partial<MDXFrontmatter>): MDXFrontmatter;
133
+ declare function stringifyFrontmatter(frontmatter: MDXFrontmatter): string;
134
+ declare function parseFrontmatter(yaml: string): Partial<MDXFrontmatter>;
135
+
136
+ /**
137
+ * @bfra.me/doc-sync/generators/mdx-generator - MDX document structure generation for Starlight
138
+ */
139
+
140
+ /**
141
+ * Options for MDX generation
142
+ */
143
+ interface MDXGeneratorOptions {
144
+ /** Include API reference section */
145
+ readonly includeAPI?: boolean;
146
+ /** Include examples from JSDoc */
147
+ readonly includeExamples?: boolean;
148
+ /** Custom frontmatter overrides */
149
+ readonly frontmatterOverrides?: Partial<MDXFrontmatter>;
150
+ /** Preserve manual content sections */
151
+ readonly preserveManualContent?: boolean;
152
+ /** Existing MDX content for merging */
153
+ readonly existingContent?: string;
154
+ }
155
+ declare function generateMDXDocument(packageInfo: PackageInfo, readme: ReadmeContent | undefined, api: PackageAPI | undefined, options?: MDXGeneratorOptions): Result<MDXDocument, SyncError>;
156
+ /**
157
+ * Sanitizes content for safe MDX rendering
158
+ * Prevents XSS by escaping potentially dangerous content
159
+ */
160
+ declare function sanitizeContent(content: string): string;
161
+ /**
162
+ * Sanitizes content within MDX while preserving JSX components
163
+ * Only escapes content that appears to be user-provided text
164
+ * Now includes sanitization of JSX component attributes to prevent XSS
165
+ * Uses safe, non-backtracking parsing to prevent ReDoS
166
+ */
167
+ declare function sanitizeTextContent(content: string): string;
168
+ declare function validateMDXSyntax(mdx: string): Result<true, SyncError>;
169
+
170
+ export { type CodeExampleOptions, type ComponentMapperConfig, type ContentSection, type MDXGeneratorOptions, type MergeOptions, type MergeResult, type SectionMapper, cleanCodeExample, createBadge, createCard, createCardGrid, createDiffSummary, createTabs, detectLanguage, extractAutoSections, extractManualSections, formatCodeBlock, formatCodeExamples, formatFunctionExamples, formatGroupedExamples, formatTypeExamples, formatUsageExample, generateAPICompact, generateAPIReference, generateCategoryReference, generateFrontmatter, generateInstallTabs, generateMDXDocument, groupExamplesByCategory, hasAutoContent, hasManualContent, mapToStarlightComponents, mergeContent, parseFrontmatter, sanitizeContent, sanitizeTextContent, stringifyFrontmatter, stripSentinelMarkers, validateMDXSyntax, validateMarkerPairing, wrapAutoSection, wrapManualSection };
@@ -0,0 +1,76 @@
1
+ import {
2
+ cleanCodeExample,
3
+ createBadge,
4
+ createCard,
5
+ createCardGrid,
6
+ createDiffSummary,
7
+ createTabs,
8
+ detectLanguage,
9
+ extractAutoSections,
10
+ extractManualSections,
11
+ formatCodeBlock,
12
+ formatCodeExamples,
13
+ formatFunctionExamples,
14
+ formatGroupedExamples,
15
+ formatTypeExamples,
16
+ formatUsageExample,
17
+ generateAPICompact,
18
+ generateAPIReference,
19
+ generateCategoryReference,
20
+ generateFrontmatter,
21
+ generateInstallTabs,
22
+ generateMDXDocument,
23
+ groupExamplesByCategory,
24
+ hasAutoContent,
25
+ hasManualContent,
26
+ mapToStarlightComponents,
27
+ mergeContent,
28
+ parseFrontmatter,
29
+ sanitizeContent,
30
+ sanitizeTextContent,
31
+ stringifyFrontmatter,
32
+ stripSentinelMarkers,
33
+ validateMDXSyntax,
34
+ validateMarkerPairing,
35
+ wrapAutoSection,
36
+ wrapManualSection
37
+ } from "../chunk-G5KKGJYO.js";
38
+ import "../chunk-ROLA7SBB.js";
39
+ export {
40
+ cleanCodeExample,
41
+ createBadge,
42
+ createCard,
43
+ createCardGrid,
44
+ createDiffSummary,
45
+ createTabs,
46
+ detectLanguage,
47
+ extractAutoSections,
48
+ extractManualSections,
49
+ formatCodeBlock,
50
+ formatCodeExamples,
51
+ formatFunctionExamples,
52
+ formatGroupedExamples,
53
+ formatTypeExamples,
54
+ formatUsageExample,
55
+ generateAPICompact,
56
+ generateAPIReference,
57
+ generateCategoryReference,
58
+ generateFrontmatter,
59
+ generateInstallTabs,
60
+ generateMDXDocument,
61
+ groupExamplesByCategory,
62
+ hasAutoContent,
63
+ hasManualContent,
64
+ mapToStarlightComponents,
65
+ mergeContent,
66
+ parseFrontmatter,
67
+ sanitizeContent,
68
+ sanitizeTextContent,
69
+ stringifyFrontmatter,
70
+ stripSentinelMarkers,
71
+ validateMDXSyntax,
72
+ validateMarkerPairing,
73
+ wrapAutoSection,
74
+ wrapManualSection
75
+ };
76
+ //# sourceMappingURL=index.js.map