81 lines
3.8 KiB
PowerShell
81 lines
3.8 KiB
PowerShell
param(
|
|
[string]$ContentRoot
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
|
|
if (-not $ContentRoot) {
|
|
$ContentRoot = Join-Path $repoRoot "Kalsa"
|
|
}
|
|
$contentFull = [System.IO.Path]::GetFullPath($ContentRoot)
|
|
if (-not (Test-Path -LiteralPath $contentFull -PathType Container)) {
|
|
throw "Canonical content root not found: $contentFull"
|
|
}
|
|
|
|
$notes = @(Get-ChildItem -LiteralPath $contentFull -Recurse -File -Filter '*.md')
|
|
$allFiles = @(Get-ChildItem -LiteralPath $contentFull -Recurse -File)
|
|
$noteByStem = @{}
|
|
$noteByRelative = @{}
|
|
$fileByName = @{}
|
|
|
|
foreach ($note in $notes) {
|
|
$stem = [System.IO.Path]::GetFileNameWithoutExtension($note.Name).ToLowerInvariant()
|
|
if (-not $noteByStem.ContainsKey($stem)) { $noteByStem[$stem] = @() }
|
|
$noteByStem[$stem] += $note.FullName
|
|
$relative = $note.FullName.Substring($contentFull.Length).TrimStart('\', '/') -replace '\\', '/'
|
|
$noteByRelative[([System.IO.Path]::ChangeExtension($relative, $null).TrimEnd('.').ToLowerInvariant())] = $note.FullName
|
|
}
|
|
foreach ($file in $allFiles) {
|
|
$key = $file.Name.ToLowerInvariant()
|
|
if (-not $fileByName.ContainsKey($key)) { $fileByName[$key] = @() }
|
|
$fileByName[$key] += $file.FullName
|
|
}
|
|
|
|
$errors = @()
|
|
$linkCount = 0
|
|
foreach ($note in $notes) {
|
|
$text = Get-Content -Raw -LiteralPath $note.FullName -Encoding utf8
|
|
foreach ($match in [regex]::Matches($text, '!??\[\[(?<target>[^\]]+)\]\]')) {
|
|
$linkCount += 1
|
|
$target = ($match.Groups['target'].Value -split '\|', 2)[0]
|
|
$target = ($target -split '#', 2)[0].Trim()
|
|
if (-not $target) { continue }
|
|
$target = $target -replace '\\', '/'
|
|
$resolved = @()
|
|
$extension = [System.IO.Path]::GetExtension($target)
|
|
if ($extension -and $extension.ToLowerInvariant() -ne '.md') {
|
|
$relativeAsset = [System.IO.Path]::GetFullPath((Join-Path $note.DirectoryName $target))
|
|
$rootAsset = [System.IO.Path]::GetFullPath((Join-Path $contentFull $target))
|
|
if (Test-Path -LiteralPath $relativeAsset -PathType Leaf) { $resolved += $relativeAsset }
|
|
if (Test-Path -LiteralPath $rootAsset -PathType Leaf) { $resolved += $rootAsset }
|
|
$assetName = [System.IO.Path]::GetFileName($target).ToLowerInvariant()
|
|
if ($fileByName.ContainsKey($assetName)) { $resolved += $fileByName[$assetName] }
|
|
}
|
|
else {
|
|
$noteTarget = if ($extension) { [System.IO.Path]::ChangeExtension($target, $null).TrimEnd('.') } else { $target }
|
|
$relativeKey = $noteTarget.TrimStart('/')
|
|
if ($noteByRelative.ContainsKey($relativeKey.ToLowerInvariant())) {
|
|
$resolved += $noteByRelative[$relativeKey.ToLowerInvariant()]
|
|
}
|
|
$relativeCandidate = [System.IO.Path]::GetFullPath((Join-Path $note.DirectoryName ($noteTarget + '.md')))
|
|
if (Test-Path -LiteralPath $relativeCandidate -PathType Leaf) { $resolved += $relativeCandidate }
|
|
$stem = [System.IO.Path]::GetFileName($noteTarget).ToLowerInvariant()
|
|
if ($noteByStem.ContainsKey($stem)) { $resolved += $noteByStem[$stem] }
|
|
}
|
|
$resolved = @($resolved | Sort-Object -Unique)
|
|
$sourceRelative = $note.FullName.Substring($contentFull.Length).TrimStart('\', '/')
|
|
if ($resolved.Count -eq 0) {
|
|
$errors += "$sourceRelative -> [[$($match.Groups['target'].Value)]] is unresolved"
|
|
}
|
|
elseif ($resolved.Count -gt 1 -and $target -notmatch '[/\\]') {
|
|
$errors += "$sourceRelative -> [[$($match.Groups['target'].Value)]] is ambiguous across $($resolved.Count) files"
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($errors.Count -gt 0) {
|
|
throw "Wikilink verification failed:`n- $($errors -join "`n- ")"
|
|
}
|
|
|
|
Write-Output "Wikilink verification passed: $linkCount links across $($notes.Count) canonical notes."
|