param( [string]$Version = "1.0.1" ) $ErrorActionPreference = "Stop" $root = Split-Path -Parent $MyInvocation.MyCommand.Path $filesRoot = Join-Path $root "files" $deleteFile = Join-Path $root "delete.txt" $outFile = Join-Path $root "manifest.json" if (!(Test-Path $filesRoot)) { New-Item -ItemType Directory -Path $filesRoot | Out-Null } $items = @() Get-ChildItem -Path $filesRoot -File -Recurse | Sort-Object FullName | ForEach-Object { $relative = $_.FullName.Substring($filesRoot.Length).TrimStart('\','/') -replace '\','/' $hash = (Get-FileHash -Algorithm SHA256 -Path $_.FullName).Hash.ToLowerInvariant() $items += [ordered]@{ Path = $relative Url = "files/$relative" Sha256 = $hash Size = $_.Length } } # delete.txt e opcional. Um caminho relativo do CLIENTE por linha. # Linhas vazias e linhas iniciadas por # sao ignoradas. $deletes = @() if (Test-Path $deleteFile) { foreach ($rawLine in (Get-Content -Path $deleteFile)) { $entry = $rawLine.Trim() if ([string]::IsNullOrWhiteSpace($entry)) { continue } if ($entry.StartsWith("#")) { continue } $entry = $entry -replace '\\','/' $entry = $entry.TrimStart('/') if ($entry -match '(^|/)\.\.(/|$)') { throw "Caminho inseguro em delete.txt: $entry" } $sameAsCurrentFile = $false foreach ($f in $items) { if ([string]::Equals([string]$f.Path, $entry, [System.StringComparison]::OrdinalIgnoreCase)) { $sameAsCurrentFile = $true break } } if ($sameAsCurrentFile) { Write-Host "IGNORADO no Delete (arquivo existe em files): $entry" -ForegroundColor Yellow } elseif ($deletes -notcontains $entry) { $deletes += $entry } } } $manifest = [ordered]@{ Version = $Version Files = $items Delete = $deletes } $manifest | ConvertTo-Json -Depth 6 | Set-Content -Path $outFile -Encoding UTF8 Write-Host "Manifesto criado: $outFile" -ForegroundColor Green Write-Host "Versao: $Version | Arquivos: $($items.Count) | Exclusoes: $($deletes.Count)" -ForegroundColor Cyan