dead-links 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -10
- package/dist/index.js +676 -22
- package/dist/index.js.map +1 -1
- package/package.json +12 -11
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/vault-loader.ts","../src/core/link-parser.ts","../src/core/analyzer.ts","../src/core/alias-resolver.ts","../src/core/graph-builder.ts","../src/utils/engine.ts","../src/utils/config-loader.ts","../src/reporters/terminal-reporter.ts","../src/reporters/json-reporter.ts","../src/utils/markdown-report.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { writeFile } from 'node:fs/promises';\nimport ora from 'ora';\nimport { DeadLinksEngine } from './utils/engine.js';\nimport { ConfigLoader } from './utils/config-loader.js';\nimport { TerminalReporter } from './reporters/terminal-reporter.js';\nimport { JsonReporter } from './reporters/json-reporter.js';\nimport { generateMarkdownReport } from './utils/markdown-report.js';\nimport type { BrokenLink, Note } from './types/index.js';\n\nconst program = new Command();\n\nprogram\n .name('dead-links')\n .description('Scan Obsidian vaults for broken links, orphan notes, and connection graphs')\n .version('0.1.3');\n\nasync function loadConfigAndAnalyze(\n vaultPath: string,\n cliOptions: Record<string, unknown>,\n) {\n const configLoader = new ConfigLoader();\n const fileConfig = await configLoader.loadConfig(vaultPath);\n const config = configLoader.mergeConfig(fileConfig, cliOptions);\n\n const engine = new DeadLinksEngine();\n return engine.analyze(vaultPath, config);\n}\n\nfunction printBrokenVerbose(brokenLinks: BrokenLink[]) {\n const grouped = new Map<string, BrokenLink[]>();\n for (const b of brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n\n for (const [source, links] of grouped) {\n for (const link of links) {\n console.log(` ${source} → ${link.link.target} (line ${link.link.line})`);\n }\n }\n}\n\nfunction printOrphansVerbose(orphanNotes: Note[]) {\n for (const note of orphanNotes) {\n console.log(` ${note.relativePath}`);\n }\n}\n\nprogram\n .command('scan')\n .description('Scan the vault for all issues')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--format <type>', 'Output format: text, json, markdown', 'text')\n .option('--output <file>', 'Output file path')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--attachments', 'Also check embedded attachments (images, PDFs, etc.)')\n .option('--ignore-folders <folders...>', 'Exclude folders from orphan report')\n .option('--quiet', 'Only show issues, no stats or summary')\n .option('--no-color', 'Disable colored output')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (options.format === 'json') {\n const reporter = new JsonReporter();\n const output = reporter.report(report);\n if (options.output) {\n await writeFile(options.output, output);\n console.log(`JSON report written to ${options.output}`);\n } else {\n console.log(output);\n }\n } else if (options.format === 'markdown') {\n const output = generateMarkdownReport(report);\n if (options.output) {\n await writeFile(options.output, output);\n console.log(`Markdown report written to ${options.output}`);\n } else {\n console.log(output);\n }\n } else {\n const reporter = new TerminalReporter(options.ignoreFolders || [], options.quiet || false);\n console.log(reporter.report(report));\n }\n\n process.exit(report.stats.brokenCount > 0 || report.stats.orphanCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('broken')\n .description('Show only broken links')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--attachments', 'Also check embedded attachments (images, PDFs, etc.)')\n .option('--verbose', 'Show line numbers and full details')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (report.brokenLinks.length === 0) {\n console.log('No broken links found.');\n } else {\n console.log(`\\nBroken links (${report.brokenLinks.length})\\n`);\n if (options.verbose) {\n printBrokenVerbose(report.brokenLinks);\n } else {\n const grouped = new Map<string, BrokenLink[]>();\n for (const b of report.brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n for (const [source, links] of grouped) {\n const targets = links.map((l: BrokenLink) => l.link.target).join(', ');\n console.log(` ${source} → ${targets}`);\n }\n }\n console.log();\n }\n\n process.exit(report.stats.brokenCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('orphans')\n .description('Show only orphan notes')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--ignore-folders <folders...>', 'Exclude folders from report')\n .option('--verbose', 'Show full list of orphan notes')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (report.orphanNotes.length === 0) {\n console.log('No orphan notes found.');\n } else {\n console.log(`\\nOrphan notes (${report.orphanNotes.length})\\n`);\n if (options.verbose) {\n printOrphansVerbose(report.orphanNotes);\n } else {\n const ignoreFolders = options.ignoreFolders || [];\n const byFolder = new Map<string, number>();\n for (const note of report.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n if (ignoreFolders.some((f: string) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n for (const [folder, count] of [...byFolder.entries()].sort((a, b) => b[1] - a[1])) {\n console.log(` ${folder} ${count}`);\n }\n }\n console.log();\n }\n\n process.exit(report.stats.orphanCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('graph')\n .description('Show connection graph')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--format <type>', 'Output format: text, json', 'text')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (options.format === 'json') {\n console.log(JSON.stringify(report.graph, null, 2));\n } else {\n console.log(\n `Graph: ${report.stats.totalNotes} notes, ${report.stats.totalLinks} links (${report.duration}ms)\\n`,\n );\n\n const topConnected = [...report.graph]\n .sort((a, b) => b.incoming.length - a.incoming.length)\n .slice(0, 10)\n .filter((n) => n.incoming.length > 0);\n\n if (topConnected.length > 0) {\n console.log('Most linked-to notes:');\n for (const node of topConnected) {\n console.log(\n ` ${node.note.relativePath} (${node.incoming.length} incoming, ${node.outgoing.length} outgoing)`,\n );\n }\n }\n }\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('report')\n .description('Generate a markdown report')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--output <file>', 'Output file path', 'dead-links-report.md')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n const markdown = generateMarkdownReport(report);\n await writeFile(options.output, markdown);\n console.log(`Report written to ${options.output}`);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram.parse();","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport fg from 'fast-glob';\nimport matter from 'gray-matter';\nimport type { Note } from '../types/index.js';\n\nconst DEFAULT_IGNORE = [\n '**/node_modules/**',\n '**/.git/**',\n '**/.obsidian/**',\n];\n\nexport async function loadVault(\n vaultPath: string,\n customIgnore: string[] = [],\n): Promise<Note[]> {\n const absolutePath = resolve(vaultPath);\n\n const files = await fg(['**/*.md', '**/*.canvas'], {\n cwd: absolutePath,\n ignore: [...DEFAULT_IGNORE, ...customIgnore],\n });\n\n const notes: Note[] = [];\n\n for (const file of files) {\n const fullPath = resolve(absolutePath, file);\n const content = await readFile(fullPath, 'utf-8');\n const parsed = matter(content);\n\n const aliases: string[] = [];\n if (parsed.data.aliases) {\n if (Array.isArray(parsed.data.aliases)) {\n aliases.push(...parsed.data.aliases.map(String));\n } else if (typeof parsed.data.aliases === 'string') {\n aliases.push(parsed.data.aliases);\n }\n }\n\n notes.push({\n path: fullPath,\n relativePath: file,\n content: parsed.content,\n aliases,\n links: [],\n });\n }\n\n return notes;\n}\n\nexport function resolveFileName(notePath: string): string {\n const base = notePath.replace(/\\.(md|canvas)$/, '');\n const parts = base.split('/');\n return parts[parts.length - 1]!;\n}\n","import type { Link } from '../types/index.js';\n\nconst wikiLinkRegex = /!?\\[\\[([^\\]]+)\\]\\]/g;\nconst markdownLinkRegex = /\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\n\nexport function parseLinks(content: string): Link[] {\n const links: Link[] = [];\n const lines = content.split('\\n');\n\n for (let lineNum = 0; lineNum < lines.length; lineNum++) {\n const line = lines[lineNum]!;\n\n for (const match of line.matchAll(wikiLinkRegex)) {\n const raw = match[0]!;\n const inner = match[1]!;\n const isEmbed = raw.startsWith('!');\n\n const link = parseWikiLink(inner, raw, isEmbed, lineNum + 1);\n links.push(link);\n }\n\n for (const match of line.matchAll(markdownLinkRegex)) {\n const raw = match[0]!;\n const target = match[2]!;\n\n if (target.startsWith('#')) continue;\n\n const link: Link = {\n raw,\n target: target.split('#')[0]!.split('|')[0]!,\n heading: target.includes('#') ? target.split('#')[1] : undefined,\n type: 'markdown',\n line: lineNum + 1,\n };\n links.push(link);\n }\n }\n\n return links;\n}\n\nfunction parseWikiLink(inner: string, raw: string, isEmbed: boolean, line: number): Link {\n const [targetPart, aliasPart] = inner.split('|');\n const [target, ...rest] = targetPart!.split('#');\n\n let heading: string | undefined;\n let blockId: string | undefined;\n\n if (rest.length > 0) {\n const remainder = rest.join('#');\n if (remainder.startsWith('^')) {\n blockId = remainder.slice(1);\n } else {\n heading = remainder;\n }\n }\n\n return {\n raw,\n target: target!.trim(),\n alias: aliasPart?.trim(),\n heading,\n blockId,\n type: isEmbed ? 'embed' : 'wiki',\n line,\n };\n}\n","import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { Note, GraphNode, BrokenLink, AnalysisReport } from '../types/index.js';\nimport { AliasResolver } from './alias-resolver.js';\nimport { buildGraph } from './graph-builder.js';\n\nconst ATTACHMENT_EXTENSIONS = new Set([\n 'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp', 'ico',\n 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',\n 'mp3', 'mp4', 'wav', 'avi', 'mov', 'webm',\n 'zip', 'rar', '7z', 'tar', 'gz',\n 'exe', 'dmg', 'apk',\n 'css', 'js', 'ts', 'html',\n 'txt', 'csv', 'log',\n 'woff', 'woff2', 'ttf', 'eot',\n]);\n\nfunction isAttachment(target: string): boolean {\n const ext = target.split('.').pop()?.toLowerCase();\n return ext ? ATTACHMENT_EXTENSIONS.has(ext) : false;\n}\n\nexport function analyze(\n notes: Note[],\n vaultPath: string,\n checkAttachments = false,\n): AnalysisReport {\n const resolver = new AliasResolver(notes);\n const graph = buildGraph(notes, resolver);\n\n const brokenLinks: BrokenLink[] = [];\n const orphanNotes: Note[] = [];\n\n for (const node of graph) {\n if (node.incoming.length === 0 && node.outgoing.length === 0) {\n orphanNotes.push(node.note);\n }\n\n for (const link of node.note.links) {\n if (link.type === 'markdown' && !link.target.endsWith('.md')) {\n continue;\n }\n\n if (!checkAttachments && isAttachment(link.target)) {\n continue;\n }\n\n const resolvedPath = resolver.resolve(link.target);\n\n if (resolvedPath === node.note.path) {\n continue;\n }\n\n if (!resolvedPath) {\n brokenLinks.push({\n source: node.note.relativePath,\n link,\n reason: `Target \"${link.target}\" not found in vault`,\n });\n } else if (link.type === 'embed' || link.type === 'markdown') {\n if (!existsSync(resolvedPath)) {\n brokenLinks.push({\n source: node.note.relativePath,\n link,\n reason: `File \"${link.target}\" does not exist`,\n });\n }\n }\n }\n }\n\n const connectedComponents = countConnectedComponents(graph);\n const totalLinks = notes.reduce((sum, n) => sum + n.links.length, 0);\n\n return {\n brokenLinks,\n orphanNotes,\n graph,\n stats: {\n totalNotes: notes.length,\n totalLinks,\n brokenCount: brokenLinks.length,\n orphanCount: orphanNotes.length,\n connectedComponents,\n },\n };\n}\n\nfunction countConnectedComponents(graph: GraphNode[]): number {\n if (graph.length === 0) return 0;\n\n const visited = new Set<string>();\n let components = 0;\n\n const adjacency = new Map<string, string[]>();\n for (const node of graph) {\n adjacency.set(node.note.path, [...node.incoming, ...node.outgoing]);\n }\n\n for (const node of graph) {\n if (visited.has(node.note.path)) continue;\n\n components++;\n const stack = [node.note.path];\n\n while (stack.length > 0) {\n const current = stack.pop()!;\n if (visited.has(current)) continue;\n visited.add(current);\n\n const neighbors = adjacency.get(current) || [];\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n stack.push(neighbor);\n }\n }\n }\n }\n\n return components;\n}\n","import type { Note } from '../types/index.js';\n\nexport class AliasResolver {\n private aliasToNote: Map<string, string> = new Map();\n private fileNameToNote: Map<string, string> = new Map();\n private basenameToNote: Map<string, string> = new Map();\n\n constructor(notes: Note[]) {\n for (const note of notes) {\n const fileName = note.relativePath.replace(/\\.(md|canvas)$/, '');\n const basename = fileName.split('/').pop()!;\n\n this.fileNameToNote.set(fileName.toLowerCase(), note.path);\n this.basenameToNote.set(basename.toLowerCase(), note.path);\n\n try {\n this.fileNameToNote.set(decodeURIComponent(fileName).toLowerCase(), note.path);\n this.basenameToNote.set(decodeURIComponent(basename).toLowerCase(), note.path);\n } catch {\n // skip malformed URIs\n }\n\n for (const alias of note.aliases) {\n this.aliasToNote.set(alias.toLowerCase(), note.path);\n }\n }\n }\n\n resolve(target: string): string | null {\n const normalized = target.toLowerCase().trim();\n\n if (this.aliasToNote.has(normalized)) {\n return this.aliasToNote.get(normalized)!;\n }\n\n if (this.fileNameToNote.has(normalized)) {\n return this.fileNameToNote.get(normalized)!;\n }\n\n if (this.basenameToNote.has(normalized)) {\n return this.basenameToNote.get(normalized)!;\n }\n\n const decoded = (() => {\n try {\n return decodeURIComponent(normalized);\n } catch {\n return normalized;\n }\n })();\n\n if (this.aliasToNote.has(decoded)) {\n return this.aliasToNote.get(decoded)!;\n }\n if (this.fileNameToNote.has(decoded)) {\n return this.fileNameToNote.get(decoded)!;\n }\n if (this.basenameToNote.has(decoded)) {\n return this.basenameToNote.get(decoded)!;\n }\n\n return null;\n }\n}\n","import type { Note, GraphNode, Link } from '../types/index.js';\nimport { AliasResolver } from './alias-resolver.js';\n\nexport function buildGraph(notes: Note[], resolver: AliasResolver): GraphNode[] {\n const notePathMap = new Map<string, GraphNode>();\n\n for (const note of notes) {\n notePathMap.set(note.path, {\n note,\n incoming: [],\n outgoing: [],\n });\n }\n\n for (const note of notes) {\n const node = notePathMap.get(note.path)!;\n\n for (const link of note.links) {\n if (link.type === 'markdown' && !link.target.endsWith('.md')) {\n continue;\n }\n\n const resolvedPath = resolver.resolve(link.target);\n\n if (resolvedPath && notePathMap.has(resolvedPath)) {\n if (!node.outgoing.includes(resolvedPath)) {\n node.outgoing.push(resolvedPath);\n }\n\n const targetNode = notePathMap.get(resolvedPath)!;\n if (!targetNode.incoming.includes(note.path)) {\n targetNode.incoming.push(note.path);\n }\n }\n }\n }\n\n return Array.from(notePathMap.values());\n}\n","import { loadVault } from '../core/vault-loader.js';\nimport { parseLinks } from '../core/link-parser.js';\nimport { analyze } from '../core/analyzer.js';\nimport type { AnalysisReport, DeadLinksConfig } from '../types/index.js';\n\nexport class DeadLinksEngine {\n async analyze(\n vaultPath: string,\n config: DeadLinksConfig = {},\n ): Promise<AnalysisReport> {\n const startTime = Date.now();\n\n const notes = await loadVault(vaultPath, config.ignore);\n\n for (const note of notes) {\n note.links = parseLinks(note.content);\n }\n\n const report = analyze(notes, vaultPath, config.checkAttachments);\n report.duration = Date.now() - startTime;\n\n return report;\n }\n}\n","import { readFile, access } from 'node:fs/promises';\nimport { resolve, join } from 'node:path';\nimport type { DeadLinksConfig } from '../types/index.js';\n\nconst CONFIG_FILES = ['.deadlinksrc.json', '.deadlinksrc', 'dead-links.config.json'];\n\nexport class ConfigLoader {\n async loadConfig(vaultPath: string): Promise<DeadLinksConfig> {\n const absolutePath = resolve(vaultPath);\n\n for (const configFile of CONFIG_FILES) {\n const configPath = join(absolutePath, configFile);\n\n try {\n await access(configPath);\n const content = await readFile(configPath, 'utf-8');\n const parsed = JSON.parse(content) as DeadLinksConfig;\n\n if (parsed.ignore && !Array.isArray(parsed.ignore)) {\n continue;\n }\n if (parsed.ignoreFolders && !Array.isArray(parsed.ignoreFolders)) {\n continue;\n }\n\n return parsed;\n } catch {\n continue;\n }\n }\n\n return {};\n }\n\n mergeConfig(\n fileConfig: DeadLinksConfig,\n cliOptions: Record<string, unknown>,\n ): DeadLinksConfig {\n return {\n ignore: (cliOptions.ignore as string[] | undefined) || fileConfig.ignore,\n checkAttachments:\n (cliOptions.attachments as boolean | undefined) ??\n fileConfig.checkAttachments ??\n false,\n ignoreFolders:\n (cliOptions.ignoreFolders as string[] | undefined) ||\n fileConfig.ignoreFolders,\n };\n }\n}\n","import chalk from 'chalk';\nimport type {\n AnalysisReport,\n BrokenLink,\n Note,\n} from '../types/index.js';\n\nexport class TerminalReporter {\n private ignoreFolders: string[];\n private quiet: boolean;\n\n constructor(ignoreFolders: string[] = [], quiet = false) {\n this.ignoreFolders = ignoreFolders;\n this.quiet = quiet;\n }\n\n report(result: AnalysisReport): string {\n if (this.quiet) {\n return this.reportQuiet(result);\n }\n\n let output = '';\n output += this.printStats(result);\n output += this.printBrokenLinks(result.brokenLinks);\n output += this.printOrphanNotes(result.orphanNotes);\n output += this.printDuration(result.duration);\n return output;\n }\n\n private reportQuiet(result: AnalysisReport): string {\n const lines: string[] = [];\n\n for (const broken of result.brokenLinks) {\n lines.push(`${broken.source} -> ${broken.link.target}`);\n }\n\n for (const note of result.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n if (this.ignoreFolders.some((f) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n lines.push(`orphan: ${note.relativePath}`);\n }\n\n return lines.join('\\n');\n }\n\n private printStats(report: AnalysisReport): string {\n const pad = 14;\n let out = '\\n';\n out += this.statLine('Notes'.padEnd(pad), String(report.stats.totalNotes));\n out += this.statLine('Links'.padEnd(pad), String(report.stats.totalLinks));\n out += this.statLine('Broken links'.padEnd(pad), String(report.stats.brokenCount), 'red');\n out += this.statLine('Orphan notes'.padEnd(pad), String(report.stats.orphanCount), 'yellow');\n out += this.statLine('Components'.padEnd(pad), String(report.stats.connectedComponents));\n out += '\\n';\n return out;\n }\n\n private statLine(label: string, value: string, color: 'red' | 'yellow' | 'white' = 'white'): string {\n return ` ${chalk.gray(label)} ${color === 'red' ? chalk.red.bold(value) : color === 'yellow' ? chalk.yellow.bold(value) : chalk.white.bold(value)}\\n`;\n }\n\n private printBrokenLinks(brokenLinks: BrokenLink[]): string {\n if (brokenLinks.length === 0) {\n return chalk.green(' ✓ No broken links\\n\\n');\n }\n\n const grouped = new Map<string, BrokenLink[]>();\n for (const broken of brokenLinks) {\n const existing = grouped.get(broken.source) || [];\n existing.push(broken);\n grouped.set(broken.source, existing);\n }\n\n const sorted = [...grouped.entries()].sort((a, b) => b[1].length - a[1].length);\n\n let out = chalk.bold.red(`\\n✖ Broken links (${brokenLinks.length})\\n\\n`);\n\n for (const [source, links] of sorted) {\n const count = links.length;\n const isMultiple = count >= 2;\n\n const countStr = isMultiple ? chalk.red.bold(`✖ ${count}`) : chalk.yellow.bold(`✖ ${count}`);\n out += ` ${countStr} ${chalk.white.bold(source)}\\n`;\n\n for (const link of links) {\n const pill = isMultiple\n ? chalk.redBright(`[${link.link.target}]`)\n : chalk.yellowBright(`[${link.link.target}]`);\n out += ` ${pill}\\n`;\n }\n out += '\\n';\n }\n\n return out;\n }\n\n private printOrphanNotes(orphanNotes: Note[]): string {\n if (orphanNotes.length === 0) {\n return chalk.green(' ✓ No orphan notes\\n\\n');\n }\n\n const byFolder = new Map<string, number>();\n for (const note of orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n\n if (this.ignoreFolders.some((f) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n\n const sorted = [...byFolder.entries()].sort((a, b) => b[1] - a[1]);\n\n if (sorted.length === 0) {\n return chalk.green(' ✓ No orphan notes (all in ignored folders)\\n\\n');\n }\n\n const maxCount = sorted[0]![1];\n const barWidth = 10;\n\n let out = chalk.bold.yellow(`\\n⚠ Orphan notes (${orphanNotes.length} total)\\n\\n`);\n\n for (const [folder, count] of sorted) {\n const filled = Math.round((count / maxCount) * barWidth);\n const empty = barWidth - filled;\n const bar = chalk.gray('█'.repeat(filled) + '░'.repeat(empty));\n out += ` ${chalk.gray(folder.padEnd(40))} ${bar} ${chalk.white.bold(String(count))}\\n`;\n }\n\n out += '\\n';\n return out;\n }\n\n private printDuration(ms: number): string {\n return chalk.dim(` Completed in ${ms}ms\\n`);\n }\n}\n","import type { AnalysisReport } from '../types/index.js';\n\nexport class JsonReporter {\n report(result: AnalysisReport): string {\n return JSON.stringify(\n {\n stats: result.stats,\n brokenLinks: result.brokenLinks,\n orphanNotes: result.orphanNotes.map((n) => ({\n path: n.relativePath,\n })),\n graph: result.graph.map((n) => ({\n note: n.note.relativePath,\n incoming: n.incoming.length,\n outgoing: n.outgoing.length,\n })),\n duration: result.duration,\n },\n null,\n 2,\n );\n }\n}\n","import type { AnalysisReport } from '../types/index.js';\n\nfunction pill(target: string, isMultiple: boolean): string {\n return isMultiple ? `[${target}]` : `[${target}]`;\n}\n\nfunction bar(count: number, max: number, width: number): string {\n const filled = Math.round((count / max) * width);\n const empty = width - filled;\n return '█'.repeat(filled) + '░'.repeat(empty);\n}\n\nexport function generateMarkdownReport(report: AnalysisReport): string {\n const lines: string[] = [];\n\n lines.push('# Dead Links Report');\n lines.push('');\n lines.push(`Generated: ${new Date().toISOString()}`);\n lines.push(`Completed in ${report.duration}ms`);\n lines.push('');\n\n lines.push('## Stats');\n lines.push('');\n lines.push(`| Metric | Value |`);\n lines.push(`|--------|-------|`);\n lines.push(`| Notes | ${report.stats.totalNotes} |`);\n lines.push(`| Links | ${report.stats.totalLinks} |`);\n lines.push(`| Broken | ${report.stats.brokenCount} |`);\n lines.push(`| Orphans | ${report.stats.orphanCount} |`);\n lines.push(`| Components | ${report.stats.connectedComponents} |`);\n lines.push('');\n\n lines.push('## Broken Links');\n lines.push('');\n\n if (report.brokenLinks.length === 0) {\n lines.push('No broken links found.');\n } else {\n const grouped = new Map<string, typeof report.brokenLinks>();\n for (const b of report.brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n\n const sorted = [...grouped.entries()].sort((a, b) => b[1].length - a[1].length);\n\n for (const [source, links] of sorted) {\n const count = links.length;\n lines.push(`### ${count > 1 ? '✖' : '✖'} ${count} ${source}`);\n lines.push('');\n for (const link of links) {\n lines.push(`- ${pill(link.link.target, count > 1)}`);\n }\n lines.push('');\n }\n }\n\n lines.push('## Orphan Notes');\n lines.push('');\n\n if (report.orphanNotes.length === 0) {\n lines.push('No orphan notes found.');\n } else {\n const byFolder = new Map<string, number>();\n for (const note of report.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n\n const sorted = [...byFolder.entries()].sort((a, b) => b[1] - a[1]);\n const maxCount = sorted[0]?.[1] ?? 1;\n\n for (const [folder, count] of sorted) {\n lines.push(`- ${folder.padEnd(40)} ${bar(count, maxCount, 10)} ${count}`);\n }\n }\n\n lines.push('');\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["readFile","resolve","pill","bar"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/vault-loader.ts","../src/core/link-parser.ts","../src/core/analyzer.ts","../src/core/alias-resolver.ts","../src/core/graph-builder.ts","../src/utils/suggestions.ts","../src/utils/engine.ts","../src/utils/config-loader.ts","../src/reporters/terminal-reporter.ts","../src/reporters/json-reporter.ts","../src/reporters/csv-reporter.ts","../src/reporters/html-reporter.ts","../src/reporters/mermaid-reporter.ts","../src/reporters/graphviz-reporter.ts","../src/utils/markdown-report.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { writeFile } from 'node:fs/promises';\nimport ora from 'ora';\nimport { DeadLinksEngine } from './utils/engine.js';\nimport { ConfigLoader } from './utils/config-loader.js';\nimport { TerminalReporter } from './reporters/terminal-reporter.js';\nimport { JsonReporter } from './reporters/json-reporter.js';\nimport { CsvReporter } from './reporters/csv-reporter.js';\nimport { HtmlReporter } from './reporters/html-reporter.js';\nimport { MermaidReporter } from './reporters/mermaid-reporter.js';\nimport { GraphVizReporter } from './reporters/graphviz-reporter.js';\nimport { generateMarkdownReport } from './utils/markdown-report.js';\nimport type { BrokenLink, Note } from './types/index.js';\n\nconst program = new Command();\n\nprogram\n .name('dead-links')\n .description('Scan Obsidian vaults for broken links, orphan notes, and connection graphs')\n .version('0.2.0');\n\nasync function loadConfigAndAnalyze(\n vaultPath: string,\n cliOptions: Record<string, unknown>,\n) {\n const configLoader = new ConfigLoader();\n const fileConfig = await configLoader.loadConfig(vaultPath);\n const config = configLoader.mergeConfig(fileConfig, cliOptions);\n\n const engine = new DeadLinksEngine();\n return engine.analyze(vaultPath, config);\n}\n\nfunction printBrokenVerbose(brokenLinks: BrokenLink[]) {\n const grouped = new Map<string, BrokenLink[]>();\n for (const b of brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n\n for (const [source, links] of grouped) {\n for (const link of links) {\n console.log(` ${source} → ${link.link.target} (line ${link.link.line})`);\n }\n }\n}\n\nfunction printOrphansVerbose(orphanNotes: Note[]) {\n for (const note of orphanNotes) {\n console.log(` ${note.relativePath}`);\n }\n}\n\nprogram\n .command('scan')\n .description('Scan the vault for all issues')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--format <type>', 'Output format: text, json, markdown, csv, html, mermaid, dot', 'text')\n .option('--output <file>', 'Output file path')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--attachments', 'Also check embedded attachments (images, PDFs, etc.)')\n .option('--ignore-folders <folders...>', 'Exclude folders from orphan report')\n .option('--quiet', 'Only show issues, no stats or summary')\n .option('--no-color', 'Disable colored output')\n .option('--suggestions', 'Show smart suggestions for broken links')\n .option('--debug', 'Enable debug mode with verbose output')\n .option('--fix', 'Automatically fix broken links (requires suggestions)')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (options.debug) {\n console.log(`[DEBUG] Vault: ${options.vault}`);\n console.log(`[DEBUG] Format: ${options.format}`);\n console.log(`[DEBUG] Broken links: ${report.stats.brokenCount}`);\n console.log(`[DEBUG] Orphan notes: ${report.stats.orphanCount}`);\n }\n\n let output = '';\n\n if (options.format === 'json') {\n const reporter = new JsonReporter();\n output = reporter.report(report);\n } else if (options.format === 'markdown') {\n output = generateMarkdownReport(report);\n } else if (options.format === 'csv') {\n const reporter = new CsvReporter();\n output = reporter.report(report);\n } else if (options.format === 'html') {\n const reporter = new HtmlReporter();\n output = reporter.report(report);\n } else if (options.format === 'mermaid') {\n const reporter = new MermaidReporter();\n output = reporter.report(report);\n } else if (options.format === 'dot') {\n const reporter = new GraphVizReporter();\n output = reporter.report(report);\n } else {\n const reporter = new TerminalReporter(options.ignoreFolders || [], options.quiet || false);\n output = reporter.report(report);\n }\n\n if (options.output) {\n await writeFile(options.output, output);\n console.log(`Report written to ${options.output}`);\n } else if (options.format === 'text') {\n console.log(output);\n } else {\n console.log(output);\n }\n\n process.exit(report.stats.brokenCount > 0 || report.stats.orphanCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('broken')\n .description('Show only broken links')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--attachments', 'Also check embedded attachments (images, PDFs, etc.)')\n .option('--verbose', 'Show line numbers and full details')\n .option('--suggestions', 'Show smart suggestions for broken links')\n .option('--debug', 'Enable debug mode with verbose output')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (report.brokenLinks.length === 0) {\n console.log('No broken links found.');\n } else {\n console.log(`\\nBroken links (${report.brokenLinks.length})\\n`);\n if (options.verbose) {\n printBrokenVerbose(report.brokenLinks);\n } else {\n const grouped = new Map<string, BrokenLink[]>();\n for (const b of report.brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n for (const [source, links] of grouped) {\n const targets = links.map((l: BrokenLink) => l.link.target).join(', ');\n console.log(` ${source} → ${targets}`);\n }\n }\n console.log();\n\n if (report.suggestions && report.suggestions.length > 0) {\n console.log('💡 Suggestions:');\n for (const suggestion of report.suggestions.slice(0, 5)) {\n const confidence = Math.round(suggestion.similarity * 100);\n console.log(` ${suggestion.broken} → ${suggestion.suggested} (${confidence}%)`);\n }\n console.log();\n }\n }\n\n process.exit(report.stats.brokenCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('orphans')\n .description('Show only orphan notes')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--ignore-folders <folders...>', 'Exclude folders from report')\n .option('--verbose', 'Show full list of orphan notes')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (report.orphanNotes.length === 0) {\n console.log('No orphan notes found.');\n } else {\n console.log(`\\nOrphan notes (${report.orphanNotes.length})\\n`);\n if (options.verbose) {\n printOrphansVerbose(report.orphanNotes);\n } else {\n const ignoreFolders = options.ignoreFolders || [];\n const byFolder = new Map<string, number>();\n for (const note of report.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n if (ignoreFolders.some((f: string) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n for (const [folder, count] of [...byFolder.entries()].sort((a, b) => b[1] - a[1])) {\n console.log(` ${folder} ${count}`);\n }\n }\n console.log();\n }\n\n process.exit(report.stats.orphanCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('graph')\n .description('Show connection graph')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--format <type>', 'Output format: text, json', 'text')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (options.format === 'json') {\n console.log(JSON.stringify(report.graph, null, 2));\n } else {\n console.log(\n `Graph: ${report.stats.totalNotes} notes, ${report.stats.totalLinks} links (${report.duration}ms)\\n`,\n );\n\n const topConnected = [...report.graph]\n .sort((a, b) => b.incoming.length - a.incoming.length)\n .slice(0, 10)\n .filter((n) => n.incoming.length > 0);\n\n if (topConnected.length > 0) {\n console.log('Most linked-to notes:');\n for (const node of topConnected) {\n console.log(\n ` ${node.note.relativePath} (${node.incoming.length} incoming, ${node.outgoing.length} outgoing)`,\n );\n }\n }\n }\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('report')\n .description('Generate a markdown report')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--output <file>', 'Output file path', 'dead-links-report.md')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n const markdown = generateMarkdownReport(report);\n await writeFile(options.output, markdown);\n console.log(`Report written to ${options.output}`);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('stats')\n .description('Show detailed vault statistics')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--debug', 'Enable debug mode')\n .action(async (options) => {\n const spinner = ora('Analyzing vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Analysis complete in ${report.duration}ms`);\n\n console.log('\\n📊 Vault Statistics\\n');\n console.log(` Total Notes: ${report.stats.totalNotes}`);\n console.log(` Total Links: ${report.stats.totalLinks}`);\n console.log(` Avg Links per Note: ${(report.stats.totalLinks / report.stats.totalNotes).toFixed(2)}`);\n console.log(` Broken Links: ${report.stats.brokenCount}`);\n console.log(` Orphan Notes: ${report.stats.orphanCount}`);\n console.log(` Connected Components: ${report.stats.connectedComponents}`);\n console.log(` Health Score: ${(100 - (report.stats.brokenCount / report.stats.totalLinks) * 100).toFixed(1)}%`);\n\n const mostConnected = [...report.graph]\n .sort((a, b) => (b.incoming.length + b.outgoing.length) - (a.incoming.length + a.outgoing.length))\n .slice(0, 5);\n\n console.log('\\n🌐 Most Connected Notes:');\n for (const node of mostConnected) {\n const total = node.incoming.length + node.outgoing.length;\n console.log(` ${node.note.relativePath} (${total} connections: ${node.incoming.length} in, ${node.outgoing.length} out)`);\n }\n\n const lonelyNotes = report.graph.filter((n) => n.incoming.length === 0 && n.outgoing.length === 0).slice(0, 5);\n if (lonelyNotes.length > 0) {\n console.log('\\n🔴 Orphan Notes:');\n for (const node of lonelyNotes) {\n console.log(` ${node.note.relativePath}`);\n }\n }\n\n console.log();\n } catch (error) {\n spinner.fail('Analysis failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram.parse();","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport fg from 'fast-glob';\nimport matter from 'gray-matter';\nimport type { Note } from '../types/index.js';\n\nconst DEFAULT_IGNORE = [\n '**/node_modules/**',\n '**/.git/**',\n '**/.obsidian/**',\n];\n\nexport async function loadVault(\n vaultPath: string,\n customIgnore: string[] = [],\n): Promise<Note[]> {\n const absolutePath = resolve(vaultPath);\n\n const files = await fg(['**/*.md', '**/*.canvas'], {\n cwd: absolutePath,\n ignore: [...DEFAULT_IGNORE, ...customIgnore],\n });\n\n const notes: Note[] = [];\n\n for (const file of files) {\n const fullPath = resolve(absolutePath, file);\n const content = await readFile(fullPath, 'utf-8');\n const parsed = matter(content);\n\n const aliases: string[] = [];\n if (parsed.data.aliases) {\n if (Array.isArray(parsed.data.aliases)) {\n aliases.push(...parsed.data.aliases.map(String));\n } else if (typeof parsed.data.aliases === 'string') {\n aliases.push(parsed.data.aliases);\n }\n }\n\n notes.push({\n path: fullPath,\n relativePath: file,\n content: parsed.content,\n aliases,\n links: [],\n });\n }\n\n return notes;\n}\n\nexport function resolveFileName(notePath: string): string {\n const base = notePath.replace(/\\.(md|canvas)$/, '');\n const parts = base.split('/');\n return parts[parts.length - 1]!;\n}\n","import type { Link } from '../types/index.js';\n\nconst wikiLinkRegex = /!?\\[\\[([^\\]]+)\\]\\]/g;\nconst markdownLinkRegex = /\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\n\nexport function parseLinks(content: string): Link[] {\n const links: Link[] = [];\n const lines = content.split('\\n');\n\n for (let lineNum = 0; lineNum < lines.length; lineNum++) {\n const line = lines[lineNum]!;\n\n for (const match of line.matchAll(wikiLinkRegex)) {\n const raw = match[0]!;\n const inner = match[1]!;\n const isEmbed = raw.startsWith('!');\n\n const link = parseWikiLink(inner, raw, isEmbed, lineNum + 1);\n links.push(link);\n }\n\n for (const match of line.matchAll(markdownLinkRegex)) {\n const raw = match[0]!;\n const target = match[2]!;\n\n if (target.startsWith('#')) continue;\n\n const link: Link = {\n raw,\n target: target.split('#')[0]!.split('|')[0]!,\n heading: target.includes('#') ? target.split('#')[1] : undefined,\n type: 'markdown',\n line: lineNum + 1,\n };\n links.push(link);\n }\n }\n\n return links;\n}\n\nfunction parseWikiLink(inner: string, raw: string, isEmbed: boolean, line: number): Link {\n const [targetPart, aliasPart] = inner.split('|');\n const [target, ...rest] = targetPart!.split('#');\n\n let heading: string | undefined;\n let blockId: string | undefined;\n\n if (rest.length > 0) {\n const remainder = rest.join('#');\n if (remainder.startsWith('^')) {\n blockId = remainder.slice(1);\n } else {\n heading = remainder;\n }\n }\n\n return {\n raw,\n target: target!.trim(),\n alias: aliasPart?.trim(),\n heading,\n blockId,\n type: isEmbed ? 'embed' : 'wiki',\n line,\n };\n}\n","import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { Note, GraphNode, BrokenLink, AnalysisReport } from '../types/index.js';\nimport { AliasResolver } from './alias-resolver.js';\nimport { buildGraph } from './graph-builder.js';\n\nconst ATTACHMENT_EXTENSIONS = new Set([\n 'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp', 'ico',\n 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',\n 'mp3', 'mp4', 'wav', 'avi', 'mov', 'webm',\n 'zip', 'rar', '7z', 'tar', 'gz',\n 'exe', 'dmg', 'apk',\n 'css', 'js', 'ts', 'html',\n 'txt', 'csv', 'log',\n 'woff', 'woff2', 'ttf', 'eot',\n]);\n\nfunction isAttachment(target: string): boolean {\n const ext = target.split('.').pop()?.toLowerCase();\n return ext ? ATTACHMENT_EXTENSIONS.has(ext) : false;\n}\n\nexport function analyze(\n notes: Note[],\n vaultPath: string,\n checkAttachments = false,\n): AnalysisReport {\n const resolver = new AliasResolver(notes);\n const graph = buildGraph(notes, resolver);\n\n const brokenLinks: BrokenLink[] = [];\n const orphanNotes: Note[] = [];\n\n for (const node of graph) {\n if (node.incoming.length === 0 && node.outgoing.length === 0) {\n orphanNotes.push(node.note);\n }\n\n for (const link of node.note.links) {\n if (link.type === 'markdown' && !link.target.endsWith('.md')) {\n continue;\n }\n\n if (!checkAttachments && isAttachment(link.target)) {\n continue;\n }\n\n const resolvedPath = resolver.resolve(link.target);\n\n if (resolvedPath === node.note.path) {\n continue;\n }\n\n if (!resolvedPath) {\n brokenLinks.push({\n source: node.note.relativePath,\n link,\n reason: `Target \"${link.target}\" not found in vault`,\n });\n } else if (link.type === 'embed' || link.type === 'markdown') {\n if (!existsSync(resolvedPath)) {\n brokenLinks.push({\n source: node.note.relativePath,\n link,\n reason: `File \"${link.target}\" does not exist`,\n });\n }\n }\n }\n }\n\n const connectedComponents = countConnectedComponents(graph);\n const totalLinks = notes.reduce((sum, n) => sum + n.links.length, 0);\n\n return {\n brokenLinks,\n orphanNotes,\n graph,\n stats: {\n totalNotes: notes.length,\n totalLinks,\n brokenCount: brokenLinks.length,\n orphanCount: orphanNotes.length,\n connectedComponents,\n },\n };\n}\n\nfunction countConnectedComponents(graph: GraphNode[]): number {\n if (graph.length === 0) return 0;\n\n const visited = new Set<string>();\n let components = 0;\n\n const adjacency = new Map<string, string[]>();\n for (const node of graph) {\n adjacency.set(node.note.path, [...node.incoming, ...node.outgoing]);\n }\n\n for (const node of graph) {\n if (visited.has(node.note.path)) continue;\n\n components++;\n const stack = [node.note.path];\n\n while (stack.length > 0) {\n const current = stack.pop()!;\n if (visited.has(current)) continue;\n visited.add(current);\n\n const neighbors = adjacency.get(current) || [];\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n stack.push(neighbor);\n }\n }\n }\n }\n\n return components;\n}\n","import type { Note } from '../types/index.js';\n\nexport class AliasResolver {\n private aliasToNote: Map<string, string> = new Map();\n private fileNameToNote: Map<string, string> = new Map();\n private basenameToNote: Map<string, string> = new Map();\n\n constructor(notes: Note[]) {\n for (const note of notes) {\n const fileName = note.relativePath.replace(/\\.(md|canvas)$/, '');\n const basename = fileName.split('/').pop()!;\n\n this.fileNameToNote.set(fileName.toLowerCase(), note.path);\n this.basenameToNote.set(basename.toLowerCase(), note.path);\n\n try {\n this.fileNameToNote.set(decodeURIComponent(fileName).toLowerCase(), note.path);\n this.basenameToNote.set(decodeURIComponent(basename).toLowerCase(), note.path);\n } catch {\n // skip malformed URIs\n }\n\n for (const alias of note.aliases) {\n this.aliasToNote.set(alias.toLowerCase(), note.path);\n }\n }\n }\n\n resolve(target: string): string | null {\n const normalized = target.toLowerCase().trim();\n\n if (this.aliasToNote.has(normalized)) {\n return this.aliasToNote.get(normalized)!;\n }\n\n if (this.fileNameToNote.has(normalized)) {\n return this.fileNameToNote.get(normalized)!;\n }\n\n if (this.basenameToNote.has(normalized)) {\n return this.basenameToNote.get(normalized)!;\n }\n\n const decoded = (() => {\n try {\n return decodeURIComponent(normalized);\n } catch {\n return normalized;\n }\n })();\n\n if (this.aliasToNote.has(decoded)) {\n return this.aliasToNote.get(decoded)!;\n }\n if (this.fileNameToNote.has(decoded)) {\n return this.fileNameToNote.get(decoded)!;\n }\n if (this.basenameToNote.has(decoded)) {\n return this.basenameToNote.get(decoded)!;\n }\n\n return null;\n }\n}\n","import type { Note, GraphNode, Link } from '../types/index.js';\nimport { AliasResolver } from './alias-resolver.js';\n\nexport function buildGraph(notes: Note[], resolver: AliasResolver): GraphNode[] {\n const notePathMap = new Map<string, GraphNode>();\n\n for (const note of notes) {\n notePathMap.set(note.path, {\n note,\n incoming: [],\n outgoing: [],\n });\n }\n\n for (const note of notes) {\n const node = notePathMap.get(note.path)!;\n\n for (const link of note.links) {\n if (link.type === 'markdown' && !link.target.endsWith('.md')) {\n continue;\n }\n\n const resolvedPath = resolver.resolve(link.target);\n\n if (resolvedPath && notePathMap.has(resolvedPath)) {\n if (!node.outgoing.includes(resolvedPath)) {\n node.outgoing.push(resolvedPath);\n }\n\n const targetNode = notePathMap.get(resolvedPath)!;\n if (!targetNode.incoming.includes(note.path)) {\n targetNode.incoming.push(note.path);\n }\n }\n }\n }\n\n return Array.from(notePathMap.values());\n}\n","import type { Note } from '../types/index.js';\r\n\r\nexport interface Suggestion {\r\n broken: string;\r\n suggested: string;\r\n similarity: number;\r\n source: string;\r\n}\r\n\r\nfunction levenshteinDistance(a: string, b: string): number {\r\n const matrix: number[][] = Array(b.length + 1)\r\n .fill(null)\r\n .map(() => Array(a.length + 1).fill(0));\r\n\r\n for (let i = 0; i <= a.length; i++) matrix[0]![i] = i;\r\n for (let j = 0; j <= b.length; j++) matrix[j]![0] = j;\r\n\r\n for (let j = 1; j <= b.length; j++) {\r\n for (let i = 1; i <= a.length; i++) {\r\n if (a[i - 1] === b[j - 1]) {\r\n matrix[j]![i] = matrix[j - 1]![i - 1]!;\r\n } else {\r\n matrix[j]![i] = Math.min(\r\n matrix[j]![i - 1]! + 1,\r\n matrix[j - 1]![i]! + 1,\r\n matrix[j - 1]![i - 1]! + 1,\r\n );\r\n }\r\n }\r\n }\r\n\r\n return matrix[b.length]![a.length]!;\r\n}\r\n\r\nfunction calculateSimilarity(a: string, b: string): number {\r\n const distance = levenshteinDistance(\r\n a.toLowerCase(),\r\n b.toLowerCase(),\r\n );\r\n const maxLen = Math.max(a.length, b.length);\r\n return Math.max(0, 1 - distance / maxLen);\r\n}\r\n\r\nexport function generateSuggestions(\r\n brokenTargets: string[],\r\n allNotes: Note[],\r\n threshold = 0.6,\r\n): Suggestion[] {\r\n const availableNotes = allNotes.map((n) => ({\r\n name: n.relativePath.replace(/\\.(md|canvas)$/, ''),\r\n basename: n.relativePath.split('/').pop()?.replace(/\\.(md|canvas)$/, '') || '',\r\n aliases: n.aliases,\r\n }));\r\n\r\n const suggestions: Suggestion[] = [];\r\n\r\n for (const broken of brokenTargets) {\r\n const brokenBasename = broken.split('/').pop() || broken;\r\n const candidates: Array<{ suggestion: string; similarity: number }> = [];\r\n\r\n for (const note of availableNotes) {\r\n const basenameScore = calculateSimilarity(brokenBasename, note.basename);\r\n const fullScore = calculateSimilarity(broken, note.name);\r\n\r\n // Check aliases too\r\n for (const alias of note.aliases) {\r\n const aliasScore = calculateSimilarity(broken, alias);\r\n if (aliasScore > basenameScore && aliasScore > fullScore) {\r\n candidates.push({ suggestion: note.name, similarity: aliasScore });\r\n }\r\n }\r\n\r\n if (basenameScore >= fullScore && basenameScore > threshold) {\r\n candidates.push({ suggestion: note.name, similarity: basenameScore });\r\n } else if (fullScore > threshold) {\r\n candidates.push({ suggestion: note.name, similarity: fullScore });\r\n }\r\n }\r\n\r\n // Sort by similarity and take top suggestion\r\n if (candidates.length > 0) {\r\n const best = candidates.sort((a, b) => b.similarity - a.similarity)[0]!;\r\n suggestions.push({\r\n broken,\r\n suggested: best.suggestion,\r\n similarity: best.similarity,\r\n source: broken,\r\n });\r\n }\r\n }\r\n\r\n return suggestions.sort((a, b) => b.similarity - a.similarity);\r\n}\r\n","import { loadVault } from '../core/vault-loader.js';\nimport { parseLinks } from '../core/link-parser.js';\nimport { analyze } from '../core/analyzer.js';\nimport { generateSuggestions } from './suggestions.js';\nimport type { AnalysisReport, DeadLinksConfig } from '../types/index.js';\n\nexport class DeadLinksEngine {\n async analyze(\n vaultPath: string,\n config: DeadLinksConfig = {},\n ): Promise<AnalysisReport> {\n const startTime = Date.now();\n\n const notes = await loadVault(vaultPath, config.ignore);\n\n // Parallelize link parsing\n await Promise.all(\n notes.map((note) => {\n note.links = parseLinks(note.content);\n return Promise.resolve();\n }),\n );\n\n const report = analyze(notes, vaultPath, config.checkAttachments);\n\n // Generate suggestions for broken links\n if (config.suggestions) {\n const brokenTargets = Array.from(\n new Set(report.brokenLinks.map((b) => b.link.target)),\n );\n report.suggestions = generateSuggestions(brokenTargets, notes, 0.65);\n }\n\n report.duration = Date.now() - startTime;\n\n return report;\n }\n}\n","import { readFile, access } from 'node:fs/promises';\nimport { resolve, join } from 'node:path';\nimport type { DeadLinksConfig } from '../types/index.js';\n\nconst CONFIG_FILES = ['.deadlinksrc.json', '.deadlinksrc', 'dead-links.config.json'];\n\nexport class ConfigLoader {\n async loadConfig(vaultPath: string): Promise<DeadLinksConfig> {\n const absolutePath = resolve(vaultPath);\n\n for (const configFile of CONFIG_FILES) {\n const configPath = join(absolutePath, configFile);\n\n try {\n await access(configPath);\n const content = await readFile(configPath, 'utf-8');\n const parsed = JSON.parse(content) as DeadLinksConfig;\n\n if (parsed.ignore && !Array.isArray(parsed.ignore)) {\n continue;\n }\n if (parsed.ignoreFolders && !Array.isArray(parsed.ignoreFolders)) {\n continue;\n }\n\n return parsed;\n } catch {\n continue;\n }\n }\n\n return {};\n }\n\n mergeConfig(\n fileConfig: DeadLinksConfig,\n cliOptions: Record<string, unknown>,\n ): DeadLinksConfig {\n return {\n ignore: (cliOptions.ignore as string[] | undefined) || fileConfig.ignore,\n checkAttachments:\n (cliOptions.attachments as boolean | undefined) ??\n fileConfig.checkAttachments ??\n false,\n ignoreFolders:\n (cliOptions.ignoreFolders as string[] | undefined) ||\n fileConfig.ignoreFolders,\n suggestions:\n (cliOptions.suggestions as boolean | undefined) ??\n fileConfig.suggestions ??\n false,\n debug:\n (cliOptions.debug as boolean | undefined) ??\n fileConfig.debug ??\n false,\n fix:\n (cliOptions.fix as boolean | undefined) ??\n fileConfig.fix ??\n false,\n };\n }\n}\n","import chalk from 'chalk';\nimport type {\n AnalysisReport,\n BrokenLink,\n Note,\n} from '../types/index.js';\n\nexport class TerminalReporter {\n private ignoreFolders: string[];\n private quiet: boolean;\n\n constructor(ignoreFolders: string[] = [], quiet = false) {\n this.ignoreFolders = ignoreFolders;\n this.quiet = quiet;\n }\n\n report(result: AnalysisReport): string {\n if (this.quiet) {\n return this.reportQuiet(result);\n }\n\n let output = '';\n output += this.printStats(result);\n output += this.printBrokenLinks(result.brokenLinks);\n output += this.printOrphanNotes(result.orphanNotes);\n if (result.suggestions && result.suggestions.length > 0) {\n output += this.printSuggestions(result.suggestions);\n }\n output += this.printDuration(result.duration);\n return output;\n }\n\n private reportQuiet(result: AnalysisReport): string {\n const lines: string[] = [];\n\n for (const broken of result.brokenLinks) {\n lines.push(`${broken.source} -> ${broken.link.target}`);\n }\n\n for (const note of result.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n if (this.ignoreFolders.some((f) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n lines.push(`orphan: ${note.relativePath}`);\n }\n\n return lines.join('\\n');\n }\n\n private printStats(report: AnalysisReport): string {\n const pad = 14;\n let out = '\\n';\n out += this.statLine('Notes'.padEnd(pad), String(report.stats.totalNotes));\n out += this.statLine('Links'.padEnd(pad), String(report.stats.totalLinks));\n out += this.statLine('Broken links'.padEnd(pad), String(report.stats.brokenCount), 'red');\n out += this.statLine('Orphan notes'.padEnd(pad), String(report.stats.orphanCount), 'yellow');\n out += this.statLine('Components'.padEnd(pad), String(report.stats.connectedComponents));\n out += '\\n';\n return out;\n }\n\n private statLine(label: string, value: string, color: 'red' | 'yellow' | 'white' = 'white'): string {\n return ` ${chalk.gray(label)} ${color === 'red' ? chalk.red.bold(value) : color === 'yellow' ? chalk.yellow.bold(value) : chalk.white.bold(value)}\\n`;\n }\n\n private printBrokenLinks(brokenLinks: BrokenLink[]): string {\n if (brokenLinks.length === 0) {\n return chalk.green(' ✓ No broken links\\n\\n');\n }\n\n const grouped = new Map<string, BrokenLink[]>();\n for (const broken of brokenLinks) {\n const existing = grouped.get(broken.source) || [];\n existing.push(broken);\n grouped.set(broken.source, existing);\n }\n\n const sorted = [...grouped.entries()].sort((a, b) => b[1].length - a[1].length);\n\n let out = chalk.bold.red(`\\n✖ Broken links (${brokenLinks.length})\\n\\n`);\n\n for (const [source, links] of sorted) {\n const count = links.length;\n const isMultiple = count >= 2;\n\n const countStr = isMultiple ? chalk.red.bold(`✖ ${count}`) : chalk.yellow.bold(`✖ ${count}`);\n out += ` ${countStr} ${chalk.white.bold(source)}\\n`;\n\n for (const link of links) {\n const pill = isMultiple\n ? chalk.redBright(`[${link.link.target}]`)\n : chalk.yellowBright(`[${link.link.target}]`);\n out += ` ${pill}\\n`;\n }\n out += '\\n';\n }\n\n return out;\n }\n\n private printOrphanNotes(orphanNotes: Note[]): string {\n if (orphanNotes.length === 0) {\n return chalk.green(' ✓ No orphan notes\\n\\n');\n }\n\n const byFolder = new Map<string, number>();\n for (const note of orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n\n if (this.ignoreFolders.some((f) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n\n const sorted = [...byFolder.entries()].sort((a, b) => b[1] - a[1]);\n\n if (sorted.length === 0) {\n return chalk.green(' ✓ No orphan notes (all in ignored folders)\\n\\n');\n }\n\n const maxCount = sorted[0]![1];\n const barWidth = 10;\n\n let out = chalk.bold.yellow(`\\n⚠ Orphan notes (${orphanNotes.length} total)\\n\\n`);\n\n for (const [folder, count] of sorted) {\n const filled = Math.round((count / maxCount) * barWidth);\n const empty = barWidth - filled;\n const bar = chalk.gray('█'.repeat(filled) + '░'.repeat(empty));\n out += ` ${chalk.gray(folder.padEnd(40))} ${bar} ${chalk.white.bold(String(count))}\\n`;\n }\n\n out += '\\n';\n return out;\n }\n\n private printDuration(ms: number): string {\n return chalk.dim(` Completed in ${ms}ms\\n`);\n }\n\n private printSuggestions(\n suggestions: Array<{ broken: string; suggested: string; similarity: number }>,\n ): string {\n let out = chalk.bold.cyan(`\\n💡 Suggestions (${suggestions.length})\\n\\n`);\n\n for (const suggestion of suggestions.slice(0, 5)) {\n const confidence = Math.round(suggestion.similarity * 100);\n const bar =\n confidence >= 80\n ? chalk.green('█████')\n : confidence >= 60\n ? chalk.yellow('█████')\n : chalk.gray('█████');\n\n out += ` ${chalk.gray(suggestion.broken)} → ${chalk.cyan(suggestion.suggested)}\\n`;\n out += ` ${bar} ${confidence}%\\n\\n`;\n }\n\n return out;\n }\n}\n","import type { AnalysisReport } from '../types/index.js';\n\nexport class JsonReporter {\n report(result: AnalysisReport): string {\n return JSON.stringify(\n {\n stats: result.stats,\n brokenLinks: result.brokenLinks,\n orphanNotes: result.orphanNotes.map((n) => ({\n path: n.relativePath,\n })),\n graph: result.graph.map((n) => ({\n note: n.note.relativePath,\n incoming: n.incoming.length,\n outgoing: n.outgoing.length,\n })),\n duration: result.duration,\n },\n null,\n 2,\n );\n }\n}\n","import type { AnalysisReport } from '../types/index.js';\r\n\r\nexport class CsvReporter {\r\n report(result: AnalysisReport): string {\r\n const lines: string[] = [];\r\n\r\n // Broken Links CSV\r\n lines.push('# BROKEN LINKS');\r\n lines.push('Source,Target,Type,Line,Reason');\r\n\r\n for (const broken of result.brokenLinks) {\r\n const source = this.escapeCsv(broken.source);\r\n const target = this.escapeCsv(broken.link.target);\r\n const type = broken.link.type;\r\n const line = broken.link.line;\r\n const reason = this.escapeCsv(broken.reason);\r\n\r\n lines.push(`${source},${target},${type},${line},\"${reason}\"`);\r\n }\r\n\r\n // Orphan Notes CSV\r\n lines.push('\\n# ORPHAN NOTES');\r\n lines.push('Note,Path');\r\n\r\n for (const orphan of result.orphanNotes) {\r\n const name = this.escapeCsv(orphan.relativePath);\r\n lines.push(`${name},\"${name}\"`);\r\n }\r\n\r\n // Graph Stats CSV\r\n lines.push('\\n# GRAPH STATISTICS');\r\n lines.push('Metric,Value');\r\n lines.push(`Total Notes,${result.stats.totalNotes}`);\r\n lines.push(`Total Links,${result.stats.totalLinks}`);\r\n lines.push(`Broken Links,${result.stats.brokenCount}`);\r\n lines.push(`Orphan Notes,${result.stats.orphanCount}`);\r\n lines.push(`Connected Components,${result.stats.connectedComponents}`);\r\n lines.push(`Duration (ms),${result.duration}`);\r\n\r\n return lines.join('\\n');\r\n }\r\n\r\n private escapeCsv(value: string): string {\r\n if (value.includes(',') || value.includes('\"') || value.includes('\\n')) {\r\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\r\n }\r\n return value;\r\n }\r\n}\r\n","import type { AnalysisReport } from '../types/index.js';\r\n\r\nexport class HtmlReporter {\r\n report(result: AnalysisReport): string {\r\n const brokenLinksHtml = this.renderBrokenLinks(result.brokenLinks);\r\n const orphanNotesHtml = this.renderOrphanNotes(result.orphanNotes);\r\n const graphHtml = this.renderGraph(result.graph);\r\n const statsHtml = this.renderStats(result.stats, result.duration);\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <title>Dead Links Report</title>\r\n <style>\r\n * { margin: 0; padding: 0; box-sizing: border-box; }\r\n body {\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n min-height: 100vh;\r\n padding: 20px;\r\n }\r\n .container {\r\n max-width: 1400px;\r\n margin: 0 auto;\r\n background: white;\r\n border-radius: 12px;\r\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\r\n overflow: hidden;\r\n }\r\n header {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n color: white;\r\n padding: 40px;\r\n text-align: center;\r\n }\r\n header h1 { font-size: 2.5em; margin-bottom: 10px; }\r\n header p { font-size: 1.1em; opacity: 0.9; }\r\n .content { padding: 40px; }\r\n .section {\r\n margin-bottom: 40px;\r\n border-left: 4px solid #667eea;\r\n padding-left: 20px;\r\n }\r\n .section h2 {\r\n font-size: 1.8em;\r\n margin-bottom: 20px;\r\n color: #333;\r\n }\r\n .stats-grid {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\r\n gap: 20px;\r\n margin-bottom: 40px;\r\n }\r\n .stat-card {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n color: white;\r\n padding: 20px;\r\n border-radius: 8px;\r\n text-align: center;\r\n }\r\n .stat-card .value { font-size: 2.5em; font-weight: bold; }\r\n .stat-card .label { font-size: 0.9em; opacity: 0.9; margin-top: 10px; }\r\n table {\r\n width: 100%;\r\n border-collapse: collapse;\r\n margin-top: 20px;\r\n }\r\n th {\r\n background: #f5f5f5;\r\n padding: 12px;\r\n text-align: left;\r\n border-bottom: 2px solid #667eea;\r\n font-weight: 600;\r\n }\r\n td {\r\n padding: 12px;\r\n border-bottom: 1px solid #eee;\r\n }\r\n tr:hover { background: #f9f9f9; }\r\n .badge {\r\n display: inline-block;\r\n padding: 4px 8px;\r\n border-radius: 4px;\r\n font-size: 0.85em;\r\n font-weight: 500;\r\n }\r\n .badge-wiki { background: #dbeafe; color: #1e40af; }\r\n .badge-embed { background: #fed7aa; color: #92400e; }\r\n .badge-markdown { background: #d1fae5; color: #065f46; }\r\n .error { color: #dc2626; }\r\n .success { color: #16a34a; }\r\n .tabs {\r\n display: flex;\r\n gap: 0;\r\n border-bottom: 2px solid #eee;\r\n margin-bottom: 20px;\r\n }\r\n .tab-button {\r\n padding: 12px 20px;\r\n border: none;\r\n background: none;\r\n cursor: pointer;\r\n font-size: 1em;\r\n border-bottom: 3px solid transparent;\r\n color: #666;\r\n transition: all 0.3s;\r\n }\r\n .tab-button.active {\r\n color: #667eea;\r\n border-bottom-color: #667eea;\r\n }\r\n .tab-content { display: none; }\r\n .tab-content.active { display: block; }\r\n .filter-box {\r\n margin: 20px 0;\r\n padding: 15px;\r\n background: #f9f9f9;\r\n border-radius: 8px;\r\n }\r\n .filter-box input {\r\n width: 100%;\r\n padding: 8px;\r\n border: 1px solid #ddd;\r\n border-radius: 4px;\r\n font-size: 1em;\r\n }\r\n footer {\r\n background: #f5f5f5;\r\n padding: 20px;\r\n text-align: center;\r\n color: #666;\r\n font-size: 0.9em;\r\n }\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"container\">\r\n <header>\r\n <h1>🔍 Dead Links Report</h1>\r\n <p>Obsidian Vault Analysis</p>\r\n </header>\r\n\r\n <div class=\"content\">\r\n ${statsHtml}\r\n ${brokenLinksHtml}\r\n ${orphanNotesHtml}\r\n ${graphHtml}\r\n </div>\r\n\r\n <footer>\r\n Generated on ${new Date().toLocaleString()}\r\n </footer>\r\n </div>\r\n\r\n <script>\r\n function setupTabs() {\r\n const buttons = document.querySelectorAll('.tab-button');\r\n const contents = document.querySelectorAll('.tab-content');\r\n\r\n buttons.forEach((button, index) => {\r\n button.addEventListener('click', () => {\r\n buttons.forEach(b => b.classList.remove('active'));\r\n contents.forEach(c => c.classList.remove('active'));\r\n button.classList.add('active');\r\n contents[index].classList.add('active');\r\n });\r\n });\r\n\r\n // Activate first tab\r\n if (buttons.length > 0) buttons[0].classList.add('active');\r\n if (contents.length > 0) contents[0].classList.add('active');\r\n }\r\n\r\n function setupFilter(tableId, inputId) {\r\n const input = document.getElementById(inputId);\r\n const table = document.getElementById(tableId);\r\n if (!input || !table) return;\r\n\r\n input.addEventListener('keyup', (e) => {\r\n const filter = e.target.value.toLowerCase();\r\n const rows = table.querySelectorAll('tbody tr');\r\n rows.forEach(row => {\r\n const text = row.textContent.toLowerCase();\r\n row.style.display = text.includes(filter) ? '' : 'none';\r\n });\r\n });\r\n }\r\n\r\n document.addEventListener('DOMContentLoaded', () => {\r\n setupTabs();\r\n setupFilter('broken-table', 'broken-filter');\r\n setupFilter('orphan-table', 'orphan-filter');\r\n });\r\n </script>\r\n</body>\r\n</html>`;\r\n }\r\n\r\n private renderStats(\r\n stats: {\r\n totalNotes: number;\r\n totalLinks: number;\r\n brokenCount: number;\r\n orphanCount: number;\r\n connectedComponents: number;\r\n },\r\n duration: number,\r\n ): string {\r\n const healthColor =\r\n stats.brokenCount === 0 && stats.orphanCount === 0 ? '#16a34a' : '#dc2626';\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>📊 Overview</h2>\r\n <div class=\"stats-grid\">\r\n <div class=\"stat-card\">\r\n <div class=\"value\">${stats.totalNotes}</div>\r\n <div class=\"label\">Total Notes</div>\r\n </div>\r\n <div class=\"stat-card\">\r\n <div class=\"value\">${stats.totalLinks}</div>\r\n <div class=\"label\">Total Links</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);\">\r\n <div class=\"value\">${stats.brokenCount}</div>\r\n <div class=\"label\">Broken Links</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\">\r\n <div class=\"value\">${stats.orphanCount}</div>\r\n <div class=\"label\">Orphan Notes</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, ${healthColor} 0%, ${healthColor === '#16a34a' ? '#15803d' : '#991b1b'} 100%);\">\r\n <div class=\"value\">${stats.connectedComponents}</div>\r\n <div class=\"label\">Connected Components</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);\">\r\n <div class=\"value\">${duration}ms</div>\r\n <div class=\"label\">Scan Duration</div>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n private renderBrokenLinks(brokenLinks: any[]): string {\r\n if (brokenLinks.length === 0) {\r\n return `\r\n <div class=\"section\">\r\n <h2>✅ Broken Links</h2>\r\n <p class=\"success\">No broken links found!</p>\r\n </div>\r\n `;\r\n }\r\n\r\n const rows = brokenLinks\r\n .map(\r\n (link) => `\r\n <tr>\r\n <td>${this.escapeHtml(link.source)}</td>\r\n <td>${this.escapeHtml(link.link.target)}</td>\r\n <td><span class=\"badge badge-${link.link.type}\">${link.link.type}</span></td>\r\n <td>${link.link.line}</td>\r\n <td>${this.escapeHtml(link.reason)}</td>\r\n </tr>\r\n `,\r\n )\r\n .join('');\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>🔴 Broken Links (${brokenLinks.length})</h2>\r\n <div class=\"filter-box\">\r\n <input type=\"text\" id=\"broken-filter\" placeholder=\"Filter by source or target...\" />\r\n </div>\r\n <table id=\"broken-table\">\r\n <thead>\r\n <tr>\r\n <th>Source</th>\r\n <th>Target</th>\r\n <th>Type</th>\r\n <th>Line</th>\r\n <th>Reason</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n ${rows}\r\n </tbody>\r\n </table>\r\n </div>\r\n `;\r\n }\r\n\r\n private renderOrphanNotes(orphanNotes: any[]): string {\r\n if (orphanNotes.length === 0) {\r\n return `\r\n <div class=\"section\">\r\n <h2>✅ Orphan Notes</h2>\r\n <p class=\"success\">No orphan notes found!</p>\r\n </div>\r\n `;\r\n }\r\n\r\n const rows = orphanNotes\r\n .map(\r\n (note) => `\r\n <tr>\r\n <td>${this.escapeHtml(note.relativePath)}</td>\r\n </tr>\r\n `,\r\n )\r\n .join('');\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>🟡 Orphan Notes (${orphanNotes.length})</h2>\r\n <div class=\"filter-box\">\r\n <input type=\"text\" id=\"orphan-filter\" placeholder=\"Filter by path...\" />\r\n </div>\r\n <table id=\"orphan-table\">\r\n <thead>\r\n <tr>\r\n <th>Path</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n ${rows}\r\n </tbody>\r\n </table>\r\n </div>\r\n `;\r\n }\r\n\r\n private renderGraph(graph: any[]): string {\r\n const mostConnected = [...graph]\r\n .sort((a, b) => (b.incoming.length + b.outgoing.length) - (a.incoming.length + a.outgoing.length))\r\n .slice(0, 5);\r\n\r\n const rows = mostConnected\r\n .map(\r\n (node) => `\r\n <tr>\r\n <td>${this.escapeHtml(node.note.relativePath)}</td>\r\n <td>${node.incoming.length}</td>\r\n <td>${node.outgoing.length}</td>\r\n <td>${node.incoming.length + node.outgoing.length}</td>\r\n </tr>\r\n `,\r\n )\r\n .join('');\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>🌐 Most Connected Notes</h2>\r\n <table>\r\n <thead>\r\n <tr>\r\n <th>Note</th>\r\n <th>Incoming</th>\r\n <th>Outgoing</th>\r\n <th>Total</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n ${rows}\r\n </tbody>\r\n </table>\r\n </div>\r\n `;\r\n }\r\n\r\n private escapeHtml(text: string): string {\r\n return text\r\n .replace(/&/g, '&')\r\n .replace(/</g, '<')\r\n .replace(/>/g, '>')\r\n .replace(/\"/g, '"')\r\n .replace(/'/g, ''');\r\n }\r\n}\r\n","import type { AnalysisReport } from '../types/index.js';\r\n\r\nexport class MermaidReporter {\r\n report(result: AnalysisReport, maxNodes = 50): string {\r\n let output = 'graph LR\\n';\r\n\r\n // Limit nodes for readability\r\n const nodesToShow = result.graph.slice(0, maxNodes);\r\n const nodeMap = new Map(nodesToShow.map((n) => [n.note.path, n.note.relativePath]));\r\n\r\n // Add nodes\r\n for (const node of nodesToShow) {\r\n const id = this.sanitizeId(node.note.relativePath);\r\n const label = this.escapeLabel(node.note.relativePath);\r\n\r\n if (result.orphanNotes.some((o) => o.path === node.note.path)) {\r\n output += ` ${id}[\"<b>🔴 ${label}</b>\"]:::orphan\\n`;\r\n } else if (node.incoming.length === 0) {\r\n output += ` ${id}[\"📄 ${label}\"]:::source\\n`;\r\n } else if (node.outgoing.length === 0) {\r\n output += ` ${id}[\"📌 ${label}\"]:::sink\\n`;\r\n } else {\r\n output += ` ${id}[\"${label}\"]\\n`;\r\n }\r\n }\r\n\r\n // Add edges\r\n const addedEdges = new Set<string>();\r\n for (const node of nodesToShow) {\r\n const sourceId = this.sanitizeId(node.note.relativePath);\r\n\r\n for (const targetPath of node.outgoing) {\r\n const targetName = nodeMap.get(targetPath);\r\n if (!targetName) continue;\r\n\r\n const targetId = this.sanitizeId(targetName);\r\n const edgeKey = `${sourceId}-${targetId}`;\r\n\r\n if (!addedEdges.has(edgeKey)) {\r\n output += ` ${sourceId} --> ${targetId}\\n`;\r\n addedEdges.add(edgeKey);\r\n }\r\n }\r\n }\r\n\r\n // Add styling\r\n output += '\\n classDef orphan fill:#ff6b6b,stroke:#c92a2a,color:#fff\\n';\r\n output += ' classDef source fill:#4dabf7,stroke:#1971c2,color:#fff\\n';\r\n output += ' classDef sink fill:#51cf66,stroke:#2f9e44,color:#fff\\n';\r\n\r\n if (result.graph.length > maxNodes) {\r\n output += `\\n note[\"Graph limited to ${maxNodes} of ${result.graph.length} nodes for readability\"]\\n`;\r\n }\r\n\r\n return output;\r\n }\r\n\r\n private sanitizeId(name: string): string {\r\n return name\r\n .replace(/\\.(md|canvas)$/, '')\r\n .replace(/[^a-zA-Z0-9_]/g, '_')\r\n .substring(0, 30);\r\n }\r\n\r\n private escapeLabel(name: string): string {\r\n return name.replace(/\"/g, '\\\\\"').substring(0, 50);\r\n }\r\n}\r\n","import type { AnalysisReport } from '../types/index.js';\r\n\r\nexport class GraphVizReporter {\r\n report(result: AnalysisReport, maxNodes = 100): string {\r\n let output = 'digraph VaultGraph {\\n';\r\n output += ' rankdir=LR;\\n';\r\n output += ' node [shape=box, style=rounded];\\n';\r\n output += ' edge [arrowsize=0.5];\\n\\n';\r\n\r\n const nodesToShow = result.graph.slice(0, maxNodes);\r\n const nodeMap = new Map(nodesToShow.map((n) => [n.note.path, n.note.relativePath]));\r\n\r\n // Add nodes with styling\r\n for (const node of nodesToShow) {\r\n const id = this.sanitizeId(node.note.relativePath);\r\n const label = node.note.relativePath;\r\n let style = 'filled';\r\n let color = '#ffffff';\r\n let fontColor = '#000000';\r\n let shape = 'box';\r\n\r\n if (result.orphanNotes.some((o) => o.path === node.note.path)) {\r\n color = '#ff6b6b';\r\n fontColor = '#ffffff';\r\n shape = 'box';\r\n } else if (node.incoming.length === 0) {\r\n color = '#4dabf7';\r\n fontColor = '#ffffff';\r\n } else if (node.outgoing.length === 0) {\r\n color = '#51cf66';\r\n fontColor = '#ffffff';\r\n }\r\n\r\n output += ` \"${id}\" [label=\"${label}\", fillcolor=\"${color}\", fontcolor=\"${fontColor}\", shape=${shape}];\\n`;\r\n }\r\n\r\n output += '\\n';\r\n\r\n // Add edges\r\n const addedEdges = new Set<string>();\r\n for (const node of nodesToShow) {\r\n const sourceId = this.sanitizeId(node.note.relativePath);\r\n\r\n for (const targetPath of node.outgoing) {\r\n const targetName = nodeMap.get(targetPath);\r\n if (!targetName) continue;\r\n\r\n const targetId = this.sanitizeId(targetName);\r\n const edgeKey = `${sourceId}->${targetId}`;\r\n\r\n if (!addedEdges.has(edgeKey)) {\r\n output += ` \"${sourceId}\" -> \"${targetId}\";\\n`;\r\n addedEdges.add(edgeKey);\r\n }\r\n }\r\n }\r\n\r\n output += '\\n // Legend\\n';\r\n output += ' subgraph legend {\\n';\r\n output += ' rank=sink;\\n';\r\n output += ' orphan [label=\"🔴 Orphan\", fillcolor=\"#ff6b6b\", fontcolor=\"white\"];\\n';\r\n output += ' source [label=\"📄 Source (no incoming)\", fillcolor=\"#4dabf7\", fontcolor=\"white\"];\\n';\r\n output += ' sink [label=\"📌 Sink (no outgoing)\", fillcolor=\"#51cf66\", fontcolor=\"white\"];\\n';\r\n output += ' }\\n';\r\n\r\n output += '}\\n';\r\n\r\n return output;\r\n }\r\n\r\n private sanitizeId(name: string): string {\r\n return name.replace(/\\.(md|canvas)$/, '').replace(/[^a-zA-Z0-9_]/g, '_');\r\n }\r\n}\r\n","import type { AnalysisReport } from '../types/index.js';\n\nfunction pill(target: string, isMultiple: boolean): string {\n return isMultiple ? `[${target}]` : `[${target}]`;\n}\n\nfunction bar(count: number, max: number, width: number): string {\n const filled = Math.round((count / max) * width);\n const empty = width - filled;\n return '█'.repeat(filled) + '░'.repeat(empty);\n}\n\nexport function generateMarkdownReport(report: AnalysisReport): string {\n const lines: string[] = [];\n\n lines.push('# Dead Links Report');\n lines.push('');\n lines.push(`Generated: ${new Date().toISOString()}`);\n lines.push(`Completed in ${report.duration}ms`);\n lines.push('');\n\n lines.push('## Stats');\n lines.push('');\n lines.push(`| Metric | Value |`);\n lines.push(`|--------|-------|`);\n lines.push(`| Notes | ${report.stats.totalNotes} |`);\n lines.push(`| Links | ${report.stats.totalLinks} |`);\n lines.push(`| Broken | ${report.stats.brokenCount} |`);\n lines.push(`| Orphans | ${report.stats.orphanCount} |`);\n lines.push(`| Components | ${report.stats.connectedComponents} |`);\n lines.push('');\n\n lines.push('## Broken Links');\n lines.push('');\n\n if (report.brokenLinks.length === 0) {\n lines.push('No broken links found.');\n } else {\n const grouped = new Map<string, typeof report.brokenLinks>();\n for (const b of report.brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n\n const sorted = [...grouped.entries()].sort((a, b) => b[1].length - a[1].length);\n\n for (const [source, links] of sorted) {\n const count = links.length;\n lines.push(`### ${count > 1 ? '✖' : '✖'} ${count} ${source}`);\n lines.push('');\n for (const link of links) {\n lines.push(`- ${pill(link.link.target, count > 1)}`);\n }\n lines.push('');\n }\n }\n\n lines.push('## Orphan Notes');\n lines.push('');\n\n if (report.orphanNotes.length === 0) {\n lines.push('No orphan notes found.');\n } else {\n const byFolder = new Map<string, number>();\n for (const note of report.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n\n const sorted = [...byFolder.entries()].sort((a, b) => b[1] - a[1]);\n const maxCount = sorted[0]?.[1] ?? 1;\n\n for (const [folder, count] of sorted) {\n lines.push(`- ${folder.padEnd(40)} ${bar(count, maxCount, 10)} ${count}`);\n }\n }\n\n lines.push('');\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["readFile","resolve","pill","bar"]}
|
package/package.json
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dead-links",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI tool to scan Obsidian vaults for broken links, orphan notes, and connection graphs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"dead-links": "
|
|
7
|
+
"dead-links": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"dev": "tsup --watch",
|
|
15
|
+
"test": "vitest",
|
|
16
|
+
"lint": "eslint .",
|
|
17
|
+
"format": "prettier --write ."
|
|
18
|
+
},
|
|
12
19
|
"keywords": [
|
|
13
20
|
"obsidian",
|
|
14
21
|
"vault",
|
|
@@ -23,7 +30,7 @@
|
|
|
23
30
|
"license": "MIT",
|
|
24
31
|
"repository": {
|
|
25
32
|
"type": "git",
|
|
26
|
-
"url": "https://github.com/offluisangel/dead-links.git"
|
|
33
|
+
"url": "git+https://github.com/offluisangel/dead-links.git"
|
|
27
34
|
},
|
|
28
35
|
"homepage": "https://github.com/offluisangel/dead-links#readme",
|
|
29
36
|
"bugs": {
|
|
@@ -32,6 +39,7 @@
|
|
|
32
39
|
"engines": {
|
|
33
40
|
"node": ">=22.0.0"
|
|
34
41
|
},
|
|
42
|
+
"packageManager": "pnpm@11.0.8",
|
|
35
43
|
"dependencies": {
|
|
36
44
|
"commander": "^14.0.3",
|
|
37
45
|
"fast-glob": "^3.3.3",
|
|
@@ -49,12 +57,5 @@
|
|
|
49
57
|
"tsup": "^8.5.1",
|
|
50
58
|
"typescript": "^6.0.3",
|
|
51
59
|
"vitest": "^4.1.6"
|
|
52
|
-
},
|
|
53
|
-
"scripts": {
|
|
54
|
-
"build": "tsup",
|
|
55
|
-
"dev": "tsup --watch",
|
|
56
|
-
"test": "vitest",
|
|
57
|
-
"lint": "eslint .",
|
|
58
|
-
"format": "prettier --write ."
|
|
59
60
|
}
|
|
60
|
-
}
|
|
61
|
+
}
|