folderblog 0.0.1 → 0.0.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.
Files changed (42) hide show
  1. package/README.md +109 -48
  2. package/dist/chunk-24MKFHML.cjs +143 -0
  3. package/dist/chunk-3RG5ZIWI.js +8 -0
  4. package/dist/chunk-4ZJGUMHS.cjs +78 -0
  5. package/dist/chunk-HMQIQUPB.cjs +387 -0
  6. package/dist/chunk-IXP35S24.js +1715 -0
  7. package/dist/chunk-OBGZSXTJ.cjs +10 -0
  8. package/dist/chunk-PARGDJNY.js +76 -0
  9. package/dist/chunk-QA4KPPTA.cjs +1787 -0
  10. package/dist/chunk-XP5J4LFJ.js +127 -0
  11. package/dist/chunk-ZRUBI3GH.js +370 -0
  12. package/dist/cli/bin.cjs +25 -0
  13. package/dist/cli/bin.d.cts +1 -0
  14. package/dist/cli/bin.d.ts +1 -0
  15. package/dist/cli/bin.js +23 -0
  16. package/dist/cli/index.cjs +22 -0
  17. package/dist/cli/index.d.cts +39 -0
  18. package/dist/cli/index.d.ts +39 -0
  19. package/dist/cli/index.js +15 -0
  20. package/dist/config-DFr-htlO.d.cts +887 -0
  21. package/dist/config-DFr-htlO.d.ts +887 -0
  22. package/dist/index.cjs +488 -1
  23. package/dist/index.d.cts +76 -8
  24. package/dist/index.d.ts +76 -8
  25. package/dist/index.js +153 -1
  26. package/dist/processor/index.cjs +337 -0
  27. package/dist/processor/index.d.cts +491 -0
  28. package/dist/processor/index.d.ts +491 -0
  29. package/dist/processor/index.js +4 -0
  30. package/dist/processor/plugins.cjs +51 -0
  31. package/dist/processor/plugins.d.cts +174 -0
  32. package/dist/processor/plugins.d.ts +174 -0
  33. package/dist/processor/plugins.js +2 -0
  34. package/dist/processor/types.cjs +67 -0
  35. package/dist/processor/types.d.cts +47 -0
  36. package/dist/processor/types.d.ts +47 -0
  37. package/dist/processor/types.js +2 -0
  38. package/dist/server/index.cjs +36 -0
  39. package/dist/server/index.d.cts +56 -0
  40. package/dist/server/index.d.ts +56 -0
  41. package/dist/server/index.js +34 -0
  42. package/package.json +63 -11
@@ -0,0 +1,1715 @@
1
+ import { PluginManager } from './chunk-ZRUBI3GH.js';
2
+ import { __require } from './chunk-3RG5ZIWI.js';
3
+ import { unified } from 'unified';
4
+ import remarkParse from 'remark-parse';
5
+ import remarkGfm from 'remark-gfm';
6
+ import remarkRehype from 'remark-rehype';
7
+ import rehypeStringify from 'rehype-stringify';
8
+ import rehypeRaw from 'rehype-raw';
9
+ import rehypeSlug from 'rehype-slug';
10
+ import GithubSlugger from 'github-slugger';
11
+ import matter from 'gray-matter';
12
+ import crypto from 'crypto';
13
+ import slugify from '@sindresorhus/slugify';
14
+ import fs from 'fs/promises';
15
+ import path5 from 'path';
16
+ import { visit } from 'unist-util-visit';
17
+
18
+ // ../processor/src/services/issueCollector.ts
19
+ var IssueCollector = class {
20
+ issues = [];
21
+ startTime;
22
+ constructor() {
23
+ this.startTime = (/* @__PURE__ */ new Date()).toISOString();
24
+ }
25
+ // --------------------------------------------------------------------------
26
+ // Core Methods
27
+ // --------------------------------------------------------------------------
28
+ /**
29
+ * Add a generic issue
30
+ */
31
+ addIssue(issue) {
32
+ this.issues.push({
33
+ ...issue,
34
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
35
+ });
36
+ }
37
+ /**
38
+ * Add a broken link issue
39
+ */
40
+ addBrokenLink(params) {
41
+ this.addIssue({
42
+ severity: "warning",
43
+ category: "broken-link",
44
+ module: "link-resolver",
45
+ message: `Broken ${params.linkType} link: [[${params.linkTarget}]] - target not found`,
46
+ filePath: params.filePath,
47
+ lineNumber: params.lineNumber,
48
+ context: {
49
+ linkText: params.linkText,
50
+ linkTarget: params.linkTarget,
51
+ linkType: params.linkType,
52
+ suggestions: params.suggestions
53
+ }
54
+ });
55
+ }
56
+ /**
57
+ * Add a missing media issue
58
+ */
59
+ addMissingMedia(params) {
60
+ this.addIssue({
61
+ severity: "warning",
62
+ category: "missing-media",
63
+ module: params.module ?? "embed-media",
64
+ message: `Missing media file: ${params.mediaPath}`,
65
+ filePath: params.filePath,
66
+ lineNumber: params.lineNumber,
67
+ context: {
68
+ mediaPath: params.mediaPath,
69
+ referencedFrom: params.referencedFrom,
70
+ originalReference: params.originalReference
71
+ }
72
+ });
73
+ }
74
+ /**
75
+ * Add a media processing error
76
+ */
77
+ addMediaProcessingError(params) {
78
+ this.addIssue({
79
+ severity: "error",
80
+ category: "media-processing",
81
+ module: "image-processor",
82
+ message: `Failed to ${params.operation} media: ${params.mediaPath} - ${params.errorMessage}`,
83
+ filePath: params.filePath,
84
+ context: {
85
+ mediaPath: params.mediaPath,
86
+ operation: params.operation,
87
+ errorMessage: params.errorMessage,
88
+ errorCode: params.errorCode
89
+ }
90
+ });
91
+ }
92
+ /**
93
+ * Add a slug conflict warning
94
+ */
95
+ addSlugConflict(params) {
96
+ this.addIssue({
97
+ severity: "info",
98
+ category: "slug-conflict",
99
+ module: "slug-generator",
100
+ message: `Slug conflict resolved: "${params.originalSlug}" \u2192 "${params.finalSlug}"`,
101
+ filePath: params.filePath,
102
+ context: {
103
+ originalSlug: params.originalSlug,
104
+ finalSlug: params.finalSlug,
105
+ conflictingFiles: params.conflictingFiles
106
+ }
107
+ });
108
+ }
109
+ /**
110
+ * Add a mermaid error
111
+ */
112
+ addMermaidError(params) {
113
+ this.addIssue({
114
+ severity: params.errorType === "missing-deps" ? "info" : "warning",
115
+ category: "mermaid-error",
116
+ module: "embed-mermaid",
117
+ message: params.message,
118
+ filePath: params.filePath,
119
+ lineNumber: params.lineNumber,
120
+ context: {
121
+ errorType: params.errorType,
122
+ diagramContent: params.diagramContent,
123
+ fallback: params.fallback
124
+ }
125
+ });
126
+ }
127
+ /**
128
+ * Add an embedding error
129
+ */
130
+ addEmbeddingError(params) {
131
+ this.addIssue({
132
+ severity: "error",
133
+ category: "embedding-error",
134
+ module: params.embeddingType === "text" ? "text-embeddings" : "image-embeddings",
135
+ message: `${params.embeddingType} embedding ${params.operation} failed: ${params.errorMessage}`,
136
+ filePath: params.filePath,
137
+ context: {
138
+ embeddingType: params.embeddingType,
139
+ operation: params.operation,
140
+ errorMessage: params.errorMessage
141
+ }
142
+ });
143
+ }
144
+ /**
145
+ * Add a plugin error
146
+ */
147
+ addPluginError(params) {
148
+ this.addIssue({
149
+ severity: "error",
150
+ category: "plugin-error",
151
+ module: "plugin-manager",
152
+ message: `Plugin "${params.pluginName}" ${params.operation} failed: ${params.errorMessage}`,
153
+ context: {
154
+ pluginName: params.pluginName,
155
+ operation: params.operation,
156
+ errorMessage: params.errorMessage
157
+ }
158
+ });
159
+ }
160
+ // --------------------------------------------------------------------------
161
+ // Query Methods
162
+ // --------------------------------------------------------------------------
163
+ /**
164
+ * Get all issues
165
+ */
166
+ getIssues() {
167
+ return [...this.issues];
168
+ }
169
+ /**
170
+ * Filter issues
171
+ */
172
+ filterIssues(options) {
173
+ return filterIssues(this.issues, options);
174
+ }
175
+ /**
176
+ * Get issue count by severity
177
+ */
178
+ getCountBySeverity(severity) {
179
+ return this.issues.filter((i) => i.severity === severity).length;
180
+ }
181
+ /**
182
+ * Check if there are any errors
183
+ */
184
+ hasErrors() {
185
+ return this.getCountBySeverity("error") > 0;
186
+ }
187
+ /**
188
+ * Get summary string for console output
189
+ */
190
+ getSummaryString() {
191
+ const errors = this.getCountBySeverity("error");
192
+ const warnings = this.getCountBySeverity("warning");
193
+ const info = this.getCountBySeverity("info");
194
+ const parts = [];
195
+ if (errors > 0) parts.push(`${errors} error${errors > 1 ? "s" : ""}`);
196
+ if (warnings > 0) parts.push(`${warnings} warning${warnings > 1 ? "s" : ""}`);
197
+ if (info > 0) parts.push(`${info} info`);
198
+ return parts.length > 0 ? `Processing completed with ${parts.join(", ")}` : "Processing completed successfully";
199
+ }
200
+ // --------------------------------------------------------------------------
201
+ // Report Generation
202
+ // --------------------------------------------------------------------------
203
+ /**
204
+ * Generate the final report
205
+ */
206
+ generateReport() {
207
+ const endTime = (/* @__PURE__ */ new Date()).toISOString();
208
+ return generateIssueReport(this.issues, this.startTime, endTime);
209
+ }
210
+ /**
211
+ * Clear all issues (for testing)
212
+ */
213
+ clear() {
214
+ this.issues = [];
215
+ }
216
+ };
217
+ var filterIssues = (issues, options) => {
218
+ let filtered = [...issues];
219
+ if (options.severity) {
220
+ const severities = Array.isArray(options.severity) ? options.severity : [options.severity];
221
+ filtered = filtered.filter((i) => severities.includes(i.severity));
222
+ }
223
+ if (options.category) {
224
+ const categories = Array.isArray(options.category) ? options.category : [options.category];
225
+ filtered = filtered.filter((i) => categories.includes(i.category));
226
+ }
227
+ if (options.module) {
228
+ const modules = Array.isArray(options.module) ? options.module : [options.module];
229
+ filtered = filtered.filter((i) => modules.includes(i.module));
230
+ }
231
+ if (options.filePath) {
232
+ const paths = Array.isArray(options.filePath) ? options.filePath : [options.filePath];
233
+ filtered = filtered.filter((i) => i.filePath && paths.includes(i.filePath));
234
+ }
235
+ return filtered;
236
+ };
237
+ var calculateSummary = (issues) => {
238
+ const errorCount = issues.filter((i) => i.severity === "error").length;
239
+ const warningCount = issues.filter((i) => i.severity === "warning").length;
240
+ const infoCount = issues.filter((i) => i.severity === "info").length;
241
+ const uniqueFiles = new Set(issues.map((i) => i.filePath).filter(Boolean));
242
+ const categoryCounts = {};
243
+ const moduleCounts = {};
244
+ for (const issue of issues) {
245
+ categoryCounts[issue.category] = (categoryCounts[issue.category] ?? 0) + 1;
246
+ moduleCounts[issue.module] = (moduleCounts[issue.module] ?? 0) + 1;
247
+ }
248
+ return {
249
+ totalIssues: issues.length,
250
+ errorCount,
251
+ warningCount,
252
+ infoCount,
253
+ filesAffected: uniqueFiles.size,
254
+ categoryCounts,
255
+ moduleCounts
256
+ };
257
+ };
258
+ var generateIssueReport = (issues, startTime, endTime) => ({
259
+ issues,
260
+ summary: calculateSummary(issues),
261
+ metadata: {
262
+ processStartTime: startTime,
263
+ processEndTime: endTime
264
+ }
265
+ });
266
+ var parseFrontmatter = (content) => {
267
+ const { content: markdown, data } = matter(content);
268
+ return {
269
+ content: markdown,
270
+ data
271
+ };
272
+ };
273
+ var DEFAULT_PIPELINE_OPTIONS = {
274
+ gfm: true,
275
+ allowRawHtml: true,
276
+ remarkPlugins: [],
277
+ rehypePlugins: []
278
+ };
279
+ var createBasePipeline = (options = {}) => {
280
+ const opts = { ...DEFAULT_PIPELINE_OPTIONS, ...options };
281
+ let processor = unified().use(remarkParse);
282
+ if (opts.gfm) {
283
+ processor = processor.use(remarkGfm);
284
+ }
285
+ for (const { plugin, options: pluginOpts } of opts.remarkPlugins ?? []) {
286
+ processor = processor.use(plugin, pluginOpts);
287
+ }
288
+ processor = processor.use(remarkRehype, {
289
+ allowDangerousHtml: opts.allowRawHtml
290
+ });
291
+ if (opts.allowRawHtml) {
292
+ processor = processor.use(rehypeRaw);
293
+ }
294
+ processor = processor.use(rehypeSlug);
295
+ for (const { plugin, options: pluginOpts } of opts.rehypePlugins ?? []) {
296
+ processor = processor.use(plugin, pluginOpts);
297
+ }
298
+ processor = processor.use(rehypeStringify);
299
+ return processor;
300
+ };
301
+ var parseToMdast = (markdown) => {
302
+ const processor = unified().use(remarkParse).use(remarkGfm);
303
+ return processor.parse(markdown);
304
+ };
305
+ var mdastToHast = async (mdast, options = {}) => {
306
+ const processor = unified().use(remarkRehype, { allowDangerousHtml: options.allowDangerousHtml ?? true }).use(rehypeRaw).use(rehypeSlug);
307
+ const result = await processor.run(mdast);
308
+ return result;
309
+ };
310
+ var hastToHtml = (hast) => {
311
+ const processor = unified().use(rehypeStringify);
312
+ return processor.stringify(hast);
313
+ };
314
+ var processMarkdown = async (content, options = {}) => {
315
+ const { content: markdown, data: frontmatter } = parseFrontmatter(content);
316
+ const mdast = parseToMdast(markdown);
317
+ const pipeline = createBasePipeline(options);
318
+ const file = await pipeline.process(markdown);
319
+ const hast = await mdastToHast(mdast, {
320
+ allowDangerousHtml: options.allowRawHtml ?? true
321
+ });
322
+ return {
323
+ html: String(file),
324
+ markdown,
325
+ frontmatter,
326
+ mdast,
327
+ hast
328
+ };
329
+ };
330
+ var mdastToText = (node) => {
331
+ const parts = [];
332
+ const visit2 = (n) => {
333
+ if (n.type === "text" || n.type === "inlineCode") {
334
+ parts.push(n.value);
335
+ }
336
+ if (n.children) {
337
+ for (const child of n.children) {
338
+ visit2(child);
339
+ }
340
+ }
341
+ };
342
+ visit2(node);
343
+ return parts.join(" ");
344
+ };
345
+ var extractFirstParagraph = (mdast) => {
346
+ for (const node of mdast.children) {
347
+ if (node.type === "paragraph") {
348
+ return mdastToText({ type: "root", children: [node] });
349
+ }
350
+ }
351
+ return "";
352
+ };
353
+ var extractHeadings = (mdast) => {
354
+ const headings = [];
355
+ const visit2 = (node) => {
356
+ if (node.type === "heading") {
357
+ headings.push({
358
+ depth: node.depth,
359
+ text: mdastToText({ type: "root", children: node.children })
360
+ });
361
+ }
362
+ if (node.children) {
363
+ for (const child of node.children) {
364
+ visit2(child);
365
+ }
366
+ }
367
+ };
368
+ visit2(mdast);
369
+ return headings;
370
+ };
371
+ var buildToc = (headings) => {
372
+ const slugger = new GithubSlugger();
373
+ return headings.map((h) => ({
374
+ ...h,
375
+ slug: slugger.slug(h.text)
376
+ }));
377
+ };
378
+
379
+ // ../processor/src/markdown/wordCount.ts
380
+ var countWords = (text) => {
381
+ if (!text || typeof text !== "string") {
382
+ return 0;
383
+ }
384
+ const normalized = text.trim().replace(/\s+/g, " ");
385
+ if (normalized.length === 0) {
386
+ return 0;
387
+ }
388
+ const words = normalized.split(" ").filter((word) => word.length > 0);
389
+ return words.length;
390
+ };
391
+ var estimateReadingTime = (wordCount, wordsPerMinute = 200) => {
392
+ return Math.max(1, Math.ceil(wordCount / wordsPerMinute));
393
+ };
394
+ var getContentStats = (text) => {
395
+ const wordCount = countWords(text);
396
+ return {
397
+ wordCount,
398
+ readingTimeMinutes: estimateReadingTime(wordCount)
399
+ };
400
+ };
401
+ var hashContent = (content) => {
402
+ const hashSum = crypto.createHash("sha256");
403
+ hashSum.update(content);
404
+ return hashSum.digest("hex");
405
+ };
406
+ var hashBuffer = (buffer) => {
407
+ const hashSum = crypto.createHash("sha256");
408
+ hashSum.update(new Uint8Array(buffer));
409
+ return hashSum.digest("hex");
410
+ };
411
+ var shortHash = (content, length = 8) => hashContent(content).substring(0, length);
412
+ var combineHashes = (...values) => hashContent(values.join(":"));
413
+ var toSlug = (s) => slugify(s, { decamelize: false });
414
+ var generateBaseSlug = (options) => {
415
+ const { fileName, parentFolder, siblingCount, frontmatterSlug } = options;
416
+ if (frontmatterSlug) {
417
+ return {
418
+ slug: frontmatterSlug,
419
+ source: "frontmatter"
420
+ };
421
+ }
422
+ if (fileName === "index" && parentFolder && (siblingCount === void 0 || siblingCount === 1)) {
423
+ return {
424
+ slug: toSlug(parentFolder),
425
+ source: "folder"
426
+ };
427
+ }
428
+ return {
429
+ slug: toSlug(fileName),
430
+ source: "filename"
431
+ };
432
+ };
433
+ var resolveSlugConflict = (baseSlug, usedSlugs, strategy = "number") => {
434
+ if (!usedSlugs.has(baseSlug)) {
435
+ return baseSlug;
436
+ }
437
+ if (strategy === "number") {
438
+ let counter = 2;
439
+ let newSlug = `${baseSlug}${counter}`;
440
+ while (usedSlugs.has(newSlug)) {
441
+ counter++;
442
+ newSlug = `${baseSlug}${counter}`;
443
+ }
444
+ return newSlug;
445
+ }
446
+ const hash = __require("crypto").createHash("sha256").update(baseSlug + Date.now().toString()).digest("hex").substring(0, 6);
447
+ return `${baseSlug}-${hash}`;
448
+ };
449
+ var SlugManager = class {
450
+ usedSlugs = /* @__PURE__ */ new Map();
451
+ scopedSlugs = /* @__PURE__ */ new Map();
452
+ fileSlugs = /* @__PURE__ */ new Map();
453
+ strategy;
454
+ constructor(strategy = "number") {
455
+ this.strategy = strategy;
456
+ }
457
+ /**
458
+ * Check if a slug is already used
459
+ */
460
+ isUsed(slug) {
461
+ return this.usedSlugs.has(slug);
462
+ }
463
+ /**
464
+ * Get slug info for a file path
465
+ */
466
+ getSlugInfo(filePath) {
467
+ return this.fileSlugs.get(filePath);
468
+ }
469
+ /**
470
+ * Get the slug map for a given scope, creating it if needed
471
+ */
472
+ getScopeMap(scope) {
473
+ let map = this.scopedSlugs.get(scope);
474
+ if (!map) {
475
+ map = /* @__PURE__ */ new Map();
476
+ this.scopedSlugs.set(scope, map);
477
+ }
478
+ return map;
479
+ }
480
+ /**
481
+ * Reserve a slug for a file
482
+ * @param scope - Optional scope for slug deduplication (e.g. top-level folder)
483
+ */
484
+ reserve(filePath, options, scope) {
485
+ const { slug: baseSlug, source } = generateBaseSlug(options);
486
+ const slugMap = scope !== void 0 ? this.getScopeMap(scope) : this.usedSlugs;
487
+ const existingFile = slugMap.get(baseSlug);
488
+ const wasModified = existingFile !== void 0 && existingFile !== filePath;
489
+ const finalSlug = wasModified ? resolveSlugConflict(baseSlug, new Set(slugMap.keys()), this.strategy) : baseSlug;
490
+ const info = {
491
+ slug: finalSlug,
492
+ originalSlug: baseSlug,
493
+ source,
494
+ wasModified
495
+ };
496
+ slugMap.set(finalSlug, filePath);
497
+ this.fileSlugs.set(filePath, info);
498
+ return info;
499
+ }
500
+ /**
501
+ * Get all assigned slugs
502
+ */
503
+ getAllSlugs() {
504
+ return this.fileSlugs;
505
+ }
506
+ };
507
+ var getFileName = (filePath) => {
508
+ const { name } = path5.parse(filePath);
509
+ return name;
510
+ };
511
+ var getExtension = (filePath) => {
512
+ const { ext } = path5.parse(filePath);
513
+ return ext.slice(1).toLowerCase();
514
+ };
515
+ var fileExists = async (filePath) => {
516
+ try {
517
+ await fs.access(filePath);
518
+ return true;
519
+ } catch {
520
+ return false;
521
+ }
522
+ };
523
+ var ensureDir = async (dirPath) => {
524
+ await fs.mkdir(dirPath, { recursive: true });
525
+ };
526
+ var writeJson = async (filePath, data) => {
527
+ await ensureDir(path5.dirname(filePath));
528
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
529
+ };
530
+ var readJson = async (filePath) => {
531
+ const content = await fs.readFile(filePath, "utf8");
532
+ return JSON.parse(content);
533
+ };
534
+ var writeText = async (filePath, content) => {
535
+ await ensureDir(path5.dirname(filePath));
536
+ await fs.writeFile(filePath, content, "utf8");
537
+ };
538
+ var readText = async (filePath) => {
539
+ return fs.readFile(filePath, "utf8");
540
+ };
541
+ var copyFile = async (src, dest) => {
542
+ await ensureDir(path5.dirname(dest));
543
+ await fs.copyFile(src, dest);
544
+ };
545
+ var getStats = async (filePath) => {
546
+ const stats = await fs.stat(filePath);
547
+ return {
548
+ size: stats.size,
549
+ created: stats.birthtime,
550
+ modified: stats.mtime
551
+ };
552
+ };
553
+ var findFiles = async (dir, predicate, recursive = true) => {
554
+ const results = [];
555
+ const entries = await fs.readdir(dir, { withFileTypes: true });
556
+ for (const entry of entries) {
557
+ const fullPath = path5.join(dir, entry.name);
558
+ if (entry.isDirectory() && recursive) {
559
+ const nested = await findFiles(fullPath, predicate, recursive);
560
+ results.push(...nested);
561
+ } else if (entry.isFile() && predicate(entry.name)) {
562
+ results.push(fullPath);
563
+ }
564
+ }
565
+ return results;
566
+ };
567
+ var findMarkdownFiles = (dir, recursive = true) => findFiles(dir, (name) => name.endsWith(".md"), recursive);
568
+ var relativePath = (from, to) => path5.relative(from, to);
569
+ var normalizePath = (p) => p.split(path5.sep).join("/");
570
+ function findByFileNameCaseInsensitive(fileName, fileMap) {
571
+ const exact = fileMap.get(fileName);
572
+ if (exact) return exact;
573
+ const lowerFileName = fileName.toLowerCase();
574
+ for (const [key, value] of fileMap.entries()) {
575
+ if (key.toLowerCase() === lowerFileName) {
576
+ return value;
577
+ }
578
+ }
579
+ return [];
580
+ }
581
+ function resolveFromCandidates(candidates, currentFilePath) {
582
+ if (candidates.length === 1) {
583
+ return candidates[0];
584
+ }
585
+ if (!candidates.length) {
586
+ return null;
587
+ }
588
+ if (currentFilePath) {
589
+ const currentDir = path5.dirname(currentFilePath);
590
+ const sameDir = candidates.find(
591
+ (f) => path5.dirname(f.originalPath) === currentDir
592
+ );
593
+ if (sameDir) return sameDir;
594
+ let parentDir = currentDir;
595
+ while (parentDir && parentDir !== "." && parentDir !== "/") {
596
+ parentDir = path5.dirname(parentDir);
597
+ const parentMatch = candidates.find(
598
+ (f) => path5.dirname(f.originalPath) === parentDir
599
+ );
600
+ if (parentMatch) return parentMatch;
601
+ }
602
+ }
603
+ return candidates[0];
604
+ }
605
+ function createPathVariations(targetPath, currentFilePath) {
606
+ const normalizedPath = targetPath.replace(/\\/g, "/");
607
+ const withLeadingSlash = normalizedPath.startsWith("/") ? normalizedPath : `/${normalizedPath}`;
608
+ const withoutLeadingSlash = normalizedPath.startsWith("/") ? normalizedPath.substring(1) : normalizedPath;
609
+ const variations = [
610
+ normalizedPath,
611
+ normalizedPath.toLowerCase(),
612
+ withLeadingSlash,
613
+ withLeadingSlash.toLowerCase(),
614
+ withoutLeadingSlash,
615
+ withoutLeadingSlash.toLowerCase()
616
+ ];
617
+ if (currentFilePath && (normalizedPath.includes("../") || normalizedPath.includes("./"))) {
618
+ const currentDir = path5.dirname(currentFilePath);
619
+ const resolvedPath = path5.join(currentDir, normalizedPath).replace(/\\/g, "/");
620
+ const normalizedResolved = path5.normalize(resolvedPath).replace(/\\/g, "/");
621
+ variations.unshift(normalizedResolved, normalizedResolved.toLowerCase());
622
+ }
623
+ if (currentFilePath && !normalizedPath.includes("/")) {
624
+ const currentDir = path5.dirname(currentFilePath);
625
+ if (currentDir && currentDir !== ".") {
626
+ const inCurrentDir = path5.join(currentDir, normalizedPath).replace(/\\/g, "/");
627
+ variations.unshift(inCurrentDir, inCurrentDir.toLowerCase());
628
+ }
629
+ }
630
+ const baseName = path5.basename(normalizedPath);
631
+ if (baseName !== normalizedPath) {
632
+ variations.push(baseName, baseName.toLowerCase());
633
+ }
634
+ return [...new Set(variations)];
635
+ }
636
+ function resolveFile(targetPath, options) {
637
+ const { byPath, byName, currentFilePath, extensions = [] } = options;
638
+ const baseVariations = createPathVariations(targetPath, currentFilePath);
639
+ const allVariations = [];
640
+ for (const variation of baseVariations) {
641
+ allVariations.push(variation);
642
+ const hasExtension = extensions.some((ext) => variation.endsWith(ext));
643
+ if (!hasExtension) {
644
+ for (const ext of extensions) {
645
+ allVariations.push(variation + ext);
646
+ }
647
+ }
648
+ }
649
+ for (const variation of allVariations) {
650
+ const exactMatch = byPath.get(variation);
651
+ if (exactMatch) return exactMatch;
652
+ }
653
+ if (byName) {
654
+ const fileName = path5.basename(targetPath);
655
+ const candidates = findByFileNameCaseInsensitive(fileName, byName);
656
+ if (candidates.length > 0) {
657
+ return resolveFromCandidates(candidates, currentFilePath);
658
+ }
659
+ for (const ext of extensions) {
660
+ if (fileName.endsWith(ext)) {
661
+ const nameWithoutExt = fileName.slice(0, -ext.length);
662
+ const candidatesNoExt = findByFileNameCaseInsensitive(
663
+ nameWithoutExt,
664
+ byName
665
+ );
666
+ if (candidatesNoExt.length > 0) {
667
+ return resolveFromCandidates(candidatesNoExt, currentFilePath);
668
+ }
669
+ }
670
+ }
671
+ }
672
+ return null;
673
+ }
674
+ function buildFileMaps(posts) {
675
+ const byPath = /* @__PURE__ */ new Map();
676
+ const bySlug = /* @__PURE__ */ new Map();
677
+ const byFileName = /* @__PURE__ */ new Map();
678
+ const byAlias = /* @__PURE__ */ new Map();
679
+ for (const post of posts) {
680
+ byPath.set(post.originalPath, post);
681
+ bySlug.set(post.slug, post);
682
+ const fileName = path5.basename(post.originalPath, ".md");
683
+ if (!byFileName.has(fileName)) {
684
+ byFileName.set(fileName, []);
685
+ }
686
+ byFileName.get(fileName).push(post);
687
+ const aliases = post.frontmatter?.aliases;
688
+ if (Array.isArray(aliases)) {
689
+ for (const alias of aliases) {
690
+ if (typeof alias === "string" && alias.trim()) {
691
+ const normalizedAlias = alias.trim().toLowerCase();
692
+ if (!byAlias.has(normalizedAlias)) {
693
+ byAlias.set(normalizedAlias, []);
694
+ }
695
+ byAlias.get(normalizedAlias).push(post);
696
+ }
697
+ }
698
+ }
699
+ }
700
+ return { byPath, bySlug, byFileName, byAlias };
701
+ }
702
+ function findBySlugCaseInsensitive(slug, bySlug) {
703
+ const exact = bySlug.get(slug);
704
+ if (exact) return exact;
705
+ const lowerSlug = slug.toLowerCase();
706
+ for (const [key, value] of bySlug.entries()) {
707
+ if (key.toLowerCase() === lowerSlug) {
708
+ return value;
709
+ }
710
+ }
711
+ const slugifiedSearch = toSlug(slug);
712
+ const exactSlugified = bySlug.get(slugifiedSearch);
713
+ if (exactSlugified) return exactSlugified;
714
+ const lowerSlugified = slugifiedSearch.toLowerCase();
715
+ for (const [key, value] of bySlug.entries()) {
716
+ if (key.toLowerCase() === lowerSlugified) {
717
+ return value;
718
+ }
719
+ }
720
+ return null;
721
+ }
722
+ function findByFileNameCI(fileName, byFileName) {
723
+ const results = findByFileNameCaseInsensitive(fileName, byFileName);
724
+ if (results.length > 0) return results;
725
+ const slugifiedSearch = toSlug(fileName).replace(/-/g, " ");
726
+ const slugResults = findByFileNameCaseInsensitive(slugifiedSearch, byFileName);
727
+ if (slugResults.length > 0) return slugResults;
728
+ return null;
729
+ }
730
+ function resolveWikilink(wikilink, fileMap, currentFilePath) {
731
+ const cleanLink = wikilink.split("#")[0].split("#^")[0];
732
+ const bySlug = findBySlugCaseInsensitive(cleanLink, fileMap.bySlug);
733
+ if (bySlug) return bySlug;
734
+ const byAlias = fileMap.byAlias.get(cleanLink.toLowerCase());
735
+ if (byAlias && byAlias.length > 0) {
736
+ return resolveFromCandidates(byAlias, currentFilePath);
737
+ }
738
+ if (cleanLink.includes("/")) {
739
+ const possiblePaths = [
740
+ cleanLink + ".md",
741
+ cleanLink,
742
+ path5.join(path5.dirname(currentFilePath), cleanLink + ".md"),
743
+ path5.join(path5.dirname(currentFilePath), cleanLink)
744
+ ];
745
+ for (const possiblePath of possiblePaths) {
746
+ const normalized = path5.normalize(possiblePath);
747
+ const file = fileMap.byPath.get(normalized);
748
+ if (file) return file;
749
+ }
750
+ }
751
+ const candidates = findByFileNameCI(cleanLink, fileMap.byFileName);
752
+ if (!candidates || candidates.length === 0) {
753
+ const withoutExt = cleanLink.replace(/\.md$/, "");
754
+ const candidatesWithoutExt = findByFileNameCI(
755
+ withoutExt,
756
+ fileMap.byFileName
757
+ );
758
+ if (!candidatesWithoutExt || candidatesWithoutExt.length === 0) {
759
+ return null;
760
+ }
761
+ return resolveFromCandidates(candidatesWithoutExt, currentFilePath);
762
+ }
763
+ return resolveFromCandidates(candidates, currentFilePath);
764
+ }
765
+ function slugifyAnchor(text) {
766
+ return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").trim();
767
+ }
768
+ function wikilinkToMarkdownLink(wikilink, alias, targetFile, options) {
769
+ let anchor = "";
770
+ if (wikilink.includes("#^")) ; else if (wikilink.includes("#")) {
771
+ const parts = wikilink.split("#");
772
+ anchor = "#" + slugifyAnchor(parts[1]);
773
+ }
774
+ const url = `${options.urlPrefix}/${targetFile.slug}${anchor}`;
775
+ const text = alias || targetFile.title || targetFile.fileName;
776
+ return { url, text };
777
+ }
778
+ function resolveMarkdownLink(href, fileMap, currentFilePath, options) {
779
+ if (href.startsWith("http://") || href.startsWith("https://") || href.startsWith("mailto:") || href.startsWith("tel:") || href.startsWith("#") || href.startsWith(options.urlPrefix)) {
780
+ return href;
781
+ }
782
+ let cleanHref = href;
783
+ if (cleanHref.startsWith("/content/")) {
784
+ cleanHref = cleanHref.substring("/content/".length);
785
+ }
786
+ let anchor = "";
787
+ const anchorIndex = cleanHref.indexOf("#");
788
+ if (anchorIndex !== -1) {
789
+ anchor = cleanHref.substring(anchorIndex);
790
+ cleanHref = cleanHref.substring(0, anchorIndex);
791
+ }
792
+ const bySlug = findBySlugCaseInsensitive(cleanHref, fileMap.bySlug);
793
+ if (bySlug) {
794
+ return `${options.urlPrefix}/${bySlug.slug}${anchor}`;
795
+ }
796
+ const possiblePaths = [
797
+ cleanHref + ".md",
798
+ cleanHref,
799
+ path5.join(path5.dirname(currentFilePath), cleanHref + ".md"),
800
+ path5.join(path5.dirname(currentFilePath), cleanHref)
801
+ ];
802
+ for (const possiblePath of possiblePaths) {
803
+ const normalized = path5.normalize(possiblePath);
804
+ const file = fileMap.byPath.get(normalized);
805
+ if (file) {
806
+ return `${options.urlPrefix}/${file.slug}${anchor}`;
807
+ }
808
+ }
809
+ return `${options.urlPrefix}/${cleanHref}${anchor}`;
810
+ }
811
+ var Regex = {
812
+ HeaderOnly: /^#[^\^]+/,
813
+ BlockOnly: /^#\^.+/,
814
+ PageAndHeader: /.+#[^\^]+/,
815
+ PageAndBlock: /.+#\^.+/
816
+ };
817
+ function wikiToObsidian(wikiLink) {
818
+ const { value, alias } = wikiLink;
819
+ switch (true) {
820
+ case Regex.BlockOnly.test(value): {
821
+ const blockOnly = { type: "block", block: value.slice(2) };
822
+ if (alias) blockOnly.alias = alias;
823
+ return blockOnly;
824
+ }
825
+ case Regex.HeaderOnly.test(value): {
826
+ const headerOnly = { type: "header", header: value.slice(1) };
827
+ if (alias) headerOnly.alias = alias;
828
+ return headerOnly;
829
+ }
830
+ case Regex.PageAndBlock.test(value): {
831
+ const [page, block] = value.split("#^");
832
+ const pageAndBlock = { type: "page-block", page, block };
833
+ if (alias) pageAndBlock.alias = alias;
834
+ return pageAndBlock;
835
+ }
836
+ case Regex.PageAndHeader.test(value): {
837
+ const [page, header] = value.split("#");
838
+ const pageAndHeader = { type: "page-header", page, header };
839
+ if (alias) pageAndHeader.alias = alias;
840
+ return pageAndHeader;
841
+ }
842
+ default: {
843
+ const page = { type: "page", page: value };
844
+ if (alias) page.alias = alias;
845
+ return page;
846
+ }
847
+ }
848
+ }
849
+ function slugifyAnchor2(text) {
850
+ return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").trim();
851
+ }
852
+ function createCustomToLink(options) {
853
+ const {
854
+ filesBySlug,
855
+ filesByName,
856
+ filesByAlias,
857
+ urlPrefix,
858
+ currentFile,
859
+ issueCollector
860
+ } = options;
861
+ return (wikiLink) => {
862
+ const obsidianLink = wikiToObsidian(wikiLink);
863
+ switch (obsidianLink.type) {
864
+ case "page":
865
+ case "page-header":
866
+ case "page-block": {
867
+ let targetFile = filesBySlug.get(obsidianLink.page);
868
+ if (!targetFile) {
869
+ const aliasCandidates = filesByAlias.get(
870
+ obsidianLink.page.toLowerCase()
871
+ );
872
+ if (aliasCandidates && aliasCandidates.length > 0) {
873
+ if (aliasCandidates.length === 1) {
874
+ targetFile = aliasCandidates[0];
875
+ } else if (currentFile) {
876
+ const currentDir = path5.dirname(currentFile.originalPath);
877
+ const sameDir = aliasCandidates.find(
878
+ (f) => path5.dirname(f.originalPath) === currentDir
879
+ );
880
+ targetFile = sameDir || aliasCandidates[0];
881
+ } else {
882
+ targetFile = aliasCandidates[0];
883
+ }
884
+ }
885
+ }
886
+ if (!targetFile) {
887
+ targetFile = filesByName.get(obsidianLink.page);
888
+ }
889
+ if (!targetFile) {
890
+ if (issueCollector && currentFile) {
891
+ issueCollector.addBrokenLink({
892
+ filePath: currentFile.originalPath,
893
+ linkText: obsidianLink.alias || obsidianLink.page,
894
+ linkTarget: obsidianLink.page,
895
+ linkType: "wiki"
896
+ });
897
+ }
898
+ return {
899
+ value: obsidianLink.alias || obsidianLink.page,
900
+ uri: `#broken-link:${obsidianLink.page}`
901
+ };
902
+ }
903
+ let uri = `${urlPrefix}/${targetFile.slug}`;
904
+ if (obsidianLink.type === "page-header") {
905
+ uri += `#${slugifyAnchor2(obsidianLink.header)}`;
906
+ }
907
+ return {
908
+ value: obsidianLink.alias || obsidianLink.page,
909
+ uri
910
+ };
911
+ }
912
+ case "header":
913
+ return {
914
+ value: obsidianLink.alias || `#${obsidianLink.header}`,
915
+ uri: `#${slugifyAnchor2(obsidianLink.header)}`
916
+ };
917
+ case "block":
918
+ return {
919
+ value: obsidianLink.alias || `#^${obsidianLink.block}`,
920
+ uri: ""
921
+ };
922
+ default: {
923
+ return { value: String(obsidianLink), uri: "" };
924
+ }
925
+ }
926
+ };
927
+ }
928
+ function resolveMarkdownLinkPath(linkPath, currentFilePath, filesByPath) {
929
+ if (linkPath.startsWith("http://") || linkPath.startsWith("https://") || linkPath.startsWith("mailto:") || linkPath.startsWith("#")) {
930
+ return null;
931
+ }
932
+ const cleanPath = linkPath.split("#")[0];
933
+ const currentDir = path5.dirname(currentFilePath);
934
+ const resolvedPath = path5.normalize(path5.join(currentDir, cleanPath));
935
+ const candidates = [resolvedPath, resolvedPath.replace(/\.md$/, "") + ".md"];
936
+ for (const candidate of candidates) {
937
+ const file = filesByPath.get(candidate);
938
+ if (file) return file;
939
+ }
940
+ return null;
941
+ }
942
+ var remarkMarkdownLinkResolver = (options) => {
943
+ const { filesByPath, urlPrefix, currentFilePath } = options;
944
+ return (tree) => {
945
+ visit(tree, "link", (node) => {
946
+ if (node.url && !node.url.startsWith(urlPrefix) && !node.url.startsWith("http")) {
947
+ const targetFile = resolveMarkdownLinkPath(
948
+ node.url,
949
+ currentFilePath,
950
+ filesByPath
951
+ );
952
+ if (targetFile) {
953
+ const anchorIndex = node.url.indexOf("#");
954
+ const anchor = anchorIndex !== -1 ? node.url.substring(anchorIndex) : "";
955
+ node.url = `${urlPrefix}/${targetFile.slug}${anchor}`;
956
+ } else {
957
+ const filename = node.url.replace(/\.md$/, "");
958
+ for (const [, file] of filesByPath.entries()) {
959
+ if (file.fileName === filename) {
960
+ const anchorIndex = node.url.indexOf("#");
961
+ const anchor = anchorIndex !== -1 ? node.url.substring(anchorIndex) : "";
962
+ node.url = `${urlPrefix}/${file.slug}${anchor}`;
963
+ break;
964
+ }
965
+ }
966
+ }
967
+ }
968
+ });
969
+ };
970
+ };
971
+ var remarkObsidianLink;
972
+ var rehypeAutolinkHeadings;
973
+ var rehypeExternalLinks;
974
+ var createDefaultLogger = (debugLevel) => {
975
+ return (message, level = "info") => {
976
+ const levelNum = level === "debug" ? 3 : level === "info" ? 2 : level === "warn" ? 1 : 0;
977
+ if (levelNum <= debugLevel) {
978
+ const prefix = "[processor-core]";
979
+ switch (level) {
980
+ case "error":
981
+ console.error(`${prefix} ERROR: ${message}`);
982
+ break;
983
+ case "warn":
984
+ console.warn(`${prefix} WARN: ${message}`);
985
+ break;
986
+ case "debug":
987
+ console.log(`${prefix} DEBUG: ${message}`);
988
+ break;
989
+ default:
990
+ console.log(`${prefix} ${message}`);
991
+ }
992
+ }
993
+ };
994
+ };
995
+ var Processor = class {
996
+ config;
997
+ pluginManager;
998
+ issues;
999
+ log;
1000
+ initialized = false;
1001
+ constructor(options) {
1002
+ this.config = options.config;
1003
+ this.issues = new IssueCollector();
1004
+ this.log = options.log ?? createDefaultLogger(this.config.debug?.level ?? 1);
1005
+ this.pluginManager = new PluginManager({
1006
+ config: this.config,
1007
+ outputDir: this.resolveOutputDir(),
1008
+ issues: this.issues,
1009
+ log: this.log
1010
+ });
1011
+ }
1012
+ // --------------------------------------------------------------------------
1013
+ // Initialization
1014
+ // --------------------------------------------------------------------------
1015
+ /**
1016
+ * Initialize the processor and plugins
1017
+ */
1018
+ async initialize() {
1019
+ if (this.initialized) {
1020
+ return;
1021
+ }
1022
+ this.log("Initializing processor...", "info");
1023
+ await this.pluginManager.initialize();
1024
+ this.initialized = true;
1025
+ this.log("Processor initialized", "info");
1026
+ }
1027
+ /**
1028
+ * Dispose the processor and cleanup resources
1029
+ */
1030
+ async dispose() {
1031
+ await this.pluginManager.dispose();
1032
+ this.initialized = false;
1033
+ }
1034
+ // --------------------------------------------------------------------------
1035
+ // Path Resolution
1036
+ // --------------------------------------------------------------------------
1037
+ resolveInputDir() {
1038
+ const base = this.config.dir.base ?? process.cwd();
1039
+ return path5.resolve(base, this.config.dir.input);
1040
+ }
1041
+ resolveOutputDir() {
1042
+ const base = this.config.dir.base ?? process.cwd();
1043
+ return path5.resolve(base, this.config.dir.output ?? "build");
1044
+ }
1045
+ // --------------------------------------------------------------------------
1046
+ // Processing
1047
+ // --------------------------------------------------------------------------
1048
+ /**
1049
+ * Process the folder and generate output
1050
+ */
1051
+ async process() {
1052
+ if (!this.initialized) {
1053
+ await this.initialize();
1054
+ }
1055
+ const inputDir = this.resolveInputDir();
1056
+ const outputDir = this.resolveOutputDir();
1057
+ this.log(`Processing folder: ${inputDir}`, "info");
1058
+ this.log(`Output directory: ${outputDir}`, "info");
1059
+ await ensureDir(outputDir);
1060
+ const state = {
1061
+ inputDir,
1062
+ outputDir,
1063
+ posts: [],
1064
+ media: [],
1065
+ slugManager: new SlugManager("number"),
1066
+ issues: this.issues,
1067
+ mediaPathMap: /* @__PURE__ */ new Map(),
1068
+ cacheStats: {
1069
+ mediaCacheHits: 0,
1070
+ mediaCacheMisses: 0,
1071
+ textEmbeddingCacheHits: 0,
1072
+ textEmbeddingCacheMisses: 0,
1073
+ imageEmbeddingCacheHits: 0,
1074
+ imageEmbeddingCacheMisses: 0
1075
+ }
1076
+ };
1077
+ if (!this.config.media?.skip) {
1078
+ await this.processMedia(state);
1079
+ } else {
1080
+ this.log("Skipping media processing", "info");
1081
+ }
1082
+ await this.processMarkdownFiles(state);
1083
+ await this.generateEmbeddings(state);
1084
+ await this.generateSimilarity(state);
1085
+ await this.buildDatabase(state);
1086
+ const outputFiles = await this.writeOutput(state);
1087
+ const issueReport = this.issues.generateReport();
1088
+ this.log(this.issues.getSummaryString(), "info");
1089
+ const hasCacheActivity = state.cacheStats.mediaCacheHits > 0 || state.cacheStats.mediaCacheMisses > 0 || state.cacheStats.textEmbeddingCacheHits > 0 || state.cacheStats.imageEmbeddingCacheHits > 0;
1090
+ if (hasCacheActivity) {
1091
+ this.log(
1092
+ `Cache stats: media ${state.cacheStats.mediaCacheHits}/${state.cacheStats.mediaCacheHits + state.cacheStats.mediaCacheMisses} hits, text embeddings ${state.cacheStats.textEmbeddingCacheHits}/${state.cacheStats.textEmbeddingCacheHits + state.cacheStats.textEmbeddingCacheMisses} hits, image embeddings ${state.cacheStats.imageEmbeddingCacheHits}/${state.cacheStats.imageEmbeddingCacheHits + state.cacheStats.imageEmbeddingCacheMisses} hits`,
1093
+ "info"
1094
+ );
1095
+ }
1096
+ return {
1097
+ posts: state.posts,
1098
+ media: state.media,
1099
+ outputDir,
1100
+ outputFiles,
1101
+ issues: issueReport,
1102
+ cacheStats: this.config.cache ? state.cacheStats : void 0
1103
+ };
1104
+ }
1105
+ // --------------------------------------------------------------------------
1106
+ // Media Processing
1107
+ // --------------------------------------------------------------------------
1108
+ async processMedia(state) {
1109
+ this.log("Processing media files...", "info");
1110
+ const imageProcessor = this.pluginManager.getPluginByKey("imageProcessor");
1111
+ if (!imageProcessor) {
1112
+ this.log("No image processor plugin, skipping media optimization", "debug");
1113
+ return;
1114
+ }
1115
+ const mediaExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".avif", ".svg"];
1116
+ const mediaFiles = [];
1117
+ const findMedia = async (dir) => {
1118
+ const entries = await fs.readdir(dir, { withFileTypes: true });
1119
+ for (const entry of entries) {
1120
+ const fullPath = path5.join(dir, entry.name);
1121
+ if (entry.isDirectory()) {
1122
+ await findMedia(fullPath);
1123
+ } else if (entry.isFile() && mediaExtensions.some((ext) => entry.name.toLowerCase().endsWith(ext))) {
1124
+ mediaFiles.push(fullPath);
1125
+ }
1126
+ }
1127
+ };
1128
+ await findMedia(state.inputDir);
1129
+ this.log(`Found ${mediaFiles.length} media files`, "info");
1130
+ const mediaOutputDir = path5.join(state.outputDir, this.config.dir.mediaOutput ?? "_media");
1131
+ await ensureDir(mediaOutputDir);
1132
+ const imageSizes = this.config.media?.sizes ?? [];
1133
+ const imageFormat = this.config.media?.format ?? "webp";
1134
+ const imageQuality = this.config.media?.quality ?? 80;
1135
+ const useHash = this.config.media?.useHash ?? true;
1136
+ const useSharding = this.config.media?.useSharding ?? false;
1137
+ for (const mediaPath of mediaFiles) {
1138
+ try {
1139
+ const relativePath2 = normalizePath(path5.relative(state.inputDir, mediaPath));
1140
+ const fileName = path5.basename(mediaPath);
1141
+ const fileBuffer = await fs.readFile(mediaPath);
1142
+ const contentHash = hashBuffer(fileBuffer);
1143
+ const shortContentHash = contentHash.substring(0, 16);
1144
+ const mediaCache = this.config.cache?.media;
1145
+ const cachedMedia = mediaCache?.get(contentHash);
1146
+ if (cachedMedia) {
1147
+ state.cacheStats.mediaCacheHits++;
1148
+ this.log(`Cache hit for ${fileName} (${contentHash.substring(0, 8)}...)`, "debug");
1149
+ const processedMedia = {
1150
+ originalPath: relativePath2,
1151
+ outputPath: cachedMedia.outputPath,
1152
+ fileName,
1153
+ type: "image",
1154
+ metadata: {
1155
+ width: cachedMedia.width,
1156
+ height: cachedMedia.height,
1157
+ format: cachedMedia.format,
1158
+ size: cachedMedia.size,
1159
+ originalSize: cachedMedia.originalSize,
1160
+ hash: contentHash
1161
+ },
1162
+ sizes: cachedMedia.sizes.length > 0 ? cachedMedia.sizes.map((s) => ({
1163
+ suffix: s.suffix,
1164
+ outputPath: s.outputPath,
1165
+ width: s.width,
1166
+ height: s.height,
1167
+ size: s.size
1168
+ })) : void 0
1169
+ };
1170
+ state.media.push(processedMedia);
1171
+ state.mediaPathMap.set(relativePath2, processedMedia);
1172
+ continue;
1173
+ }
1174
+ state.cacheStats.mediaCacheMisses++;
1175
+ let outputFileName;
1176
+ let outputSubDir = mediaOutputDir;
1177
+ if (useHash) {
1178
+ outputFileName = `${shortContentHash}.${imageFormat}`;
1179
+ if (useSharding) {
1180
+ const shardDir = contentHash.substring(0, 2);
1181
+ outputSubDir = path5.join(mediaOutputDir, shardDir);
1182
+ await ensureDir(outputSubDir);
1183
+ }
1184
+ } else {
1185
+ const fileNameWithoutExt = path5.basename(mediaPath, path5.extname(mediaPath));
1186
+ outputFileName = `${fileNameWithoutExt}.${imageFormat}`;
1187
+ }
1188
+ const outputPath = path5.join(outputSubDir, outputFileName);
1189
+ const stats = await getStats(mediaPath);
1190
+ if (imageProcessor.canProcess(mediaPath)) {
1191
+ const metadata = await imageProcessor.getMetadata(mediaPath);
1192
+ const originalWidth = metadata.width;
1193
+ const result = await imageProcessor.process(mediaPath, outputPath, {
1194
+ format: imageFormat,
1195
+ quality: imageQuality
1196
+ });
1197
+ const sizeVariants = [];
1198
+ for (const sizeConfig of imageSizes) {
1199
+ if (sizeConfig.width === null || sizeConfig.width >= originalWidth) {
1200
+ continue;
1201
+ }
1202
+ try {
1203
+ let sizeOutputFileName;
1204
+ if (useHash) {
1205
+ sizeOutputFileName = `${shortContentHash}-${sizeConfig.suffix}.${imageFormat}`;
1206
+ } else {
1207
+ const fileNameWithoutExt = path5.basename(mediaPath, path5.extname(mediaPath));
1208
+ sizeOutputFileName = `${fileNameWithoutExt}-${sizeConfig.suffix}.${imageFormat}`;
1209
+ }
1210
+ const sizeOutputPath = path5.join(outputSubDir, sizeOutputFileName);
1211
+ const sizeResult = await imageProcessor.process(mediaPath, sizeOutputPath, {
1212
+ width: sizeConfig.width,
1213
+ height: sizeConfig.height ?? void 0,
1214
+ format: imageFormat,
1215
+ quality: imageQuality
1216
+ });
1217
+ sizeVariants.push({
1218
+ suffix: sizeConfig.suffix,
1219
+ outputPath: normalizePath(path5.relative(state.outputDir, sizeResult.outputPath)),
1220
+ width: sizeResult.width,
1221
+ height: sizeResult.height,
1222
+ size: sizeResult.size
1223
+ });
1224
+ } catch (sizeError) {
1225
+ const sizeErrorMsg = sizeError instanceof Error ? sizeError.message : String(sizeError);
1226
+ this.log(`Failed to generate ${sizeConfig.suffix} size for ${fileName}: ${sizeErrorMsg}`, "warn");
1227
+ }
1228
+ }
1229
+ const processedMedia = {
1230
+ originalPath: relativePath2,
1231
+ outputPath: normalizePath(path5.relative(state.outputDir, result.outputPath)),
1232
+ fileName,
1233
+ type: "image",
1234
+ metadata: {
1235
+ width: result.width,
1236
+ height: result.height,
1237
+ format: result.format,
1238
+ size: result.size,
1239
+ originalSize: stats.size,
1240
+ hash: contentHash
1241
+ },
1242
+ sizes: sizeVariants.length > 0 ? sizeVariants : void 0
1243
+ };
1244
+ state.media.push(processedMedia);
1245
+ state.mediaPathMap.set(relativePath2, processedMedia);
1246
+ } else {
1247
+ const ext = path5.extname(mediaPath);
1248
+ let copyOutputPath;
1249
+ if (useHash) {
1250
+ const copyFileName = `${shortContentHash}${ext}`;
1251
+ copyOutputPath = path5.join(outputSubDir, copyFileName);
1252
+ } else {
1253
+ copyOutputPath = path5.join(outputSubDir, fileName);
1254
+ }
1255
+ await imageProcessor.copy(mediaPath, copyOutputPath);
1256
+ const copiedMedia = {
1257
+ originalPath: relativePath2,
1258
+ outputPath: normalizePath(path5.relative(state.outputDir, copyOutputPath)),
1259
+ fileName,
1260
+ type: "media",
1261
+ metadata: {
1262
+ size: stats.size,
1263
+ originalSize: stats.size,
1264
+ hash: contentHash
1265
+ }
1266
+ };
1267
+ state.media.push(copiedMedia);
1268
+ state.mediaPathMap.set(relativePath2, copiedMedia);
1269
+ }
1270
+ } catch (error) {
1271
+ const errorMessage = error instanceof Error ? error.message : String(error);
1272
+ this.issues.addMediaProcessingError({
1273
+ filePath: mediaPath,
1274
+ mediaPath,
1275
+ operation: "read",
1276
+ errorMessage
1277
+ });
1278
+ }
1279
+ }
1280
+ this.log(`Processed ${state.media.length} media files`, "info");
1281
+ }
1282
+ // --------------------------------------------------------------------------
1283
+ // Markdown Processing
1284
+ // --------------------------------------------------------------------------
1285
+ async processMarkdownFiles(state) {
1286
+ this.log("Processing markdown files...", "info");
1287
+ const mdFiles = await findMarkdownFiles(state.inputDir);
1288
+ this.log(`Found ${mdFiles.length} markdown files`, "info");
1289
+ const parsedFiles = [];
1290
+ for (const filePath of mdFiles) {
1291
+ try {
1292
+ const content = await readText(filePath);
1293
+ const stats = await getStats(filePath);
1294
+ parsedFiles.push({ filePath, content, stats });
1295
+ } catch (error) {
1296
+ const errorMessage = error instanceof Error ? error.message : String(error);
1297
+ this.log(`Failed to read ${filePath}: ${errorMessage}`, "warn");
1298
+ }
1299
+ }
1300
+ for (const { filePath, content, stats } of parsedFiles) {
1301
+ try {
1302
+ const post = await this.parseMarkdownFile(state, filePath, content, stats);
1303
+ if (post) {
1304
+ state.posts.push(post);
1305
+ }
1306
+ } catch (error) {
1307
+ const errorMessage = error instanceof Error ? error.message : String(error);
1308
+ this.log(`Failed to process ${filePath}: ${errorMessage}`, "error");
1309
+ }
1310
+ }
1311
+ const wikiLinksEnabled = this.config.pipeline?.wikiLinks !== false;
1312
+ if (wikiLinksEnabled && state.posts.length > 0) {
1313
+ await this.renderPostsWithLinks(state);
1314
+ }
1315
+ this.log(`Processed ${state.posts.length} posts`, "info");
1316
+ }
1317
+ /**
1318
+ * Phase 1: Parse a markdown file — extract all metadata but use basic HTML.
1319
+ * The markdown source is stored for Phase 2 re-rendering.
1320
+ */
1321
+ async parseMarkdownFile(state, filePath, content, stats) {
1322
+ const relativePath2 = normalizePath(path5.relative(state.inputDir, filePath));
1323
+ const fileName = getFileName(filePath);
1324
+ const pipelineConfig = this.config.pipeline ?? {};
1325
+ const result = await processMarkdown(content, {
1326
+ gfm: pipelineConfig.gfm ?? true,
1327
+ allowRawHtml: pipelineConfig.allowRawHtml ?? true
1328
+ });
1329
+ const isPublished = result.frontmatter.published !== false && result.frontmatter.draft !== true;
1330
+ if (!isPublished && !this.config.content?.processAllFiles) {
1331
+ this.log(`Skipping unpublished: ${relativePath2}`, "debug");
1332
+ return null;
1333
+ }
1334
+ const parentFolder = path5.dirname(relativePath2);
1335
+ const scope = this.config.content?.slugScope === "top-folder" ? relativePath2.split("/")[0] : void 0;
1336
+ const slugInfo = state.slugManager.reserve(filePath, {
1337
+ fileName,
1338
+ parentFolder: parentFolder !== "." ? parentFolder : void 0,
1339
+ frontmatterSlug: result.frontmatter.slug
1340
+ }, scope);
1341
+ if (slugInfo.wasModified) {
1342
+ this.issues.addSlugConflict({
1343
+ filePath: relativePath2,
1344
+ originalSlug: slugInfo.originalSlug,
1345
+ finalSlug: slugInfo.slug,
1346
+ conflictingFiles: []
1347
+ });
1348
+ }
1349
+ const plainText = mdastToText(result.mdast);
1350
+ const headings = extractHeadings(result.mdast);
1351
+ const toc = buildToc(headings);
1352
+ const firstParagraph = extractFirstParagraph(result.mdast);
1353
+ const wordCount = countWords(plainText);
1354
+ const hash = hashContent(content);
1355
+ const title = result.frontmatter.title || (headings.length > 0 && headings[0].depth === 1 ? headings[0].text : fileName);
1356
+ const cover = this.resolveCover(
1357
+ result.frontmatter,
1358
+ relativePath2,
1359
+ state.mediaPathMap
1360
+ );
1361
+ const post = {
1362
+ hash,
1363
+ slug: slugInfo.slug,
1364
+ fileName,
1365
+ title,
1366
+ content: result.html,
1367
+ markdown: result.markdown,
1368
+ frontmatter: result.frontmatter,
1369
+ plainText,
1370
+ excerpt: firstParagraph,
1371
+ wordCount,
1372
+ toc,
1373
+ originalPath: relativePath2,
1374
+ metadata: {
1375
+ createdAt: stats.created.toISOString(),
1376
+ modifiedAt: stats.modified.toISOString(),
1377
+ processedAt: (/* @__PURE__ */ new Date()).toISOString()
1378
+ }
1379
+ };
1380
+ if (cover) {
1381
+ post.cover = cover;
1382
+ }
1383
+ return post;
1384
+ }
1385
+ /**
1386
+ * Phase 2: Build file maps from all parsed posts, then re-render each
1387
+ * file's markdown through a pipeline that includes wiki-link resolution.
1388
+ */
1389
+ async renderPostsWithLinks(state) {
1390
+ this.log("Phase 2: Rendering with wiki-link resolution...", "info");
1391
+ if (!remarkObsidianLink) {
1392
+ const [rolMod, rahMod, relMod] = await Promise.all([
1393
+ import('remark-obsidian-link'),
1394
+ import('rehype-autolink-headings'),
1395
+ import('rehype-external-links')
1396
+ ]);
1397
+ remarkObsidianLink = rolMod.remarkObsidianLink;
1398
+ rehypeAutolinkHeadings = rahMod.default ?? rahMod;
1399
+ rehypeExternalLinks = relMod.default ?? relMod;
1400
+ }
1401
+ const fileMaps = buildFileMaps(state.posts);
1402
+ const filesByName = /* @__PURE__ */ new Map();
1403
+ for (const [name, posts] of fileMaps.byFileName) {
1404
+ if (posts.length > 0) {
1405
+ filesByName.set(name, posts[0]);
1406
+ }
1407
+ }
1408
+ const urlPrefix = this.config.content?.notePathPrefix ?? "/content";
1409
+ const pipelineConfig = this.config.pipeline ?? {};
1410
+ const { unified: unified2 } = await import('unified');
1411
+ const remarkParse2 = (await import('remark-parse')).default;
1412
+ const remarkGfm2 = (await import('remark-gfm')).default;
1413
+ const remarkRehype2 = (await import('remark-rehype')).default;
1414
+ const rehypeRaw2 = (await import('rehype-raw')).default;
1415
+ const rehypeSlug2 = (await import('rehype-slug')).default;
1416
+ const rehypeStringify2 = (await import('rehype-stringify')).default;
1417
+ for (let i = 0; i < state.posts.length; i++) {
1418
+ const post = state.posts[i];
1419
+ try {
1420
+ const toLink = createCustomToLink({
1421
+ filesBySlug: fileMaps.bySlug,
1422
+ filesByName,
1423
+ filesByPath: fileMaps.byPath,
1424
+ filesByAlias: fileMaps.byAlias,
1425
+ urlPrefix,
1426
+ currentFile: post,
1427
+ issueCollector: this.issues
1428
+ });
1429
+ let pipeline = unified2().use(remarkParse2);
1430
+ if (pipelineConfig.gfm !== false) {
1431
+ pipeline = pipeline.use(remarkGfm2);
1432
+ }
1433
+ pipeline = pipeline.use(remarkObsidianLink, { toLink }).use(remarkMarkdownLinkResolver, {
1434
+ filesByPath: fileMaps.byPath,
1435
+ urlPrefix,
1436
+ currentFilePath: post.originalPath
1437
+ }).use(remarkRehype2, {
1438
+ allowDangerousHtml: pipelineConfig.allowRawHtml !== false
1439
+ });
1440
+ if (pipelineConfig.allowRawHtml !== false) {
1441
+ pipeline = pipeline.use(rehypeRaw2);
1442
+ }
1443
+ pipeline = pipeline.use(rehypeSlug2).use(rehypeExternalLinks, { target: "_blank", rel: ["nofollow", "noopener", "noreferrer"] }).use(rehypeAutolinkHeadings, { behavior: "wrap" }).use(rehypeStringify2);
1444
+ const file = await pipeline.process(post.markdown);
1445
+ state.posts[i].content = String(file);
1446
+ } catch (error) {
1447
+ const errorMessage = error instanceof Error ? error.message : String(error);
1448
+ this.log(
1449
+ `Failed to render links for ${post.originalPath}: ${errorMessage}`,
1450
+ "warn"
1451
+ );
1452
+ }
1453
+ }
1454
+ }
1455
+ /**
1456
+ * Build absolute URL from relative path using domain config
1457
+ * URL structure: {domain}/_shared/medias/{filename}
1458
+ */
1459
+ buildAbsoluteUrl(relativePath2) {
1460
+ const domain = this.config.media?.domain;
1461
+ if (!domain) {
1462
+ return void 0;
1463
+ }
1464
+ const filename = relativePath2.split("/").pop();
1465
+ if (!filename) {
1466
+ return void 0;
1467
+ }
1468
+ const baseUrl = domain.replace(/\/$/, "");
1469
+ return `${baseUrl}/_shared/medias/${filename}`;
1470
+ }
1471
+ /**
1472
+ * Resolve cover image from frontmatter
1473
+ * Returns PostCover if found, PostCoverError if specified but not found, undefined if not specified
1474
+ */
1475
+ resolveCover(frontmatter, postPath, mediaPathMap) {
1476
+ const coverFields = ["cover", "image", "thumbnail", "coverImage", "cover_image"];
1477
+ let coverValue;
1478
+ for (const field of coverFields) {
1479
+ const value = frontmatter[field];
1480
+ if (typeof value === "string" && value.length > 0) {
1481
+ coverValue = value;
1482
+ break;
1483
+ }
1484
+ }
1485
+ if (!coverValue) {
1486
+ return void 0;
1487
+ }
1488
+ const postDir = path5.dirname(postPath);
1489
+ const pathsToTry = [
1490
+ normalizePath(coverValue),
1491
+ // Direct path from root
1492
+ normalizePath(path5.join(postDir, coverValue)),
1493
+ // Relative to post directory
1494
+ normalizePath(coverValue.replace(/^\//, ""))
1495
+ // Remove leading slash if present
1496
+ ];
1497
+ let mediaInfo;
1498
+ for (const tryPath of pathsToTry) {
1499
+ mediaInfo = mediaPathMap.get(tryPath);
1500
+ if (mediaInfo) {
1501
+ break;
1502
+ }
1503
+ }
1504
+ if (!mediaInfo) {
1505
+ this.log(`Cover image not found for "${postPath}": "${coverValue}"`, "warn");
1506
+ return {
1507
+ original: coverValue,
1508
+ error: `Cover image not found: "${coverValue}"`
1509
+ };
1510
+ }
1511
+ const cover = {
1512
+ original: coverValue,
1513
+ path: mediaInfo.outputPath,
1514
+ url: this.buildAbsoluteUrl(mediaInfo.outputPath),
1515
+ hash: mediaInfo.metadata?.hash,
1516
+ width: mediaInfo.metadata?.width,
1517
+ height: mediaInfo.metadata?.height,
1518
+ sizes: mediaInfo.sizes?.map((s) => ({
1519
+ suffix: s.suffix,
1520
+ path: s.outputPath,
1521
+ url: this.buildAbsoluteUrl(s.outputPath),
1522
+ width: s.width,
1523
+ height: s.height
1524
+ }))
1525
+ };
1526
+ return cover;
1527
+ }
1528
+ // --------------------------------------------------------------------------
1529
+ // Embeddings
1530
+ // --------------------------------------------------------------------------
1531
+ async generateEmbeddings(state) {
1532
+ const textEmbedder = this.pluginManager.getPluginByKey("textEmbedder");
1533
+ if (!textEmbedder || textEmbedder.dimensions === 0) {
1534
+ this.log("No text embedder plugin, skipping embeddings", "debug");
1535
+ return;
1536
+ }
1537
+ this.log("Generating text embeddings...", "info");
1538
+ const textEmbeddingsCache = this.config.cache?.textEmbeddings;
1539
+ try {
1540
+ const uncachedPosts = [];
1541
+ const cachedCount = { hits: 0, misses: 0 };
1542
+ for (let i = 0; i < state.posts.length; i++) {
1543
+ const post = state.posts[i];
1544
+ if (!post) continue;
1545
+ const cachedEmbedding = textEmbeddingsCache?.get(post.hash);
1546
+ if (cachedEmbedding) {
1547
+ post.embedding = cachedEmbedding;
1548
+ cachedCount.hits++;
1549
+ state.cacheStats.textEmbeddingCacheHits++;
1550
+ } else {
1551
+ uncachedPosts.push({ index: i, text: post.plainText });
1552
+ cachedCount.misses++;
1553
+ state.cacheStats.textEmbeddingCacheMisses++;
1554
+ }
1555
+ }
1556
+ if (uncachedPosts.length > 0) {
1557
+ const texts = uncachedPosts.map((p) => p.text);
1558
+ const embeddings = await textEmbedder.batchEmbed(texts);
1559
+ for (let i = 0; i < uncachedPosts.length; i++) {
1560
+ const postInfo = uncachedPosts[i];
1561
+ if (!postInfo) continue;
1562
+ const post = state.posts[postInfo.index];
1563
+ if (post && embeddings[i]) {
1564
+ post.embedding = embeddings[i];
1565
+ }
1566
+ }
1567
+ this.log(`Generated ${embeddings.length} text embeddings (${cachedCount.hits} from cache)`, "info");
1568
+ } else {
1569
+ this.log(`All ${cachedCount.hits} text embeddings loaded from cache`, "info");
1570
+ }
1571
+ } catch (error) {
1572
+ const errorMessage = error instanceof Error ? error.message : String(error);
1573
+ this.issues.addEmbeddingError({
1574
+ embeddingType: "text",
1575
+ operation: "batch",
1576
+ errorMessage
1577
+ });
1578
+ }
1579
+ const imageEmbedder = this.pluginManager.getPluginByKey("imageEmbedder");
1580
+ if (imageEmbedder && imageEmbedder.dimensions > 0 && state.media.length > 0) {
1581
+ this.log("Generating image embeddings...", "info");
1582
+ const imageEmbeddingsCache = this.config.cache?.imageEmbeddings;
1583
+ let cachedCount = 0;
1584
+ let generatedCount = 0;
1585
+ for (const media of state.media) {
1586
+ if (media.type === "image") {
1587
+ const mediaHash = media.metadata?.hash;
1588
+ const cachedEmbedding = mediaHash ? imageEmbeddingsCache?.get(mediaHash) : void 0;
1589
+ if (cachedEmbedding) {
1590
+ media.embedding = cachedEmbedding;
1591
+ cachedCount++;
1592
+ state.cacheStats.imageEmbeddingCacheHits++;
1593
+ } else {
1594
+ state.cacheStats.imageEmbeddingCacheMisses++;
1595
+ try {
1596
+ const mediaPath = path5.join(state.outputDir, media.outputPath);
1597
+ const embedding = await imageEmbedder.embedFile(mediaPath);
1598
+ media.embedding = embedding;
1599
+ generatedCount++;
1600
+ } catch (error) {
1601
+ const errorMessage = error instanceof Error ? error.message : String(error);
1602
+ this.issues.addEmbeddingError({
1603
+ filePath: media.originalPath,
1604
+ embeddingType: "image",
1605
+ operation: "embed",
1606
+ errorMessage
1607
+ });
1608
+ }
1609
+ }
1610
+ }
1611
+ }
1612
+ this.log(`Image embeddings: ${generatedCount} generated, ${cachedCount} from cache`, "info");
1613
+ }
1614
+ }
1615
+ // --------------------------------------------------------------------------
1616
+ // Similarity
1617
+ // --------------------------------------------------------------------------
1618
+ async generateSimilarity(state) {
1619
+ const similarity = this.pluginManager.getPluginByKey("similarity");
1620
+ if (!similarity) {
1621
+ this.log("No similarity plugin, skipping similarity computation", "debug");
1622
+ return;
1623
+ }
1624
+ this.log("Generating similarity data...", "info");
1625
+ try {
1626
+ const result = await similarity.generateSimilarityMap(state.posts);
1627
+ const similarityPath = path5.join(state.outputDir, "similarity.json");
1628
+ await writeJson(similarityPath, {
1629
+ pairwiseScores: Object.fromEntries(result.pairwiseScores),
1630
+ similarPosts: Object.fromEntries(result.similarPosts),
1631
+ metadata: result.metadata
1632
+ });
1633
+ this.log(
1634
+ `Generated similarity data: ${result.metadata.pairCount} pairs`,
1635
+ "info"
1636
+ );
1637
+ } catch (error) {
1638
+ const errorMessage = error instanceof Error ? error.message : String(error);
1639
+ this.log(`Failed to generate similarity: ${errorMessage}`, "error");
1640
+ }
1641
+ }
1642
+ // --------------------------------------------------------------------------
1643
+ // Database
1644
+ // --------------------------------------------------------------------------
1645
+ async buildDatabase(state) {
1646
+ const database = this.pluginManager.getPluginByKey("database");
1647
+ if (!database) {
1648
+ this.log("No database plugin, skipping database generation", "debug");
1649
+ return;
1650
+ }
1651
+ this.log("Building database...", "info");
1652
+ try {
1653
+ const result = await database.build({
1654
+ posts: state.posts,
1655
+ media: state.media
1656
+ });
1657
+ this.log(
1658
+ `Database built: ${result.tables.length} tables, ${result.hasVectorSearch ? "with" : "without"} vector search`,
1659
+ "info"
1660
+ );
1661
+ } catch (error) {
1662
+ const errorMessage = error instanceof Error ? error.message : String(error);
1663
+ this.log(`Failed to build database: ${errorMessage}`, "error");
1664
+ }
1665
+ }
1666
+ // --------------------------------------------------------------------------
1667
+ // Output
1668
+ // --------------------------------------------------------------------------
1669
+ async writeOutput(state) {
1670
+ const { outputDir, posts, media } = state;
1671
+ this.log("Writing output files...", "info");
1672
+ const postsPath = path5.join(outputDir, "posts.json");
1673
+ const mediaPath = path5.join(outputDir, "medias.json");
1674
+ const slugMapPath = path5.join(outputDir, "posts-slug-map.json");
1675
+ const pathMapPath = path5.join(outputDir, "posts-path-map.json");
1676
+ const issuesPath = path5.join(outputDir, "processor-issues.json");
1677
+ const slugMap = {};
1678
+ const pathMap = {};
1679
+ for (const post of posts) {
1680
+ slugMap[post.slug] = post.hash;
1681
+ pathMap[post.originalPath] = post.hash;
1682
+ }
1683
+ await Promise.all([
1684
+ writeJson(postsPath, posts),
1685
+ writeJson(mediaPath, media),
1686
+ writeJson(slugMapPath, slugMap),
1687
+ writeJson(pathMapPath, pathMap),
1688
+ writeJson(issuesPath, this.issues.generateReport())
1689
+ ]);
1690
+ this.log(`Output written to ${outputDir}`, "info");
1691
+ return {
1692
+ posts: postsPath,
1693
+ media: mediaPath,
1694
+ slugMap: slugMapPath,
1695
+ pathMap: pathMapPath,
1696
+ issues: issuesPath
1697
+ };
1698
+ }
1699
+ };
1700
+ var createProcessor = async (config) => {
1701
+ const processor = new Processor({ config });
1702
+ await processor.initialize();
1703
+ return processor;
1704
+ };
1705
+ var processFolder = async (config) => {
1706
+ const processor = await createProcessor(config);
1707
+ try {
1708
+ return await processor.process();
1709
+ } finally {
1710
+ await processor.dispose();
1711
+ }
1712
+ };
1713
+ var processVault = processFolder;
1714
+
1715
+ export { DEFAULT_PIPELINE_OPTIONS, IssueCollector, Processor, SlugManager, buildFileMaps, buildToc, calculateSummary, combineHashes, copyFile, countWords, createBasePipeline, createCustomToLink, createPathVariations, createProcessor, ensureDir, estimateReadingTime, extractFirstParagraph, extractHeadings, fileExists, filterIssues, findByFileNameCaseInsensitive, findFiles, findMarkdownFiles, generateBaseSlug, generateIssueReport, getContentStats, getExtension, getFileName, getStats, hashBuffer, hashContent, hastToHtml, mdastToHast, mdastToText, normalizePath, parseFrontmatter, parseToMdast, processFolder, processMarkdown, processVault, readJson, readText, relativePath, remarkMarkdownLinkResolver, resolveFile, resolveFromCandidates, resolveMarkdownLink, resolveMarkdownLinkPath, resolveSlugConflict, resolveWikilink, shortHash, toSlug, wikiToObsidian, wikilinkToMarkdownLink, writeJson, writeText };