<# .SYNOPSIS Automatyczne budowanie paczki aktualizacji shopPRO na podstawie git diff miedzy tagami. .DESCRIPTION Skrypt porownuje dwa tagi git, filtruje pliki przez .updateignore, tworzy ZIP + manifest JSON i aktualizuje changelog.php oraz versions.php. .PARAMETER FromTag Tag poczatkowy (np. v0.299). Domyslnie: ostatni tag. .PARAMETER ToTag Tag docelowy (np. v0.300). Wymagany. .PARAMETER ChangelogEntry Wpis do changelogu. Wymagany (chyba ze -DryRun). .PARAMETER DryRun Tylko pokaz co zostaloby zrobione, bez tworzenia plikow. .EXAMPLE ./build-update.ps1 -ToTag v0.300 -ChangelogEntry "NEW - Manifest-based update system" ./build-update.ps1 -FromTag v0.299 -ToTag v0.300 -ChangelogEntry "NEW - opis" -DryRun #> param( [string]$FromTag = "", [Parameter(Mandatory=$true)] [string]$ToTag, [string]$ChangelogEntry = "", [switch]$DryRun ) $ErrorActionPreference = "Stop" $Utf8NoBom = New-Object System.Text.UTF8Encoding $false # --- Helpers --- function Write-Step($msg) { Write-Host " [*] $msg" -ForegroundColor Cyan } function Write-Ok($msg) { Write-Host " [OK] $msg" -ForegroundColor Green } function Write-Warn($msg) { Write-Host " [!] $msg" -ForegroundColor Yellow } function Write-Err($msg) { Write-Host " [ERROR] $msg" -ForegroundColor Red } # --- 1. Walidacja tagow --- Write-Host "`n=== shopPRO Build Update ===" -ForegroundColor White if (-not $FromTag) { $FromTag = (git describe --tags --abbrev=0 2>$null) if (-not $FromTag) { Write-Err "Nie znaleziono zadnego taga. Uzyj parametru -FromTag." exit 1 } Write-Step "Auto-detect FromTag: $FromTag" } # Sprawdz czy tagi istnieja $tagExists = git tag -l $FromTag if (-not $tagExists) { Write-Err "Tag '$FromTag' nie istnieje." exit 1 } $toTagExists = git tag -l $ToTag if (-not $toTagExists) { Write-Warn "Tag '$ToTag' nie istnieje. Uzywam HEAD jako punktu docelowego." $diffTarget = "HEAD" } else { $diffTarget = $ToTag } # --- 2. Wersja i katalog --- $versionNumber = $ToTag -replace '^v', '' $versionInt = [int]($versionNumber -replace '^0\.', '') # Oblicz katalog: 0.001-0.009 -> 0.00, 0.010-0.099 -> 0.00, 0.100-0.199 -> 0.10, 0.200-0.299 -> 0.20, 0.300-0.399 -> 0.30 $dirTens = [math]::Floor($versionInt / 100) $dirStr = "0.{0}0" -f ($dirTens * 10).ToString().PadLeft(1, '0') # Format: jesli dirTens < 10 to "0.X0", np 0.00, 0.10, 0.20, 0.30 if ($dirTens -lt 10) { $dirStr = "0.{0}0" -f $dirTens.ToString().PadLeft(1, '0') } else { $dirStr = "0.{0}0" -f $dirTens } Write-Step "Wersja: $versionNumber (int: $versionInt)" Write-Step "Katalog: updates/$dirStr/" # --- 3. Git diff --- Write-Step "Porownywanie: $FromTag..$diffTarget" $diffOutput = git diff --name-status "$FromTag..$diffTarget" 2>&1 if ($LASTEXITCODE -ne 0) { Write-Err "git diff nie powiodl sie: $diffOutput" exit 1 } $addedFiles = @() $modifiedFiles = @() $deletedFiles = @() $renamedFiles = @() foreach ($line in ($diffOutput -split "`n")) { $line = $line.Trim() if (-not $line) { continue } $parts = $line -split "`t" $status = $parts[0] $filePath = if ($parts.Count -gt 1) { $parts[1] } else { "" } # Zamien backslash na slash $filePath = $filePath -replace '\\', '/' switch -Wildcard ($status) { "A" { $addedFiles += $filePath } "M" { $modifiedFiles += $filePath } "D" { $deletedFiles += $filePath } "R*" { # Rename: stary plik = deleted, nowy = added $newPath = if ($parts.Count -gt 2) { $parts[2] -replace '\\', '/' } else { "" } $deletedFiles += $filePath if ($newPath) { $addedFiles += $newPath } } } } Write-Step "Zmiany: A=$($addedFiles.Count), M=$($modifiedFiles.Count), D=$($deletedFiles.Count)" # --- 4. Filtrowanie przez .updateignore --- $ignorePatterns = @() $ignoreFile = ".updateignore" if (Test-Path $ignoreFile) { $ignorePatterns = Get-Content $ignoreFile | ForEach-Object { $_.Trim() } | Where-Object { $_ -and ($_ -notmatch '^\s*#') } Write-Step "Zaladowano $($ignorePatterns.Count) wzorcow z .updateignore" } function Test-Ignored { param([string]$FilePath) foreach ($pattern in $ignorePatterns) { # Wzorzec katalogu (konczy sie na /) if ($pattern.EndsWith('/')) { $dirPattern = $pattern.TrimEnd('/') if ($FilePath -like "$dirPattern/*" -or $FilePath -eq $dirPattern) { return $true } } # Wzorzec z wildcard elseif ($pattern.Contains('*')) { # *.md -> dopasuj nazwe pliku if ($pattern.StartsWith('*')) { $fileName = Split-Path $FilePath -Leaf if ($fileName -like $pattern) { return $true } } # Zwykly glob elseif ($FilePath -like $pattern) { return $true } } # Dokladne dopasowanie else { if ($FilePath -eq $pattern) { return $true } } } return $false } $filteredAdded = @() $filteredModified = @() $filteredDeleted = @() $ignoredCount = 0 foreach ($f in $addedFiles) { if (Test-Ignored $f) { $ignoredCount++; continue } $filteredAdded += $f } foreach ($f in $modifiedFiles) { if (Test-Ignored $f) { $ignoredCount++; continue } $filteredModified += $f } foreach ($f in $deletedFiles) { if (Test-Ignored $f) { $ignoredCount++; continue } $filteredDeleted += $f } Write-Step "Po filtrowaniu: A=$($filteredAdded.Count), M=$($filteredModified.Count), D=$($filteredDeleted.Count) (pominieto: $ignoredCount)" # Rozdziel usuniete pliki i katalogi $deletedDirs = @() $deletedFilesOnly = @() foreach ($f in $filteredDeleted) { # Sprawdz czy to byl katalog (w git nie ma katalogow, ale mozemy sprawdzic po wzorcu) # Git nie trackuje pustych katalogow, wiec deleted entries to zawsze pliki $deletedFilesOnly += $f } # --- 5. Odczyt migracji SQL --- $sqlQueries = @() $migrationFile = "migrations/$versionNumber.sql" if (Test-Path $migrationFile) { # Read entire file, strip comment lines, split by semicolons to get complete SQL statements $rawLines = Get-Content $migrationFile | Where-Object { $_.Trim() -ne '' -and $_.Trim() -notmatch '^\s*--' } $rawSql = ($rawLines -join "`n").Trim() $sqlQueries = @($rawSql -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } | ForEach-Object { $_.ToString() }) Write-Step "Znaleziono migracje SQL: $migrationFile ($($sqlQueries.Count) zapytan)" } else { Write-Step "Brak migracji SQL ($migrationFile nie istnieje)" } # --- 6. Podsumowanie --- $filesToPack = $filteredAdded + $filteredModified if ($filesToPack.Count -eq 0 -and $filteredDeleted.Count -eq 0 -and $sqlQueries.Count -eq 0) { Write-Warn "Brak zmian do pakowania (po filtrowaniu). Przerywam." exit 0 } Write-Host "`n--- Pliki do paczki ---" -ForegroundColor White foreach ($f in $filteredAdded) { Write-Host " A $f" -ForegroundColor Green } foreach ($f in $filteredModified) { Write-Host " M $f" -ForegroundColor Yellow } foreach ($f in $filteredDeleted) { Write-Host " D $f" -ForegroundColor Red } if ($sqlQueries.Count -gt 0) { Write-Host "`n--- SQL ---" -ForegroundColor White foreach ($q in $sqlQueries) { Write-Host " $q" -ForegroundColor Gray } } # --- DryRun? --- if ($DryRun) { Write-Host "`n[DRY RUN] Zadne pliki nie zostaly utworzone.`n" -ForegroundColor Magenta exit 0 } # --- 7. Walidacja ChangelogEntry --- if (-not $ChangelogEntry) { Write-Err "Parametr -ChangelogEntry jest wymagany (chyba ze uzywasz -DryRun)." exit 1 } # --- 8. Tworzenie temp i kopiowanie plikow --- $tempDir = "$env:TEMP\shopPRO_build_$versionInt" if (Test-Path $tempDir) { Remove-Item -Recurse -Force $tempDir } if (-not (Test-Path $tempDir)) { New-Item -ItemType Directory -Path $tempDir -Force | Out-Null } foreach ($f in $filesToPack) { $destPath = Join-Path $tempDir $f $destDir = Split-Path $destPath -Parent if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null } if (Test-Path $f) { Copy-Item $f $destPath -Force } else { Write-Warn "Plik nie istnieje (moze zostal usuniety po TAGU): $f" } } Write-Ok "Skopiowano $($filesToPack.Count) plikow do $tempDir" # --- 9. Tworzenie ZIP --- $updatesDir = "updates/$dirStr" if (-not (Test-Path $updatesDir)) { New-Item -ItemType Directory -Path $updatesDir -Force | Out-Null } $zipPath = "$updatesDir/ver_$versionNumber.zip" if (Test-Path $zipPath) { Remove-Item $zipPath -Force } # Pakuj zawartosc temp dir (bez folderu temp/) $originalLocation = Get-Location $absoluteZipPath = Join-Path $originalLocation $zipPath Set-Location $tempDir $tempItems = Get-ChildItem -Force if ($tempItems) { Compress-Archive -Path '*' -DestinationPath $absoluteZipPath -Force } else { # SQL-only update: create minimal ZIP with empty placeholder $placeholderPath = "_sql_only_update.txt" Set-Content -Path $placeholderPath -Value "SQL-only update $versionNumber" Compress-Archive -Path $placeholderPath -DestinationPath $absoluteZipPath -Force Remove-Item $placeholderPath -Force } Set-Location $originalLocation Write-Ok "Utworzono ZIP: $zipPath" # --- 10. Checksum SHA256 --- $hash = (Get-FileHash $zipPath -Algorithm SHA256).Hash.ToLower() Write-Ok "SHA256: $hash" # --- 11. Manifest JSON --- $manifest = @{ version = $versionNumber date = (Get-Date -Format "yyyy-MM-dd") checksum_zip = "sha256:$hash" files = @{ modified = $filteredModified added = $filteredAdded deleted = $deletedFilesOnly } directories_deleted = $deletedDirs sql = $sqlQueries changelog = $ChangelogEntry } $manifestJson = $manifest | ConvertTo-Json -Depth 4 $manifestPath = "$updatesDir/ver_${versionNumber}_manifest.json" [System.IO.File]::WriteAllText($manifestPath, $manifestJson, $Utf8NoBom) Write-Ok "Utworzono manifest: $manifestPath" # --- 12. Legacy _sql.txt i _files.txt (okres przejsciowy) --- if ($sqlQueries.Count -gt 0) { $sqlPath = "$updatesDir/ver_${versionNumber}_sql.txt" [System.IO.File]::WriteAllText($sqlPath, ($sqlQueries -join "`n"), $Utf8NoBom) Write-Ok "Utworzono legacy SQL: $sqlPath" } if ($deletedFilesOnly.Count -gt 0 -or $deletedDirs.Count -gt 0) { $filesContent = @() foreach ($f in $deletedFilesOnly) { $filesContent += "F: ../$f" } foreach ($d in $deletedDirs) { $filesContent += "D: ../$d" } $filesPath = "$updatesDir/ver_${versionNumber}_files.txt" [System.IO.File]::WriteAllText($filesPath, ($filesContent -join "`n"), $Utf8NoBom) Write-Ok "Utworzono legacy files: $filesPath" } # --- 13. Aktualizacja versions.php --- $versionsFile = "updates/versions.php" if (Test-Path $versionsFile) { $content = Get-Content $versionsFile -Raw $content = $content -replace '\$current_ver\s*=\s*\d+;', "`$current_ver = $versionInt;" [System.IO.File]::WriteAllText($versionsFile, $content, $Utf8NoBom) Write-Ok "Zaktualizowano versions.php: `$current_ver = $versionInt" } # --- 14. Aktualizacja changelog-data.html --- $changelogFile = "updates/changelog-data.html" if (Test-Path $changelogFile) { $dateStr = Get-Date -Format "dd.MM.yyyy" $newEntry = "ver. $versionNumber - $dateStr
`n$ChangelogEntry`n
`n" $changelogContent = [System.IO.File]::ReadAllText($changelogFile, $Utf8NoBom) $changelogContent = $newEntry + $changelogContent [System.IO.File]::WriteAllText($changelogFile, $changelogContent, $Utf8NoBom) Write-Ok "Zaktualizowano changelog-data.html" } # --- 15. Cleanup --- if (Test-Path $tempDir) { Remove-Item -Recurse -Force $tempDir } # Usun pusty folder temp jesli nie ma juz w nim nic if ((Test-Path "temp") -and ((Get-ChildItem "temp" -Force).Count -eq 0)) { Remove-Item "temp" -Force } Write-Ok "Wyczyszczono pliki tymczasowe" # --- Podsumowanie --- Write-Host "`n=== Gotowe ===" -ForegroundColor Green Write-Host " ZIP: $zipPath" Write-Host " Manifest: $manifestPath" Write-Host " Wersja: $versionNumber (int: $versionInt)" Write-Host ""