first commit
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
param(
|
||||
[string]$OutputPath = "PROJECT_MD5_REFERENCE.md",
|
||||
[switch]$Verify
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$fallbackRoot = Resolve-Path -LiteralPath (Join-Path $scriptDir "..")
|
||||
|
||||
try {
|
||||
$gitRoot = git -C $fallbackRoot rev-parse --show-toplevel 2>$null
|
||||
if ([string]::IsNullOrWhiteSpace($gitRoot)) {
|
||||
$repoRoot = $fallbackRoot.Path
|
||||
}
|
||||
else {
|
||||
$repoRoot = (Resolve-Path -LiteralPath $gitRoot.Trim()).Path
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$repoRoot = $fallbackRoot.Path
|
||||
}
|
||||
|
||||
Push-Location $repoRoot
|
||||
try {
|
||||
$normalizedOutputPath = $OutputPath.Replace("\", "/")
|
||||
|
||||
$files = @()
|
||||
try {
|
||||
$files = git ls-files --cached --modified --others --exclude-standard |
|
||||
ForEach-Object { $_.Replace("\", "/") } |
|
||||
Where-Object { $_ -and $_ -ne $normalizedOutputPath }
|
||||
}
|
||||
catch {
|
||||
$ignoredDirectories = @(".git", ".vs", "bin", "obj", "publish", "Migrations")
|
||||
$ignoredExtensions = @(".user", ".suo", ".userosscache", ".sln.docstates", ".log", ".zip")
|
||||
|
||||
$files = Get-ChildItem -LiteralPath $repoRoot -Recurse -File |
|
||||
Where-Object {
|
||||
$relative = [System.IO.Path]::GetRelativePath($repoRoot, $_.FullName).Replace("\", "/")
|
||||
$parts = $relative -split "/"
|
||||
$extension = [System.IO.Path]::GetExtension($_.Name)
|
||||
|
||||
($ignoredDirectories | Where-Object { $parts -contains $_ }).Count -eq 0 -and
|
||||
($ignoredExtensions -notcontains $extension) -and
|
||||
$relative -ne $normalizedOutputPath
|
||||
} |
|
||||
ForEach-Object { [System.IO.Path]::GetRelativePath($repoRoot, $_.FullName).Replace("\", "/") }
|
||||
}
|
||||
|
||||
$files = $files | Sort-Object -Unique
|
||||
|
||||
$hashRows = foreach ($file in $files) {
|
||||
$fullPath = Join-Path $repoRoot $file
|
||||
if (Test-Path -LiteralPath $fullPath -PathType Leaf) {
|
||||
$item = Get-Item -LiteralPath $fullPath
|
||||
$hash = Get-FileHash -LiteralPath $fullPath -Algorithm MD5
|
||||
|
||||
[PSCustomObject]@{
|
||||
Path = $file
|
||||
Size = $item.Length
|
||||
MD5 = $hash.Hash.ToLowerInvariant()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$statusLines = @()
|
||||
try {
|
||||
$statusLines = git status --short |
|
||||
Where-Object {
|
||||
$statusPath = ($_ -replace "^[ MADRCU?!]{2}\s+", "").Replace("\", "/")
|
||||
$statusPath -ne $normalizedOutputPath
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$statusLines = @()
|
||||
}
|
||||
|
||||
if ($Verify) {
|
||||
if (-not (Test-Path -LiteralPath $OutputPath -PathType Leaf)) {
|
||||
throw "Cannot verify because $OutputPath does not exist."
|
||||
}
|
||||
|
||||
$expectedRows = Get-Content -LiteralPath $OutputPath |
|
||||
ForEach-Object {
|
||||
if ($_ -match "^\| (?<Path>.+?) \| (?<Size>\d+) \| (?<MD5>[a-f0-9]{32}) \|$") {
|
||||
[PSCustomObject]@{
|
||||
Path = $Matches.Path
|
||||
Size = [int64]$Matches.Size
|
||||
MD5 = $Matches.MD5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$expectedByPath = @{}
|
||||
foreach ($row in $expectedRows) {
|
||||
$expectedByPath[$row.Path] = $row
|
||||
}
|
||||
|
||||
$currentByPath = @{}
|
||||
foreach ($row in $hashRows) {
|
||||
$currentByPath[$row.Path] = $row
|
||||
}
|
||||
|
||||
$added = @($currentByPath.Keys | Where-Object { -not $expectedByPath.ContainsKey($_) } | Sort-Object)
|
||||
$removed = @($expectedByPath.Keys | Where-Object { -not $currentByPath.ContainsKey($_) } | Sort-Object)
|
||||
$changed = @(
|
||||
foreach ($path in $expectedByPath.Keys) {
|
||||
if ($currentByPath.ContainsKey($path) -and $currentByPath[$path].MD5 -ne $expectedByPath[$path].MD5) {
|
||||
"$path ($($expectedByPath[$path].MD5) -> $($currentByPath[$path].MD5))"
|
||||
}
|
||||
}
|
||||
) | Sort-Object
|
||||
|
||||
if ($added.Count -eq 0 -and $removed.Count -eq 0 -and $changed.Count -eq 0) {
|
||||
Write-Host "MD5 reference verified. No changes detected across $($hashRows.Count) files."
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "MD5 reference verification found differences."
|
||||
if ($added.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "Added files:"
|
||||
$added | ForEach-Object { Write-Host " $_" }
|
||||
}
|
||||
if ($removed.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "Removed files:"
|
||||
$removed | ForEach-Object { Write-Host " $_" }
|
||||
}
|
||||
if ($changed.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "Changed files:"
|
||||
$changed | ForEach-Object { Write-Host " $_" }
|
||||
}
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
$projectName = Split-Path -Leaf $repoRoot
|
||||
$generatedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss zzz"
|
||||
$targetFramework = ""
|
||||
if (Test-Path -LiteralPath "Fitness_core_BE.csproj") {
|
||||
$projectXml = [xml](Get-Content -Raw -LiteralPath "Fitness_core_BE.csproj")
|
||||
$targetFramework = $projectXml.Project.PropertyGroup.TargetFramework
|
||||
}
|
||||
|
||||
$topLevelCounts = $hashRows |
|
||||
Group-Object {
|
||||
if ($_.Path.Contains("/")) {
|
||||
($_.Path -split "/")[0]
|
||||
}
|
||||
else {
|
||||
"."
|
||||
}
|
||||
} |
|
||||
Sort-Object Name
|
||||
|
||||
$lines = [System.Collections.Generic.List[string]]::new()
|
||||
$lines.Add("# Project MD5 Reference")
|
||||
$lines.Add("")
|
||||
$lines.Add("Generated: $generatedAt")
|
||||
$lines.Add("")
|
||||
$lines.Add("Project: $projectName")
|
||||
if (-not [string]::IsNullOrWhiteSpace($targetFramework)) {
|
||||
$lines.Add("")
|
||||
$lines.Add("Target framework: $targetFramework")
|
||||
}
|
||||
$lines.Add("")
|
||||
$lines.Add("Purpose: baseline MD5 hashes for future implementation checks. MD5 is used here for change detection, not for security-sensitive integrity verification.")
|
||||
$lines.Add("")
|
||||
$lines.Add('Manifest scope: Git tracked files plus untracked files not ignored by `.gitignore`; build outputs, IDE files, ignored migrations, and this manifest are excluded.')
|
||||
$lines.Add("")
|
||||
$lines.Add("Regenerate command:")
|
||||
$lines.Add("")
|
||||
$lines.Add('```powershell')
|
||||
$lines.Add('.\tools\Update-Md5Reference.ps1')
|
||||
$lines.Add('```')
|
||||
$lines.Add("")
|
||||
$lines.Add("Verify command:")
|
||||
$lines.Add("")
|
||||
$lines.Add('```powershell')
|
||||
$lines.Add('.\tools\Update-Md5Reference.ps1 -Verify')
|
||||
$lines.Add('```')
|
||||
$lines.Add("")
|
||||
$lines.Add("## Project Shape")
|
||||
$lines.Add("")
|
||||
$lines.Add("| Area | Files |")
|
||||
$lines.Add("| --- | ---: |")
|
||||
foreach ($group in $topLevelCounts) {
|
||||
$lines.Add("| $($group.Name) | $($group.Count) |")
|
||||
}
|
||||
$lines.Add("")
|
||||
$lines.Add("## Git Status At Baseline")
|
||||
$lines.Add("")
|
||||
if ($statusLines.Count -gt 0) {
|
||||
$lines.Add('```text')
|
||||
foreach ($status in $statusLines) {
|
||||
$lines.Add($status)
|
||||
}
|
||||
$lines.Add('```')
|
||||
}
|
||||
else {
|
||||
$lines.Add("Clean working tree or Git status unavailable.")
|
||||
}
|
||||
$lines.Add("")
|
||||
$lines.Add("## MD5 Hashes")
|
||||
$lines.Add("")
|
||||
$lines.Add("| Path | Size Bytes | MD5 |")
|
||||
$lines.Add("| --- | ---: | --- |")
|
||||
foreach ($row in $hashRows) {
|
||||
$lines.Add("| $($row.Path) | $($row.Size) | $($row.MD5) |")
|
||||
}
|
||||
$lines.Add("")
|
||||
|
||||
Set-Content -LiteralPath $OutputPath -Value $lines -Encoding UTF8
|
||||
Write-Host "Wrote $OutputPath with $($hashRows.Count) MD5 hashes."
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
Reference in New Issue
Block a user