@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
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-DRBRT57F.js.map
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// src/utils/sanitization.ts
|
|
2
|
+
import { sanitizeInput } from "@bfra.me/es/validation";
|
|
3
|
+
import escapeHtml from "escape-html";
|
|
4
|
+
function sanitizeForMDX(content) {
|
|
5
|
+
const escaped = sanitizeInput(content, { trim: false });
|
|
6
|
+
return escaped.replaceAll("{", "{").replaceAll("}", "}");
|
|
7
|
+
}
|
|
8
|
+
function sanitizeAttribute(value) {
|
|
9
|
+
return escapeHtml(value);
|
|
10
|
+
}
|
|
11
|
+
function parseJSXAttributes(tag) {
|
|
12
|
+
const attrs = [];
|
|
13
|
+
const spaceIndex = tag.indexOf(" ");
|
|
14
|
+
if (spaceIndex === -1) return attrs;
|
|
15
|
+
const closeIndex = tag.lastIndexOf(">");
|
|
16
|
+
if (closeIndex === -1) return attrs;
|
|
17
|
+
const attrRegion = tag.slice(spaceIndex + 1, closeIndex).trim();
|
|
18
|
+
let i = 0;
|
|
19
|
+
while (i < attrRegion.length) {
|
|
20
|
+
while (i < attrRegion.length && /\s/.test(attrRegion.charAt(i))) i++;
|
|
21
|
+
if (i >= attrRegion.length) break;
|
|
22
|
+
let name = "";
|
|
23
|
+
while (i < attrRegion.length && /[\w-]/.test(attrRegion.charAt(i))) {
|
|
24
|
+
name += attrRegion[i];
|
|
25
|
+
i++;
|
|
26
|
+
}
|
|
27
|
+
if (!name) break;
|
|
28
|
+
while (i < attrRegion.length && /\s/.test(attrRegion.charAt(i))) i++;
|
|
29
|
+
if (i >= attrRegion.length || attrRegion[i] !== "=") {
|
|
30
|
+
attrs.push({ name, value: null });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
i++;
|
|
34
|
+
while (i < attrRegion.length && /\s/.test(attrRegion.charAt(i))) i++;
|
|
35
|
+
if (i >= attrRegion.length) {
|
|
36
|
+
attrs.push({ name, value: "" });
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
let value = "";
|
|
40
|
+
const quote = attrRegion[i];
|
|
41
|
+
if (quote === '"' || quote === "'") {
|
|
42
|
+
i++;
|
|
43
|
+
while (i < attrRegion.length && attrRegion[i] !== quote) {
|
|
44
|
+
value += attrRegion[i];
|
|
45
|
+
i++;
|
|
46
|
+
}
|
|
47
|
+
if (i < attrRegion.length) i++;
|
|
48
|
+
} else {
|
|
49
|
+
while (i < attrRegion.length && !/[\s/>]/.test(attrRegion.charAt(i))) {
|
|
50
|
+
value += attrRegion[i];
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
attrs.push({ name, value });
|
|
55
|
+
}
|
|
56
|
+
return attrs;
|
|
57
|
+
}
|
|
58
|
+
function sanitizeJSXTag(tag) {
|
|
59
|
+
const tagMatch = tag.match(/^<([A-Z][a-zA-Z0-9]*)/);
|
|
60
|
+
if (!tagMatch || typeof tagMatch[1] !== "string" || tagMatch[1].length === 0) {
|
|
61
|
+
return escapeHtml(tag);
|
|
62
|
+
}
|
|
63
|
+
const tagName = tagMatch[1];
|
|
64
|
+
const selfClosing = tag.endsWith("/>");
|
|
65
|
+
const attributes = parseJSXAttributes(tag);
|
|
66
|
+
const sanitizedAttrs = attributes.map(({ name, value }) => {
|
|
67
|
+
if (value === null) return name;
|
|
68
|
+
const escaped = escapeHtml(value);
|
|
69
|
+
return `${name}="${escaped}"`;
|
|
70
|
+
});
|
|
71
|
+
const attrString = sanitizedAttrs.length > 0 ? ` ${sanitizedAttrs.join(" ")}` : "";
|
|
72
|
+
return `<${tagName}${attrString}${selfClosing ? " />" : ">"}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/utils/safe-patterns.ts
|
|
76
|
+
import remarkParse from "remark-parse";
|
|
77
|
+
import { unified } from "unified";
|
|
78
|
+
function createHeadingPattern(level) {
|
|
79
|
+
if (level < 1 || level > 6) {
|
|
80
|
+
throw new Error("Heading level must be between 1 and 6");
|
|
81
|
+
}
|
|
82
|
+
const hashes = "#".repeat(level);
|
|
83
|
+
return new RegExp(`^${hashes} ([^\r
|
|
84
|
+
]+)$`, "gm");
|
|
85
|
+
}
|
|
86
|
+
function hasComponent(content, componentName) {
|
|
87
|
+
const escaped = componentName.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
88
|
+
const pattern = new RegExp(String.raw`<${escaped}(?:\s[^>]*)?(?:>|\/>)`);
|
|
89
|
+
return pattern.test(content);
|
|
90
|
+
}
|
|
91
|
+
function extractCodeBlocks(content) {
|
|
92
|
+
const processor = unified().use(remarkParse);
|
|
93
|
+
let tree;
|
|
94
|
+
try {
|
|
95
|
+
tree = processor.parse(content);
|
|
96
|
+
} catch {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
const blocks = [];
|
|
100
|
+
function visit(node) {
|
|
101
|
+
if (node.type === "code") {
|
|
102
|
+
const lang = node.lang ?? "";
|
|
103
|
+
const value = node.value ?? "";
|
|
104
|
+
blocks.push(`\`\`\`${lang}
|
|
105
|
+
${value}
|
|
106
|
+
\`\`\``);
|
|
107
|
+
}
|
|
108
|
+
if (node.type === "inlineCode") {
|
|
109
|
+
const value = node.value ?? "";
|
|
110
|
+
blocks.push(`\`${value}\``);
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(node.children)) {
|
|
113
|
+
for (const child of node.children) {
|
|
114
|
+
visit(child);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
visit(tree);
|
|
119
|
+
return blocks;
|
|
120
|
+
}
|
|
121
|
+
function parseJSXTags(content) {
|
|
122
|
+
const results = [];
|
|
123
|
+
let i = 0;
|
|
124
|
+
while (i < content.length) {
|
|
125
|
+
if (content[i] !== "<") {
|
|
126
|
+
i++;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const startIndex = i;
|
|
130
|
+
i++;
|
|
131
|
+
if (i >= content.length) break;
|
|
132
|
+
const isClosing = content[i] === "/";
|
|
133
|
+
if (isClosing) {
|
|
134
|
+
i++;
|
|
135
|
+
if (i >= content.length) break;
|
|
136
|
+
}
|
|
137
|
+
if (!/^[A-Z]/.test(content[i] ?? "")) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
let tagName = "";
|
|
141
|
+
while (i < content.length && /^[a-z0-9]/i.test(content[i] ?? "")) {
|
|
142
|
+
tagName += content[i];
|
|
143
|
+
i++;
|
|
144
|
+
}
|
|
145
|
+
if (tagName.length === 0) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (isClosing) {
|
|
149
|
+
if (i < content.length && content[i] === ">") {
|
|
150
|
+
results.push({
|
|
151
|
+
tag: `</${tagName}>`,
|
|
152
|
+
index: startIndex,
|
|
153
|
+
isClosing: true,
|
|
154
|
+
isSelfClosing: false
|
|
155
|
+
});
|
|
156
|
+
i++;
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
let depth = 0;
|
|
161
|
+
let foundEnd = false;
|
|
162
|
+
let isSelfClosing = false;
|
|
163
|
+
while (i < content.length && !foundEnd) {
|
|
164
|
+
const char = content[i];
|
|
165
|
+
if ((char === '"' || char === "'") && depth === 0) {
|
|
166
|
+
const quote = char;
|
|
167
|
+
i++;
|
|
168
|
+
while (i < content.length && content[i] !== quote) {
|
|
169
|
+
if (content[i] === "\\" && i + 1 < content.length) {
|
|
170
|
+
i += 2;
|
|
171
|
+
} else {
|
|
172
|
+
i++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (i < content.length) i++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (char === "{") {
|
|
179
|
+
depth++;
|
|
180
|
+
i++;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (char === "}") {
|
|
184
|
+
depth = Math.max(0, depth - 1);
|
|
185
|
+
i++;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (depth === 0) {
|
|
189
|
+
if (char === "/" && i + 1 < content.length && content[i + 1] === ">") {
|
|
190
|
+
isSelfClosing = true;
|
|
191
|
+
foundEnd = true;
|
|
192
|
+
i += 2;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (char === ">") {
|
|
196
|
+
foundEnd = true;
|
|
197
|
+
i++;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
i++;
|
|
202
|
+
}
|
|
203
|
+
if (foundEnd) {
|
|
204
|
+
const fullTag = content.slice(startIndex, i);
|
|
205
|
+
results.push({ tag: fullTag, index: startIndex, isClosing: false, isSelfClosing });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return results;
|
|
209
|
+
}
|
|
210
|
+
function findEmptyMarkdownLinks(content) {
|
|
211
|
+
const positions = [];
|
|
212
|
+
let pos = 0;
|
|
213
|
+
while (pos < content.length) {
|
|
214
|
+
const openBracket = content.indexOf("[", pos);
|
|
215
|
+
if (openBracket === -1) break;
|
|
216
|
+
let bracketDepth = 1;
|
|
217
|
+
let closeBracket = openBracket + 1;
|
|
218
|
+
while (closeBracket < content.length && bracketDepth > 0) {
|
|
219
|
+
if (content[closeBracket] === "[") {
|
|
220
|
+
bracketDepth++;
|
|
221
|
+
} else if (content[closeBracket] === "]") {
|
|
222
|
+
bracketDepth--;
|
|
223
|
+
}
|
|
224
|
+
closeBracket++;
|
|
225
|
+
}
|
|
226
|
+
if (bracketDepth !== 0) {
|
|
227
|
+
pos = openBracket + 1;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
closeBracket--;
|
|
231
|
+
if (closeBracket + 1 < content.length && content[closeBracket + 1] === "(") {
|
|
232
|
+
let parenPos = closeBracket + 2;
|
|
233
|
+
let isEmptyOrWhitespace = true;
|
|
234
|
+
while (parenPos < content.length && content[parenPos] !== ")") {
|
|
235
|
+
if (!/^\s/.test(content[parenPos] ?? "")) {
|
|
236
|
+
isEmptyOrWhitespace = false;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
parenPos++;
|
|
240
|
+
}
|
|
241
|
+
if (isEmptyOrWhitespace && parenPos < content.length && content[parenPos] === ")") {
|
|
242
|
+
positions.push(openBracket);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
pos = closeBracket + 1;
|
|
246
|
+
}
|
|
247
|
+
return positions;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export {
|
|
251
|
+
sanitizeForMDX,
|
|
252
|
+
sanitizeAttribute,
|
|
253
|
+
parseJSXAttributes,
|
|
254
|
+
sanitizeJSXTag,
|
|
255
|
+
createHeadingPattern,
|
|
256
|
+
hasComponent,
|
|
257
|
+
extractCodeBlocks,
|
|
258
|
+
parseJSXTags,
|
|
259
|
+
findEmptyMarkdownLinks
|
|
260
|
+
};
|
|
261
|
+
//# sourceMappingURL=chunk-GZ2MP3VN.js.map
|