182 lines
6.2 KiB
PowerShell
182 lines
6.2 KiB
PowerShell
|
|
# hooks/archive-check.ps1
|
|||
|
|
# Check changed .md files and suggest related docs to update.
|
|||
|
|
# This script only outputs suggestions; it does NOT modify any files.
|
|||
|
|
#
|
|||
|
|
# Mechanism:
|
|||
|
|
# For every changed .md file, parse its "## 文档关联" section and extract
|
|||
|
|
# Markdown links. Those links are the documents that may need syncing.
|
|||
|
|
|
|||
|
|
$ErrorActionPreference = "SilentlyContinue"
|
|||
|
|
|
|||
|
|
$scriptPath = $MyInvocation.MyCommand.Path
|
|||
|
|
if (-not [System.IO.Path]::IsPathRooted($scriptPath)) {
|
|||
|
|
$scriptPath = Join-Path (Get-Location) $scriptPath
|
|||
|
|
}
|
|||
|
|
$scriptPath = [System.IO.Path]::GetFullPath($scriptPath)
|
|||
|
|
$scriptDir = Split-Path -Parent $scriptPath
|
|||
|
|
$projectRoot = [System.IO.Path]::GetFullPath((Join-Path $scriptDir ".."))
|
|||
|
|
|
|||
|
|
Set-Location $projectRoot
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# Helpers
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
function Resolve-RelativePath {
|
|||
|
|
param(
|
|||
|
|
[string]$BaseFile,
|
|||
|
|
[string]$LinkPath
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Skip anchors, query strings, and external URLs
|
|||
|
|
if ($LinkPath -match '^[a-zA-Z][a-zA-Z0-9+.-]*://') { return $null }
|
|||
|
|
if ([string]::IsNullOrWhiteSpace($LinkPath)) { return $null }
|
|||
|
|
|
|||
|
|
# Drop fragment
|
|||
|
|
$pathOnly = ($LinkPath -split '#')[0]
|
|||
|
|
if ([string]::IsNullOrWhiteSpace($pathOnly)) { return $null }
|
|||
|
|
|
|||
|
|
$baseDir = Split-Path -Parent $BaseFile
|
|||
|
|
$combined = Join-Path $baseDir $pathOnly
|
|||
|
|
try {
|
|||
|
|
$resolved = [System.IO.Path]::GetFullPath($combined)
|
|||
|
|
} catch {
|
|||
|
|
return $null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Convert to relative path from project root, normalizing separators
|
|||
|
|
$fullProjectRoot = [System.IO.Path]::GetFullPath($projectRoot)
|
|||
|
|
if ($resolved.StartsWith($fullProjectRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|||
|
|
$relative = $resolved.Substring($fullProjectRoot.Length).TrimStart('\', '/')
|
|||
|
|
return $relative -replace '\\', '/'
|
|||
|
|
}
|
|||
|
|
return $pathOnly -replace '\\', '/'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Get-DocumentAssociations {
|
|||
|
|
param(
|
|||
|
|
[string]$MdFile
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
$fullPath = Join-Path $projectRoot $MdFile
|
|||
|
|
if (-not (Test-Path $fullPath)) { return @() }
|
|||
|
|
|
|||
|
|
$lines = [System.IO.File]::ReadAllLines($fullPath, [System.Text.Encoding]::UTF8)
|
|||
|
|
$associations = @()
|
|||
|
|
$inSection = $false
|
|||
|
|
$inCodeBlock = $false
|
|||
|
|
|
|||
|
|
foreach ($line in $lines) {
|
|||
|
|
# Toggle fenced code block state (``` or ~~~)
|
|||
|
|
if ($line -match '^\s*```' -or $line -match '^\s*~~~') {
|
|||
|
|
$inCodeBlock = -not $inCodeBlock
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
if ($inCodeBlock) { continue }
|
|||
|
|
|
|||
|
|
# Match exactly '## 文档关联' (not '## 文档关联机制' etc.).
|
|||
|
|
if ($line -match "^##\s+文档关联\s*$") {
|
|||
|
|
$inSection = $true
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
if ($inSection -and $line -match '^##\s+') {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
if ($inSection) {
|
|||
|
|
# Extract markdown links: [text](path)
|
|||
|
|
$regex = '\[(?<text>[^\]]*)\]\((?<path>[^\)]+)\)'
|
|||
|
|
$matches = [regex]::Matches($line, $regex)
|
|||
|
|
foreach ($match in $matches) {
|
|||
|
|
$linkPath = $match.Groups['path'].Value.Trim()
|
|||
|
|
$resolved = Resolve-RelativePath -BaseFile $MdFile -LinkPath $linkPath
|
|||
|
|
if ($resolved) {
|
|||
|
|
$associations += $resolved
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $associations | Select-Object -Unique
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Test-WildcardMatch {
|
|||
|
|
param(
|
|||
|
|
[string]$Pattern,
|
|||
|
|
[string]$Path
|
|||
|
|
)
|
|||
|
|
$regex = "^" + ($Pattern -replace "\.", "\." -replace "\*", ".*" -replace "\?", ".") + "$"
|
|||
|
|
return $Path -match $regex
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# Collect changed .md files (tracked modifications + untracked new files)
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
$changedFiles = @(cmd /c "git diff --name-only 2>nul") | Where-Object { $_ }
|
|||
|
|
if ($changedFiles.Count -eq 0) {
|
|||
|
|
$changedFiles = @(cmd /c "git diff --name-only --cached 2>nul") | Where-Object { $_ }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Also include untracked .md files so new documents are checked too
|
|||
|
|
$untrackedFiles = @(cmd /c "git ls-files --others --exclude-standard 2>nul") | Where-Object { $_ }
|
|||
|
|
$allChangedFiles = @($changedFiles) + @($untrackedFiles) | Where-Object { $_ } | Select-Object -Unique
|
|||
|
|
|
|||
|
|
if (-not $allChangedFiles) {
|
|||
|
|
Write-Output "[archive-check] No documentation changes detected (git diff is empty and no untracked .md files)."
|
|||
|
|
exit 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$changedMdFiles = $allChangedFiles | Where-Object { $_ -like "*.md" } | Select-Object -Unique
|
|||
|
|
|
|||
|
|
if ($changedMdFiles.Count -eq 0) {
|
|||
|
|
Write-Output "[archive-check] No .md documentation changes detected."
|
|||
|
|
exit 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# Build suggestions from each changed file's "## 文档关联" section
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
$suggestions = @{}
|
|||
|
|
|
|||
|
|
foreach ($changed in $changedMdFiles) {
|
|||
|
|
$relatedDocs = Get-DocumentAssociations -MdFile $changed
|
|||
|
|
foreach ($related in $relatedDocs) {
|
|||
|
|
# Normalize related path
|
|||
|
|
$relatedNormalized = $related -replace '\\', '/'
|
|||
|
|
if (-not $suggestions.ContainsKey($relatedNormalized)) {
|
|||
|
|
$suggestions[$relatedNormalized] = @()
|
|||
|
|
}
|
|||
|
|
if ($suggestions[$relatedNormalized] -notcontains $changed) {
|
|||
|
|
$suggestions[$relatedNormalized] += $changed
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# Output results
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Write-Output "[archive-check] Changed documentation files:"
|
|||
|
|
foreach ($changed in $changedMdFiles | Sort-Object) {
|
|||
|
|
Write-Output " - $changed"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($suggestions.Count -eq 0) {
|
|||
|
|
Write-Output ""
|
|||
|
|
Write-Output "[archive-check] No associations found in changed files' `"## 文档关联`" sections."
|
|||
|
|
exit 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Write-Output ""
|
|||
|
|
Write-Output "[archive-check] Related docs that may need updating:"
|
|||
|
|
foreach ($related in $suggestions.Keys | Sort-Object) {
|
|||
|
|
$sources = $suggestions[$related] | Sort-Object
|
|||
|
|
$sourceList = $sources -join ", "
|
|||
|
|
Write-Output " - $related (mentioned by: $sourceList)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Write-Output ""
|
|||
|
|
Write-Output "[archive-check] Please confirm whether to sync-update the above docs."
|
|||
|
|
exit 0
|