@bfra.me/doc-sync 0.1.0 → 0.1.2
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-G5KKGJYO.js → chunk-CUBMCGAY.js} +22 -261
- package/lib/chunk-DRBRT57F.js +1 -0
- package/lib/{chunk-DR6UG237.js → chunk-DTXB5PMR.js} +21 -335
- package/lib/chunk-GZ2MP3VN.js +261 -0
- package/lib/chunk-SQSYXPIF.js +1 -0
- package/lib/{chunk-6NKAJT2M.js → chunk-VHUUC45J.js} +35 -3
- package/lib/cli/index.js +5 -3
- 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/parsers/index.js +1 -1
- 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/component-mapper.ts +11 -1
- package/src/generators/mdx-generator.ts +18 -17
- package/src/index.ts +82 -0
- package/src/parsers/readme-parser.ts +55 -2
- package/src/utils/safe-patterns.ts +6 -2
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// src/watcher/change-detector.ts
|
|
2
|
+
import { createChangeDetector as createBaseDetector } from "@bfra.me/es/watcher";
|
|
3
|
+
|
|
4
|
+
// src/watcher/file-watcher.ts
|
|
5
|
+
import path from "path";
|
|
6
|
+
import process from "process";
|
|
7
|
+
import { createFileWatcher as createBaseWatcher } from "@bfra.me/es/watcher";
|
|
8
|
+
var DEFAULT_WATCH_PATTERNS = [
|
|
9
|
+
"packages/*/README.md",
|
|
10
|
+
"packages/*/readme.md",
|
|
11
|
+
"packages/*/package.json",
|
|
12
|
+
"packages/*/src/**/*.ts",
|
|
13
|
+
"packages/*/src/**/*.tsx"
|
|
14
|
+
];
|
|
15
|
+
var DEFAULT_IGNORE_PATTERNS = [
|
|
16
|
+
"**/node_modules/**",
|
|
17
|
+
"**/lib/**",
|
|
18
|
+
"**/dist/**",
|
|
19
|
+
"**/.git/**",
|
|
20
|
+
"**/coverage/**",
|
|
21
|
+
"**/*.test.ts",
|
|
22
|
+
"**/*.spec.ts",
|
|
23
|
+
"**/__tests__/**",
|
|
24
|
+
"**/__mocks__/**"
|
|
25
|
+
];
|
|
26
|
+
function extractPackageName(filePath, root) {
|
|
27
|
+
const relativePath = path.relative(root, filePath);
|
|
28
|
+
const parts = relativePath.split(path.sep);
|
|
29
|
+
if (parts[0] === "packages" && parts.length > 1) {
|
|
30
|
+
return parts[1];
|
|
31
|
+
}
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
function createDocWatcher(options = {}) {
|
|
35
|
+
const {
|
|
36
|
+
rootDir = process.cwd(),
|
|
37
|
+
debounceMs = 300,
|
|
38
|
+
additionalIgnore = [],
|
|
39
|
+
usePolling = false
|
|
40
|
+
} = options;
|
|
41
|
+
const watchPaths = DEFAULT_WATCH_PATTERNS.map((pattern) => path.join(rootDir, pattern));
|
|
42
|
+
const ignoredPatterns = [...DEFAULT_IGNORE_PATTERNS, ...additionalIgnore];
|
|
43
|
+
const watcherOptions = {
|
|
44
|
+
debounceMs,
|
|
45
|
+
ignored: ignoredPatterns,
|
|
46
|
+
usePolling
|
|
47
|
+
};
|
|
48
|
+
const baseWatcher = createBaseWatcher(watchPaths, watcherOptions);
|
|
49
|
+
const handlers = /* @__PURE__ */ new Set();
|
|
50
|
+
function transformToDocEvents(changes) {
|
|
51
|
+
return changes.map((change) => ({
|
|
52
|
+
type: change.type,
|
|
53
|
+
path: change.path,
|
|
54
|
+
packageName: extractPackageName(change.path, rootDir),
|
|
55
|
+
timestamp: new Date(change.timestamp)
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
async start() {
|
|
60
|
+
baseWatcher.on("change", (event) => {
|
|
61
|
+
const docEvents = transformToDocEvents(event.changes);
|
|
62
|
+
for (const handler of handlers) {
|
|
63
|
+
Promise.resolve(handler(docEvents)).catch((error) => {
|
|
64
|
+
console.error("[doc-sync] Error in change handler:", error);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
await baseWatcher.start();
|
|
69
|
+
},
|
|
70
|
+
async close() {
|
|
71
|
+
await baseWatcher.close();
|
|
72
|
+
handlers.clear();
|
|
73
|
+
},
|
|
74
|
+
onChanges(handler) {
|
|
75
|
+
handlers.add(handler);
|
|
76
|
+
return () => {
|
|
77
|
+
handlers.delete(handler);
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
getWatchedPaths() {
|
|
81
|
+
return watchPaths;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function categorizeFile(filePath) {
|
|
86
|
+
const basename = path.basename(filePath).toLowerCase();
|
|
87
|
+
if (basename === "readme.md" || basename === "readme") {
|
|
88
|
+
return "readme";
|
|
89
|
+
}
|
|
90
|
+
if (basename === "package.json") {
|
|
91
|
+
return "package-json";
|
|
92
|
+
}
|
|
93
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
94
|
+
if (ext === ".ts" || ext === ".tsx") {
|
|
95
|
+
return "source";
|
|
96
|
+
}
|
|
97
|
+
return "unknown";
|
|
98
|
+
}
|
|
99
|
+
function groupChangesByPackage(events) {
|
|
100
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
101
|
+
for (const event of events) {
|
|
102
|
+
const pkg = event.packageName ?? "__unknown__";
|
|
103
|
+
const existing = grouped.get(pkg);
|
|
104
|
+
if (existing === void 0) {
|
|
105
|
+
grouped.set(pkg, [event]);
|
|
106
|
+
} else {
|
|
107
|
+
existing.push(event);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return grouped;
|
|
111
|
+
}
|
|
112
|
+
function filterDocumentationChanges(events) {
|
|
113
|
+
return events.filter((event) => {
|
|
114
|
+
const category = categorizeFile(event.path);
|
|
115
|
+
return category !== "unknown";
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/watcher/change-detector.ts
|
|
120
|
+
function createDocChangeDetector(options = {}) {
|
|
121
|
+
const baseDetector = createBaseDetector({ algorithm: options.algorithm });
|
|
122
|
+
const packageFiles = /* @__PURE__ */ new Map();
|
|
123
|
+
return {
|
|
124
|
+
async hasChanged(filePath) {
|
|
125
|
+
return baseDetector.hasChanged(filePath);
|
|
126
|
+
},
|
|
127
|
+
async record(filePath) {
|
|
128
|
+
await baseDetector.record(filePath);
|
|
129
|
+
},
|
|
130
|
+
async recordPackage(pkg, files) {
|
|
131
|
+
const fileSet = new Set(files);
|
|
132
|
+
packageFiles.set(pkg.name, fileSet);
|
|
133
|
+
await Promise.all(files.map(async (file) => baseDetector.record(file)));
|
|
134
|
+
},
|
|
135
|
+
clear(filePath) {
|
|
136
|
+
baseDetector.clear(filePath);
|
|
137
|
+
for (const fileSet of packageFiles.values()) {
|
|
138
|
+
fileSet.delete(filePath);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
clearAll() {
|
|
142
|
+
baseDetector.clearAll();
|
|
143
|
+
packageFiles.clear();
|
|
144
|
+
},
|
|
145
|
+
async analyzeChanges(events) {
|
|
146
|
+
const packageChanges = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const event of events) {
|
|
148
|
+
const packageName = event.packageName ?? "__unknown__";
|
|
149
|
+
const category = categorizeFile(event.path);
|
|
150
|
+
let analysis = packageChanges.get(packageName);
|
|
151
|
+
if (analysis === void 0) {
|
|
152
|
+
analysis = { categories: /* @__PURE__ */ new Set(), files: [] };
|
|
153
|
+
packageChanges.set(packageName, analysis);
|
|
154
|
+
}
|
|
155
|
+
if (category !== "unknown") {
|
|
156
|
+
analysis.categories.add(category);
|
|
157
|
+
}
|
|
158
|
+
analysis.files.push(event.path);
|
|
159
|
+
}
|
|
160
|
+
const results = [];
|
|
161
|
+
for (const [packageName, analysis] of packageChanges) {
|
|
162
|
+
const changedCategories = [...analysis.categories];
|
|
163
|
+
const needsRegeneration = changedCategories.includes("readme") || changedCategories.includes("package-json") || changedCategories.includes("source");
|
|
164
|
+
results.push({
|
|
165
|
+
packageName,
|
|
166
|
+
needsRegeneration,
|
|
167
|
+
changedCategories,
|
|
168
|
+
changedFiles: analysis.files
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function determineRegenerationScope(changedCategories) {
|
|
176
|
+
const hasReadme = changedCategories.includes("readme");
|
|
177
|
+
const hasSource = changedCategories.includes("source");
|
|
178
|
+
const hasPackageJson = changedCategories.includes("package-json");
|
|
179
|
+
if (hasReadme && hasSource) {
|
|
180
|
+
return "full";
|
|
181
|
+
}
|
|
182
|
+
if (hasSource) {
|
|
183
|
+
return "api-only";
|
|
184
|
+
}
|
|
185
|
+
if (hasReadme) {
|
|
186
|
+
return "readme-only";
|
|
187
|
+
}
|
|
188
|
+
if (hasPackageJson) {
|
|
189
|
+
return "metadata-only";
|
|
190
|
+
}
|
|
191
|
+
return "none";
|
|
192
|
+
}
|
|
193
|
+
async function hasAnyFileChanged(detector, files) {
|
|
194
|
+
const results = await Promise.all(files.map(async (file) => detector.hasChanged(file)));
|
|
195
|
+
return results.some((changed) => changed);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/watcher/debouncer.ts
|
|
199
|
+
import { createDebouncer as createBaseDebouncer } from "@bfra.me/es/watcher";
|
|
200
|
+
function createDocDebouncer(handler, options = {}) {
|
|
201
|
+
const { debounceMs = 300, maxWaitMs = 5e3 } = options;
|
|
202
|
+
let pendingEvents = [];
|
|
203
|
+
let maxWaitTimeout;
|
|
204
|
+
function processEvents(events) {
|
|
205
|
+
if (maxWaitTimeout !== void 0) {
|
|
206
|
+
clearTimeout(maxWaitTimeout);
|
|
207
|
+
maxWaitTimeout = void 0;
|
|
208
|
+
}
|
|
209
|
+
const deduplicated = deduplicateEvents(events);
|
|
210
|
+
if (deduplicated.length > 0) {
|
|
211
|
+
Promise.resolve(handler(deduplicated)).catch((error) => {
|
|
212
|
+
console.error("[doc-sync] Error in batch handler:", error);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const baseDebouncer = createBaseDebouncer((events) => {
|
|
217
|
+
processEvents(events);
|
|
218
|
+
pendingEvents = [];
|
|
219
|
+
}, debounceMs);
|
|
220
|
+
function startMaxWaitTimer() {
|
|
221
|
+
if (maxWaitTimeout === void 0) {
|
|
222
|
+
maxWaitTimeout = setTimeout(() => {
|
|
223
|
+
baseDebouncer.flush();
|
|
224
|
+
}, maxWaitMs);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
add(event) {
|
|
229
|
+
pendingEvents.push(event);
|
|
230
|
+
startMaxWaitTimer();
|
|
231
|
+
baseDebouncer.add(event);
|
|
232
|
+
},
|
|
233
|
+
addAll(events) {
|
|
234
|
+
for (const event of events) {
|
|
235
|
+
pendingEvents.push(event);
|
|
236
|
+
baseDebouncer.add(event);
|
|
237
|
+
}
|
|
238
|
+
if (events.length > 0) {
|
|
239
|
+
startMaxWaitTimer();
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
flush() {
|
|
243
|
+
if (maxWaitTimeout !== void 0) {
|
|
244
|
+
clearTimeout(maxWaitTimeout);
|
|
245
|
+
maxWaitTimeout = void 0;
|
|
246
|
+
}
|
|
247
|
+
baseDebouncer.flush();
|
|
248
|
+
},
|
|
249
|
+
cancel() {
|
|
250
|
+
if (maxWaitTimeout !== void 0) {
|
|
251
|
+
clearTimeout(maxWaitTimeout);
|
|
252
|
+
maxWaitTimeout = void 0;
|
|
253
|
+
}
|
|
254
|
+
pendingEvents = [];
|
|
255
|
+
baseDebouncer.cancel();
|
|
256
|
+
},
|
|
257
|
+
getPendingCount() {
|
|
258
|
+
return pendingEvents.length;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function deduplicateEvents(events) {
|
|
263
|
+
const latestByPath = /* @__PURE__ */ new Map();
|
|
264
|
+
for (const event of events) {
|
|
265
|
+
const existing = latestByPath.get(event.path);
|
|
266
|
+
if (existing === void 0 || event.timestamp > existing.timestamp) {
|
|
267
|
+
latestByPath.set(event.path, event);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return [...latestByPath.values()];
|
|
271
|
+
}
|
|
272
|
+
function consolidateEvents(events) {
|
|
273
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
274
|
+
for (const event of events) {
|
|
275
|
+
const existing = byPath.get(event.path);
|
|
276
|
+
if (existing === void 0) {
|
|
277
|
+
byPath.set(event.path, [event]);
|
|
278
|
+
} else {
|
|
279
|
+
existing.push(event);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const consolidated = [];
|
|
283
|
+
for (const [, pathEvents] of byPath) {
|
|
284
|
+
const firstEvent = pathEvents[0];
|
|
285
|
+
if (firstEvent === void 0) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (pathEvents.length === 1) {
|
|
289
|
+
consolidated.push(firstEvent);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
pathEvents.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
293
|
+
const first = pathEvents[0];
|
|
294
|
+
const last = pathEvents.at(-1);
|
|
295
|
+
if (first === void 0 || last === void 0) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (first.type === "add" && last.type === "unlink") {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (first.type === "unlink" && last.type === "add") {
|
|
302
|
+
consolidated.push({
|
|
303
|
+
type: "change",
|
|
304
|
+
path: last.path,
|
|
305
|
+
packageName: last.packageName,
|
|
306
|
+
timestamp: last.timestamp
|
|
307
|
+
});
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
consolidated.push(last);
|
|
311
|
+
}
|
|
312
|
+
return consolidated;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export {
|
|
316
|
+
createDocWatcher,
|
|
317
|
+
categorizeFile,
|
|
318
|
+
groupChangesByPackage,
|
|
319
|
+
filterDocumentationChanges,
|
|
320
|
+
createDocChangeDetector,
|
|
321
|
+
determineRegenerationScope,
|
|
322
|
+
hasAnyFileChanged,
|
|
323
|
+
createDocDebouncer,
|
|
324
|
+
deduplicateEvents,
|
|
325
|
+
consolidateEvents
|
|
326
|
+
};
|
|
327
|
+
//# sourceMappingURL=chunk-45NROJIG.js.map
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SENTINEL_MARKERS
|
|
3
3
|
} from "./chunk-ROLA7SBB.js";
|
|
4
|
+
import {
|
|
5
|
+
extractCodeBlocks,
|
|
6
|
+
parseJSXTags,
|
|
7
|
+
sanitizeAttribute,
|
|
8
|
+
sanitizeForMDX,
|
|
9
|
+
sanitizeJSXTag
|
|
10
|
+
} from "./chunk-GZ2MP3VN.js";
|
|
4
11
|
|
|
5
12
|
// src/generators/api-reference-generator.ts
|
|
6
13
|
function generateAPIReference(api) {
|
|
@@ -435,80 +442,6 @@ function formatGroupedExamples(api, options = {}) {
|
|
|
435
442
|
return sections.join("\n").trim();
|
|
436
443
|
}
|
|
437
444
|
|
|
438
|
-
// src/utils/sanitization.ts
|
|
439
|
-
import { sanitizeInput } from "@bfra.me/es/validation";
|
|
440
|
-
import escapeHtml from "escape-html";
|
|
441
|
-
function sanitizeForMDX(content) {
|
|
442
|
-
const escaped = sanitizeInput(content, { trim: false });
|
|
443
|
-
return escaped.replaceAll("{", "{").replaceAll("}", "}");
|
|
444
|
-
}
|
|
445
|
-
function sanitizeAttribute(value) {
|
|
446
|
-
return escapeHtml(value);
|
|
447
|
-
}
|
|
448
|
-
function parseJSXAttributes(tag) {
|
|
449
|
-
const attrs = [];
|
|
450
|
-
const spaceIndex = tag.indexOf(" ");
|
|
451
|
-
if (spaceIndex === -1) return attrs;
|
|
452
|
-
const closeIndex = tag.lastIndexOf(">");
|
|
453
|
-
if (closeIndex === -1) return attrs;
|
|
454
|
-
const attrRegion = tag.slice(spaceIndex + 1, closeIndex).trim();
|
|
455
|
-
let i = 0;
|
|
456
|
-
while (i < attrRegion.length) {
|
|
457
|
-
while (i < attrRegion.length && /\s/.test(attrRegion.charAt(i))) i++;
|
|
458
|
-
if (i >= attrRegion.length) break;
|
|
459
|
-
let name = "";
|
|
460
|
-
while (i < attrRegion.length && /[\w-]/.test(attrRegion.charAt(i))) {
|
|
461
|
-
name += attrRegion[i];
|
|
462
|
-
i++;
|
|
463
|
-
}
|
|
464
|
-
if (!name) break;
|
|
465
|
-
while (i < attrRegion.length && /\s/.test(attrRegion.charAt(i))) i++;
|
|
466
|
-
if (i >= attrRegion.length || attrRegion[i] !== "=") {
|
|
467
|
-
attrs.push({ name, value: null });
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
i++;
|
|
471
|
-
while (i < attrRegion.length && /\s/.test(attrRegion.charAt(i))) i++;
|
|
472
|
-
if (i >= attrRegion.length) {
|
|
473
|
-
attrs.push({ name, value: "" });
|
|
474
|
-
break;
|
|
475
|
-
}
|
|
476
|
-
let value = "";
|
|
477
|
-
const quote = attrRegion[i];
|
|
478
|
-
if (quote === '"' || quote === "'") {
|
|
479
|
-
i++;
|
|
480
|
-
while (i < attrRegion.length && attrRegion[i] !== quote) {
|
|
481
|
-
value += attrRegion[i];
|
|
482
|
-
i++;
|
|
483
|
-
}
|
|
484
|
-
if (i < attrRegion.length) i++;
|
|
485
|
-
} else {
|
|
486
|
-
while (i < attrRegion.length && !/[\s/>]/.test(attrRegion.charAt(i))) {
|
|
487
|
-
value += attrRegion[i];
|
|
488
|
-
i++;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
attrs.push({ name, value });
|
|
492
|
-
}
|
|
493
|
-
return attrs;
|
|
494
|
-
}
|
|
495
|
-
function sanitizeJSXTag(tag) {
|
|
496
|
-
const tagMatch = tag.match(/^<([A-Z][a-zA-Z0-9]*)/);
|
|
497
|
-
if (!tagMatch || typeof tagMatch[1] !== "string" || tagMatch[1].length === 0) {
|
|
498
|
-
return escapeHtml(tag);
|
|
499
|
-
}
|
|
500
|
-
const tagName = tagMatch[1];
|
|
501
|
-
const selfClosing = tag.endsWith("/>");
|
|
502
|
-
const attributes = parseJSXAttributes(tag);
|
|
503
|
-
const sanitizedAttrs = attributes.map(({ name, value }) => {
|
|
504
|
-
if (value === null) return name;
|
|
505
|
-
const escaped = escapeHtml(value);
|
|
506
|
-
return `${name}="${escaped}"`;
|
|
507
|
-
});
|
|
508
|
-
const attrString = sanitizedAttrs.length > 0 ? ` ${sanitizedAttrs.join(" ")}` : "";
|
|
509
|
-
return `<${tagName}${attrString}${selfClosing ? " />" : ">"}`;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
445
|
// src/generators/component-mapper.ts
|
|
513
446
|
var DEFAULT_CONFIG = {
|
|
514
447
|
featureSections: ["features", "highlights", "key features"],
|
|
@@ -564,6 +497,9 @@ function isFeatureSection(heading, featureSections) {
|
|
|
564
497
|
function isInstallationSection(heading, tabSections) {
|
|
565
498
|
return tabSections.some((tab) => heading.includes(tab.toLowerCase()));
|
|
566
499
|
}
|
|
500
|
+
function escapeAngleBrackets(text) {
|
|
501
|
+
return text.replaceAll("<", "<").replaceAll(">", ">");
|
|
502
|
+
}
|
|
567
503
|
function mapFeatureSection(section) {
|
|
568
504
|
const lines = [];
|
|
569
505
|
lines.push(`## ${section.heading}`);
|
|
@@ -574,7 +510,7 @@ function mapFeatureSection(section) {
|
|
|
574
510
|
for (const feature of features) {
|
|
575
511
|
const icon = inferFeatureIcon(feature.title, feature.emoji);
|
|
576
512
|
lines.push(` <Card title="${sanitizeAttribute(feature.title)}" icon="${icon}">`);
|
|
577
|
-
lines.push(` ${feature.description}`);
|
|
513
|
+
lines.push(` ${escapeAngleBrackets(feature.description)}`);
|
|
578
514
|
lines.push(" </Card>");
|
|
579
515
|
}
|
|
580
516
|
lines.push("</CardGrid>");
|
|
@@ -1167,179 +1103,6 @@ function stripQuotes(value) {
|
|
|
1167
1103
|
|
|
1168
1104
|
// src/generators/mdx-generator.ts
|
|
1169
1105
|
import { err as err2, ok as ok2 } from "@bfra.me/es/result";
|
|
1170
|
-
|
|
1171
|
-
// src/utils/safe-patterns.ts
|
|
1172
|
-
import remarkParse from "remark-parse";
|
|
1173
|
-
import { unified } from "unified";
|
|
1174
|
-
function createHeadingPattern(level) {
|
|
1175
|
-
if (level < 1 || level > 6) {
|
|
1176
|
-
throw new Error("Heading level must be between 1 and 6");
|
|
1177
|
-
}
|
|
1178
|
-
const hashes = "#".repeat(level);
|
|
1179
|
-
return new RegExp(`^${hashes} ([^\r
|
|
1180
|
-
]+)$`, "gm");
|
|
1181
|
-
}
|
|
1182
|
-
function hasComponent(content, componentName) {
|
|
1183
|
-
const escaped = componentName.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
1184
|
-
const pattern = new RegExp(String.raw`<${escaped}(?:\s[^>]*)?(?:>|\/>)`);
|
|
1185
|
-
return pattern.test(content);
|
|
1186
|
-
}
|
|
1187
|
-
function extractCodeBlocks(content) {
|
|
1188
|
-
const processor = unified().use(remarkParse);
|
|
1189
|
-
let tree;
|
|
1190
|
-
try {
|
|
1191
|
-
tree = processor.parse(content);
|
|
1192
|
-
} catch {
|
|
1193
|
-
return [];
|
|
1194
|
-
}
|
|
1195
|
-
const blocks = [];
|
|
1196
|
-
function visit(node) {
|
|
1197
|
-
if (node.type === "code") {
|
|
1198
|
-
const lang = node.lang ?? "";
|
|
1199
|
-
const value = node.value ?? "";
|
|
1200
|
-
blocks.push(`\`\`\`${lang}
|
|
1201
|
-
${value}
|
|
1202
|
-
\`\`\``);
|
|
1203
|
-
}
|
|
1204
|
-
if (Array.isArray(node.children)) {
|
|
1205
|
-
for (const child of node.children) {
|
|
1206
|
-
visit(child);
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
visit(tree);
|
|
1211
|
-
return blocks;
|
|
1212
|
-
}
|
|
1213
|
-
function parseJSXTags(content) {
|
|
1214
|
-
const results = [];
|
|
1215
|
-
let i = 0;
|
|
1216
|
-
while (i < content.length) {
|
|
1217
|
-
if (content[i] !== "<") {
|
|
1218
|
-
i++;
|
|
1219
|
-
continue;
|
|
1220
|
-
}
|
|
1221
|
-
const startIndex = i;
|
|
1222
|
-
i++;
|
|
1223
|
-
if (i >= content.length) break;
|
|
1224
|
-
const isClosing = content[i] === "/";
|
|
1225
|
-
if (isClosing) {
|
|
1226
|
-
i++;
|
|
1227
|
-
if (i >= content.length) break;
|
|
1228
|
-
}
|
|
1229
|
-
if (!/^[A-Z]/.test(content[i] ?? "")) {
|
|
1230
|
-
continue;
|
|
1231
|
-
}
|
|
1232
|
-
let tagName = "";
|
|
1233
|
-
while (i < content.length && /^[a-z0-9]/i.test(content[i] ?? "")) {
|
|
1234
|
-
tagName += content[i];
|
|
1235
|
-
i++;
|
|
1236
|
-
}
|
|
1237
|
-
if (tagName.length === 0) {
|
|
1238
|
-
continue;
|
|
1239
|
-
}
|
|
1240
|
-
if (isClosing) {
|
|
1241
|
-
if (i < content.length && content[i] === ">") {
|
|
1242
|
-
results.push({
|
|
1243
|
-
tag: `</${tagName}>`,
|
|
1244
|
-
index: startIndex,
|
|
1245
|
-
isClosing: true,
|
|
1246
|
-
isSelfClosing: false
|
|
1247
|
-
});
|
|
1248
|
-
i++;
|
|
1249
|
-
}
|
|
1250
|
-
continue;
|
|
1251
|
-
}
|
|
1252
|
-
let depth = 0;
|
|
1253
|
-
let foundEnd = false;
|
|
1254
|
-
let isSelfClosing = false;
|
|
1255
|
-
while (i < content.length && !foundEnd) {
|
|
1256
|
-
const char = content[i];
|
|
1257
|
-
if ((char === '"' || char === "'") && depth === 0) {
|
|
1258
|
-
const quote = char;
|
|
1259
|
-
i++;
|
|
1260
|
-
while (i < content.length && content[i] !== quote) {
|
|
1261
|
-
if (content[i] === "\\" && i + 1 < content.length) {
|
|
1262
|
-
i += 2;
|
|
1263
|
-
} else {
|
|
1264
|
-
i++;
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
if (i < content.length) i++;
|
|
1268
|
-
continue;
|
|
1269
|
-
}
|
|
1270
|
-
if (char === "{") {
|
|
1271
|
-
depth++;
|
|
1272
|
-
i++;
|
|
1273
|
-
continue;
|
|
1274
|
-
}
|
|
1275
|
-
if (char === "}") {
|
|
1276
|
-
depth = Math.max(0, depth - 1);
|
|
1277
|
-
i++;
|
|
1278
|
-
continue;
|
|
1279
|
-
}
|
|
1280
|
-
if (depth === 0) {
|
|
1281
|
-
if (char === "/" && i + 1 < content.length && content[i + 1] === ">") {
|
|
1282
|
-
isSelfClosing = true;
|
|
1283
|
-
foundEnd = true;
|
|
1284
|
-
i += 2;
|
|
1285
|
-
continue;
|
|
1286
|
-
}
|
|
1287
|
-
if (char === ">") {
|
|
1288
|
-
foundEnd = true;
|
|
1289
|
-
i++;
|
|
1290
|
-
continue;
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
i++;
|
|
1294
|
-
}
|
|
1295
|
-
if (foundEnd) {
|
|
1296
|
-
const fullTag = content.slice(startIndex, i);
|
|
1297
|
-
results.push({ tag: fullTag, index: startIndex, isClosing: false, isSelfClosing });
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
return results;
|
|
1301
|
-
}
|
|
1302
|
-
function findEmptyMarkdownLinks(content) {
|
|
1303
|
-
const positions = [];
|
|
1304
|
-
let pos = 0;
|
|
1305
|
-
while (pos < content.length) {
|
|
1306
|
-
const openBracket = content.indexOf("[", pos);
|
|
1307
|
-
if (openBracket === -1) break;
|
|
1308
|
-
let bracketDepth = 1;
|
|
1309
|
-
let closeBracket = openBracket + 1;
|
|
1310
|
-
while (closeBracket < content.length && bracketDepth > 0) {
|
|
1311
|
-
if (content[closeBracket] === "[") {
|
|
1312
|
-
bracketDepth++;
|
|
1313
|
-
} else if (content[closeBracket] === "]") {
|
|
1314
|
-
bracketDepth--;
|
|
1315
|
-
}
|
|
1316
|
-
closeBracket++;
|
|
1317
|
-
}
|
|
1318
|
-
if (bracketDepth !== 0) {
|
|
1319
|
-
pos = openBracket + 1;
|
|
1320
|
-
continue;
|
|
1321
|
-
}
|
|
1322
|
-
closeBracket--;
|
|
1323
|
-
if (closeBracket + 1 < content.length && content[closeBracket + 1] === "(") {
|
|
1324
|
-
let parenPos = closeBracket + 2;
|
|
1325
|
-
let isEmptyOrWhitespace = true;
|
|
1326
|
-
while (parenPos < content.length && content[parenPos] !== ")") {
|
|
1327
|
-
if (!/^\s/.test(content[parenPos] ?? "")) {
|
|
1328
|
-
isEmptyOrWhitespace = false;
|
|
1329
|
-
break;
|
|
1330
|
-
}
|
|
1331
|
-
parenPos++;
|
|
1332
|
-
}
|
|
1333
|
-
if (isEmptyOrWhitespace && parenPos < content.length && content[parenPos] === ")") {
|
|
1334
|
-
positions.push(openBracket);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
pos = closeBracket + 1;
|
|
1338
|
-
}
|
|
1339
|
-
return positions;
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
// src/generators/mdx-generator.ts
|
|
1343
1106
|
var DEFAULT_OPTIONS2 = {
|
|
1344
1107
|
includeAPI: true,
|
|
1345
1108
|
includeExamples: true,
|
|
@@ -1442,11 +1205,7 @@ function sanitizeTextContent(content) {
|
|
|
1442
1205
|
if (index > lastIndex) {
|
|
1443
1206
|
parts.push(sanitizeContent(content.slice(lastIndex, index)));
|
|
1444
1207
|
}
|
|
1445
|
-
|
|
1446
|
-
parts.push(tag);
|
|
1447
|
-
} else {
|
|
1448
|
-
parts.push(sanitizeJSXTag(tag));
|
|
1449
|
-
}
|
|
1208
|
+
parts.push(isClosing ? tag : sanitizeJSXTag(tag));
|
|
1450
1209
|
lastIndex = index + tag.length;
|
|
1451
1210
|
}
|
|
1452
1211
|
if (lastIndex < content.length) {
|
|
@@ -1471,17 +1230,23 @@ function validateMDXSyntax(mdx) {
|
|
|
1471
1230
|
}
|
|
1472
1231
|
return ok2(true);
|
|
1473
1232
|
}
|
|
1233
|
+
function isTypeScriptGeneric(tag) {
|
|
1234
|
+
const tagNameMatch = tag.match(/<\/?([A-Z][a-zA-Z0-9]*)/);
|
|
1235
|
+
const tagName = tagNameMatch?.[1];
|
|
1236
|
+
return tagName !== void 0 && tagName.length === 1;
|
|
1237
|
+
}
|
|
1474
1238
|
function checkForUnclosedTags(mdx) {
|
|
1475
1239
|
const unclosed = [];
|
|
1476
1240
|
const tagStack = [];
|
|
1477
1241
|
const codeBlocks = extractCodeBlocks(mdx);
|
|
1478
|
-
let
|
|
1242
|
+
let contentWithoutCode = mdx;
|
|
1479
1243
|
for (const block of codeBlocks) {
|
|
1480
1244
|
const lineCount = block.split("\n").length;
|
|
1481
1245
|
const placeholder = "\n".repeat(lineCount);
|
|
1482
|
-
|
|
1246
|
+
contentWithoutCode = contentWithoutCode.replace(block, placeholder);
|
|
1483
1247
|
}
|
|
1484
|
-
const
|
|
1248
|
+
const allJSXTags = parseJSXTags(contentWithoutCode);
|
|
1249
|
+
const jsxTags = allJSXTags.filter(({ tag }) => !isTypeScriptGeneric(tag));
|
|
1485
1250
|
for (const { tag, isClosing, isSelfClosing } of jsxTags) {
|
|
1486
1251
|
const tagNameMatch = isClosing ? tag.match(/^<\/([A-Z][a-zA-Z0-9]*)>$/) : tag.match(/^<([A-Z][a-zA-Z0-9]*)/);
|
|
1487
1252
|
const tagName = tagNameMatch?.[1];
|
|
@@ -1548,13 +1313,9 @@ export {
|
|
|
1548
1313
|
generateFrontmatter,
|
|
1549
1314
|
stringifyFrontmatter,
|
|
1550
1315
|
parseFrontmatter,
|
|
1551
|
-
createHeadingPattern,
|
|
1552
|
-
hasComponent,
|
|
1553
|
-
extractCodeBlocks,
|
|
1554
|
-
findEmptyMarkdownLinks,
|
|
1555
1316
|
generateMDXDocument,
|
|
1556
1317
|
sanitizeContent,
|
|
1557
1318
|
sanitizeTextContent,
|
|
1558
1319
|
validateMDXSyntax
|
|
1559
1320
|
};
|
|
1560
|
-
//# sourceMappingURL=chunk-
|
|
1321
|
+
//# sourceMappingURL=chunk-CUBMCGAY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-DRBRT57F.js.map
|