# Pyramid Release Script # Usage: .\scripts\release.ps1 # Builds Windows + Android, creates Gitea release, uploads artifacts. Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" # Always run from the project root, regardless of where the script was called from. Set-Location (Split-Path $PSScriptRoot -Parent) # ── Config ──────────────────────────────────────────────────────────────────── $GiteaBase = "http://192.168.178.71:3000" $GiteaRepo = "steggi/pyramid" $GiteaToken = "bb522f9646c6856c9ab58bef0151ac36a9ce1d57" $Headers = @{ Authorization = "token $GiteaToken"; "Content-Type" = "application/json" } # ── Read and Update version from pubspec.yaml ───────────────────────────────── $pubspecPath = "pubspec.yaml" $pubspecLines = @(Get-Content $pubspecPath) $versionIndex = -1 for ($i = 0; $i -lt $pubspecLines.Count; $i++) { if ($pubspecLines[$i] -match "^version:") { $versionIndex = $i break } } if ($versionIndex -lt 0) { Write-Error "version not found in pubspec.yaml"; exit 1 } $currentVersionFull = ($pubspecLines[$versionIndex] -split ":")[1].Trim() $currentVersion = $currentVersionFull.Split("+")[0] $currentBuild = 0 if ($currentVersionFull -match "\+") { $currentBuild = [int]$currentVersionFull.Split("+")[1] } $vParts = $currentVersion.Split(".") $major = [int]$vParts[0] $minor = [int]$vParts[1] $patch = [int]$vParts[2] $nextPatch = "$major.$minor.$($patch + 1)" $nextBuild = $currentBuild + 1 Write-Host "" Write-Host " Aktuelle Version : $currentVersion (Build $currentBuild)" -ForegroundColor Cyan $newVersionInput = Read-Host " Neue Version eingeben (Leer lassen für $nextPatch)" if ([string]::IsNullOrWhiteSpace($newVersionInput)) { $newVersion = $nextPatch } else { $newVersion = $newVersionInput.Trim().TrimStart("v") } $newVersionFull = "$newVersion+$nextBuild" if ($newVersionFull -ne $currentVersionFull) { $pubspecLines[$versionIndex] = "version: $newVersionFull" Set-Content -Path $pubspecPath -Value $pubspecLines Write-Host " pubspec.yaml auf $newVersionFull aktualisiert." -ForegroundColor Green } $version = $newVersion $tag = "v$version" Write-Host "" Write-Host " Pyramid Release Builder" -ForegroundColor Cyan Write-Host " Version : $tag" -ForegroundColor White Write-Host "" # ── Check for existing release ──────────────────────────────────────────────── try { $existing = Invoke-RestMethod "$GiteaBase/api/v1/repos/$GiteaRepo/releases/tags/$tag" ` -Headers $Headers -Method Get -ErrorAction Stop Write-Host " [!] Release $tag existiert bereits auf Gitea." -ForegroundColor Yellow $overwrite = Read-Host " Überschreiben? (j/N)" if ($overwrite -notmatch "^[jJ]$") { Write-Host " Abgebrochen."; exit 0 } # Delete existing release Invoke-RestMethod "$GiteaBase/api/v1/repos/$GiteaRepo/releases/$($existing.id)" ` -Headers $Headers -Method Delete | Out-Null Write-Host " Alter Release gelöscht." -ForegroundColor Gray } catch { # 404 = release doesn't exist, that's fine } # ── Release notes ───────────────────────────────────────────────────────────── Write-Host " Release-Notizen (leer lassen = Changelog aus Git-Commits):" -ForegroundColor Cyan $notes = Read-Host " > " if ([string]::IsNullOrWhiteSpace($notes)) { # Auto-generate from git log since last tag. # git describe exits non-zero when no tags exist; catch the error explicitly # because $ErrorActionPreference = "Stop" would otherwise abort the script. $lastTag = $null try { $lastTag = git describe --tags --abbrev=0 HEAD^ 2>$null } catch {} if ($lastTag) { $notes = git log "$lastTag..HEAD" --pretty=format:"- %s" 2>$null | Out-String } else { $notes = git log --pretty=format:"- %s" -20 2>$null | Out-String } Write-Host " (Commits als Release-Notizen verwendet)" -ForegroundColor Gray } # ── Build Windows ───────────────────────────────────────────────────────────── Write-Host "" Write-Host " [1/4] Windows Build..." -ForegroundColor Cyan flutter build windows --release if ($LASTEXITCODE -ne 0) { Write-Error "Windows build fehlgeschlagen"; exit 1 } $winSrc = "build\windows\x64\runner\Release" $winZip = "build\pyramid-windows-$tag.zip" if (Test-Path $winZip) { Remove-Item $winZip -Force } Compress-Archive -Path "$winSrc\*" -DestinationPath $winZip Write-Host " Windows ZIP: $winZip" -ForegroundColor Green # ── Build Android ───────────────────────────────────────────────────────────── Write-Host "" Write-Host " [2/4] Android Build (split per ABI)..." -ForegroundColor Cyan flutter build apk --release --split-per-abi if ($LASTEXITCODE -ne 0) { Write-Error "Android build fehlgeschlagen"; exit 1 } # Order matters: arm64-v8a is uploaded first so app versions that still pick # the first APK asset (pre-ABI-aware updater) get the variant that fits # practically every real device. $abiBuilds = @( @{ Abi = "arm64-v8a"; Desc = "64-bit ARM — praktisch jedes Smartphone der letzten ~8 Jahre. Im Zweifel diese Datei." }, @{ Abi = "armeabi-v7a"; Desc = "32-bit ARM — sehr alte Geräte" }, @{ Abi = "x86_64"; Desc = "Emulatoren / x86-Geräte" } ) $apkFiles = @() foreach ($b in $abiBuilds) { $abi = $b.Abi $src = "build\app\outputs\flutter-apk\app-$abi-release.apk" if (-not (Test-Path $src)) { Write-Error "APK fehlt: $src"; exit 1 } $dst = "build\pyramid-android-$abi-$tag.apk" Copy-Item $src $dst -Force $apkFiles += @{ Path = $dst; Name = "pyramid-android-$abi-$tag.apk"; Desc = $b.Desc } $sizeMb = [math]::Round((Get-Item $dst).Length / 1MB, 1) Write-Host " Android APK ($abi, $sizeMb MB): $dst" -ForegroundColor Green } # Append a download guide to the release notes so every asset is explained. $downloadGuide = "`n`n## Downloads`n`n| Datei | Für welches Gerät? |`n|---|---|`n" foreach ($f in $apkFiles) { $downloadGuide += "| ``$($f.Name)`` | $($f.Desc) |`n" } $downloadGuide += "| ``pyramid-windows-$tag.zip`` | Windows 10/11 |`n" $downloadGuide += "`n*Die App lädt beim automatischen Update selbst die passende Variante.*" $notes = $notes.Trim() + $downloadGuide # ── Git tag ─────────────────────────────────────────────────────────────────── Write-Host "" Write-Host " [3/4] Git Tag $tag..." -ForegroundColor Cyan git tag -f $tag # Token in die Push-URL einbetten, damit keine interaktive Passwort-Abfrage nötig ist $pushUrl = $GiteaBase -replace "http://", "http://steggi:$GiteaToken@" & git push "$pushUrl/steggi/pyramid.git" $tag --force if ($LASTEXITCODE -ne 0) { Write-Host " [!] Tag-Push fehlgeschlagen (Release wird trotzdem erstellt)." -ForegroundColor Yellow } else { Write-Host " Tag gepusht." -ForegroundColor Green } # ── Create Gitea release ────────────────────────────────────────────────────── Write-Host "" Write-Host " [4/4] Gitea Release erstellen..." -ForegroundColor Cyan $body = @{ tag_name = $tag name = "Pyramid $tag" body = $notes.Trim() draft = $false prerelease = $false target_commitish = "master" } | ConvertTo-Json -Compress $release = Invoke-RestMethod "$GiteaBase/api/v1/repos/$GiteaRepo/releases" ` -Headers $Headers -Method Post -Body $body $releaseId = $release.id Write-Host " Release ID: $releaseId" -ForegroundColor Gray # ── Upload artifacts ────────────────────────────────────────────────────────── function Upload-Asset($filePath, $fileName) { # curl.exe (built into Windows 10+) handles large binary uploads reliably. $uri = "$GiteaBase/api/v1/repos/$GiteaRepo/releases/$releaseId/assets?name=$fileName" $result = & curl.exe -s --fail -w "%{http_code}" ` -X POST $uri ` -H "Authorization: token $GiteaToken" ` -F "attachment=@`"$filePath`"" if ($LASTEXITCODE -ne 0) { Write-Error "Upload fehlgeschlagen ($result): $fileName" } Write-Host " Hochgeladen: $fileName" -ForegroundColor Green } # APKs first (arm64-v8a leads — see comment at $abiBuilds), then Windows. foreach ($f in $apkFiles) { Upload-Asset $f.Path $f.Name } Upload-Asset $winZip "pyramid-windows-$tag.zip" # ── Done ────────────────────────────────────────────────────────────────────── Write-Host "" Write-Host " Release $tag erfolgreich erstellt!" -ForegroundColor Green Write-Host " $GiteaBase/$GiteaRepo/releases/tag/$tag" -ForegroundColor Cyan Write-Host ""