folderblog 0.0.1 → 0.0.3

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