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