# ── VibeFinance release script (PowerShell) ────────────────────────────── # Productie release voor de 2-container opzet: draait de tests, bouwt het # gecombineerde app-image multi-platform, pusht naar de registry, maakt een # Git-tag en publiceert de release in Gitea. # # Gebruik: # .\release.ps1 # test + bouw + push + Git-tag + Gitea-release # .\release.ps1 -NoBuild # sla docker build over, alleen tag + release # .\release.ps1 -SkipTests # sla de testsuite over (niet aanbevolen) # .\release.ps1 -DryRun # toon alle commando's zonder iets uit te voeren # .\release.ps1 -Force # sla conflictcontrole over, overschrijf tags # .\release.ps1 -NoChangelog # publiceer bewust zonder release-notes # # Het script werkt zelf vier dingen bij, dus die hoef je niet met de hand te # doen: de changelog-kop `## [Niet vrijgegeven]` -> `## [x.x.x] - datum`, de # versie in frontend/package.json, de versiebadge in README.md, en ná het # publiceren de versie in de root package.json -> `x.x.(x+1)-dev`. # # LET OP bij -Force: dat verwijdert de bestaande tag lokaal en op origin, en # Gitea gooit de bijbehorende release daarmee weg. Onder -DryRun wordt dat # alleen gemeld (dat was eerder niet zo — zie stap 4). # # Vereisten: # - Docker Desktop actief # - Ingelogd op de registry: docker login # - docker buildx builder actief: docker buildx create --use # - Git geinstalleerd en working directory is de repo root # - root package.json bevat een schoon versienummer ZONDER -dev suffix # - Changelog.md beschrijft deze versie, onder `## [Niet vrijgegeven]` of # onder een kop `## [x.x.x]`; anders breekt het script af (-NoChangelog # omzeilt dat) # - GITEA_TOKEN in de omgeving (token met release-rechten) voor stap 5, # de Gitea-release waar de update-melding in de app op afgaat param( [switch]$NoBuild, [switch]$SkipTests, [switch]$DryRun, [switch]$Force, # Releasen zonder inhoudelijke release-notes. Zonder deze switch breekt het # script af wanneer de changelog niets over deze versie zegt. [switch]$NoChangelog ) $ErrorActionPreference = "Stop" # ── Geheimen uit .env.release ────────────────────────────────────────────── # Staat niet in git (.gitignore) en bestaat alleen op de release-machine, zodat # GITEA_TOKEN niet elke sessie met de hand gezet hoeft te worden. Een variabele # die al in de omgeving staat wint, zodat je per run kunt afwijken. $envFile = Join-Path $PSScriptRoot ".env.release" if (Test-Path $envFile) { $geladen = @() foreach ($regel in Get-Content $envFile) { if ($regel -notmatch '^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$') { continue } $naam = $Matches[1] $waarde = $Matches[2].Trim().Trim('"').Trim("'") if (-not $waarde) { continue } # Al gezet in de shell? Dan die laten staan. if ([Environment]::GetEnvironmentVariable($naam)) { continue } [Environment]::SetEnvironmentVariable($naam, $waarde) $geladen += $naam } # Alleen de namen loggen, nooit de waarden. if ($geladen) { Write-Host "Geheimen geladen uit .env.release: $($geladen -join ', ')" -ForegroundColor DarkGray } } # ════════════════════════════════════════════════════════════════════════════ # ── PROJECTCONFIGURATIE — pas alleen dit blok aan voor een nieuw project ── # ════════════════════════════════════════════════════════════════════════════ $appName = "VibeFinance" $registry = "vibehoogie.duckdns.org/vibe" $appImage = "vibefinance-app" $apiUrl = "/api" $platforms = "linux/amd64,linux/arm64" # arm64 = Raspberry Pi # ════════════════════════════════════════════════════════════════════════════ # ── Versie uit root package.json ─────────────────────────────────────────── $rootPkg = Get-Content (Join-Path $PSScriptRoot "package.json") -Raw | ConvertFrom-Json $appVersion = $rootPkg.version if (-not $appVersion) { Write-Error "Kon versienummer niet lezen uit package.json" exit 1 } if ($appVersion -match "-dev$") { Write-Error "Versienummer '$appVersion' bevat '-dev' suffix. Verwijder de suffix voor een productie-release." exit 1 } # Git-tag met `v`-prefix, net als de release-naam hieronder en net als de tags # t/m v1.0.0. Gitea toont de tag op de release-pagina, dus zonder prefix stond # daar het kale nummer. De image-tags in de registry houden bewust géén `v`: # dat is de conventie voor container-tags. # De update-check verdraagt beide: zowel backend/src/routes/update.js als # useUpdateCheck.js strippen een leidende `v` voordat ze versies vergelijken. $gitTag = "v$appVersion" # ── Registry tags (één image voor frontend + backend) ───────────────────── $appBase = "$registry/$appImage" $appVersionTag = "$appBase`:$appVersion" $appLatestTag = "$appBase`:latest" # ── Helpers ──────────────────────────────────────────────────────────────── function Invoke-Cmd { param( [string[]]$Cmd, # Wordt bij een fout uitgevoerd om een gerichte uitleg te geven [scriptblock]$Uitleg ) $display = $Cmd -join " " if ($DryRun) { Write-Host "[DRY-RUN] $display" -ForegroundColor Yellow return "" } Write-Host "> $display" -ForegroundColor DarkGray $output = & $Cmd[0] $Cmd[1..($Cmd.Length - 1)] if ($LASTEXITCODE -ne 0) { # Geen 'throw': dat levert een PowerShell-stacktrace met het hele # commando erin, wat de eigenlijke oorzaak wegdrukt. Write-Host "" Write-Host "Mislukt: $($Cmd[0]) $($Cmd[1]) (exitcode $LASTEXITCODE)" -ForegroundColor Red if ($Uitleg) { & $Uitleg } Write-Host "" Write-Host "Er is niets gepubliceerd. Los het bovenstaande op en draai release.ps1 opnieuw." -ForegroundColor DarkGray exit 1 } return $output } function Write-Step { param([string]$Label) Write-Host "" Write-Host "── $Label" -ForegroundColor Cyan } # Haalt de sectie van deze versie uit changelog-tekst. Werkt op een string en # niet op het bestand, zodat een droogloop kan valideren tegen de kop die hij # zou schrijven zonder die daadwerkelijk te schrijven. function Get-ChangelogSectionFromText { param([string]$Text, [string]$Version) if (-not $Text) { return "" } $lines = $Text -split "\r?\n" $start = -1 $needle = "^##\s*\[" + [regex]::Escape($Version) + "\]" for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match $needle) { $start = $i + 1; break } } if ($start -lt 0) { return "" } $out = @() for ($i = $start; $i -lt $lines.Count; $i++) { if ($lines[$i] -match "^##\s*\[") { break } if ($lines[$i] -match "^---\s*$") { continue } $out += $lines[$i] } return (($out -join "`n").Trim()) } # ── Changelog-kop vrijgeven ─────────────────────────────────────────────── # De release-notes komen uit de sectie `## [x.x.x]` in Changelog.md. Stond het # werk nog onder `## [Niet vrijgegeven]`, dan vond het script niets en werd stil # de placeholder "Release x.x.x" gepubliceerd — dat gebeurde bij 0.1.9 en 1.0.2, # en is achteraf alleen met een PATCH op de release te herstellen. Daarom zet het # script de kop nu zelf om, en breekt het af als er niets te melden valt. # Zelfde regel als bij de andere auto-updates: een droogloop meldt, maar schrijft # niet. $changelogPath = Join-Path $PSScriptRoot "Changelog.md" $nieuweKop = "## [$appVersion] - $(Get-Date -Format 'yyyy-MM-dd')" $changelogTxt = if (Test-Path $changelogPath) { [System.IO.File]::ReadAllText($changelogPath) } else { "" } $kopPatroon = "(?m)^##\s*\[" + [regex]::Escape($appVersion) + "\]" # Niet `\s*$`: `\s` matcht ook newlines, en dan slokt de match de lege regel ná # de kop op — die verdween daardoor bij het omzetten. En niet `[ \t]*$` alleen: # dit bestand heeft CRLF (zie .gitattributes), dus `$` staat achter een `\r` die # geen space of tab is. De lookahead laat het regeleinde buiten de match, zodat # de CRLF intact blijft. $onderwegPatroon = "(?m)^##[ \t]*\[[ \t]*Niet vrijgegeven[ \t]*\][ \t]*(?=\r?$)" # Eerst alleen in het geheugen omzetten. Schrijven gebeurt na de controle # hieronder: bij een lege `[Niet vrijgegeven]`-sectie zou de kop anders al # vervangen zijn terwijl het script daarna afbreekt, en dan blijft er een # aangepaste changelog achter bij een release die niet doorging. $kopOmzetten = ($changelogTxt -notmatch $kopPatroon -and $changelogTxt -match $onderwegPatroon) if ($kopOmzetten) { # $nieuweKop bevat geen '$', dus veilig als vervangingstekst. $changelogTxt = [regex]::Replace($changelogTxt, $onderwegPatroon, $nieuweKop) } if (-not (Get-ChangelogSectionFromText $changelogTxt $appVersion) -and -not $NoChangelog) { Write-Host "" Write-Host "Changelog.md zegt niets over $appVersion." -ForegroundColor Red Write-Host "" Write-Host "Zonder notes komt er alleen de tekst 'Release $appVersion' in de Gitea-release," -ForegroundColor Yellow Write-Host "en dat is achteraf alleen met een PATCH op de release te herstellen." -ForegroundColor Yellow Write-Host "" Write-Host "Zet je wijzigingen in Changelog.md onder een kop:" -ForegroundColor White Write-Host " ## [Niet vrijgegeven]" -ForegroundColor White Write-Host "Die zet dit script bij de volgende run zelf om naar '$nieuweKop'." -ForegroundColor White Write-Host "" Write-Host "Bewust zonder release-notes publiceren? Draai met -NoChangelog." -ForegroundColor DarkGray Write-Host "" Write-Host "Er is niets gepubliceerd." -ForegroundColor DarkGray exit 1 } # De controle is doorstaan, dus nu pas schrijven. if ($kopOmzetten) { if ($DryRun) { Write-Host "[DRY-RUN] Changelog-kop vrijgeven: '## [Niet vrijgegeven]' -> '$nieuweKop'" -ForegroundColor Yellow } else { [System.IO.File]::WriteAllText($changelogPath, $changelogTxt) Write-Host "Changelog-kop vrijgegeven: $nieuweKop" -ForegroundColor Green } } # ── Versie synchroniseren naar frontend/package.json ────────────────────── # Staat na de changelog-controle: anders laat een afgebroken release wél een # gewijzigde working tree achter terwijl hij "niets gepubliceerd" meldt. # Een droogloop schrijft hier niet, om dezelfde reden. $frontendPkgPath = Join-Path $PSScriptRoot "frontend/package.json" $frontendPkg = Get-Content $frontendPkgPath -Raw | ConvertFrom-Json if ($frontendPkg.version -ne $appVersion) { if ($DryRun) { Write-Host "[DRY-RUN] Versie naar frontend synchroniseren: $($frontendPkg.version) -> $appVersion" -ForegroundColor Yellow } else { $frontendPkg.version = $appVersion [System.IO.File]::WriteAllText($frontendPkgPath, ($frontendPkg | ConvertTo-Json -Depth 10)) Write-Host "Versie gesynchroniseerd naar frontend: $appVersion" -ForegroundColor Green } } # ── Versiebadge in README.md ────────────────────────────────────────────── # Anders loopt die badge stil achter: hij stond drie releases achter voordat # dit erin zat, en het script kent het nummer toch al. Zelfde regel als # hierboven: een droogloop meldt het, maar schrijft niet. $readmePath = Join-Path $PSScriptRoot "README.md" if (Test-Path $readmePath) { $readme = [System.IO.File]::ReadAllText($readmePath) $patroon = "badge/versie-[^-]+-" $nieuw = "badge/versie-$appVersion-" $huidig = [regex]::Match($readme, $patroon) if ($huidig.Success -and $huidig.Value -ne $nieuw) { if ($DryRun) { Write-Host "[DRY-RUN] Versiebadge in README.md: $($huidig.Value) -> $nieuw" -ForegroundColor Yellow } else { [System.IO.File]::WriteAllText($readmePath, ($readme -replace $patroon, $nieuw)) Write-Host "Versiebadge in README.md bijgewerkt: $appVersion" -ForegroundColor Green } } } # ── Samenvatting ─────────────────────────────────────────────────────────── Write-Host "" Write-Host "════════════════════════════════════════" -ForegroundColor Cyan Write-Host " $appName Release" -ForegroundColor Cyan Write-Host " Versie : $appVersion" -ForegroundColor Green Write-Host " Git-tag : $gitTag" -ForegroundColor Green Write-Host " Image : $appVersionTag" -ForegroundColor Green Write-Host " Platform: $platforms" -ForegroundColor Green if ($DryRun) { Write-Host " Mode : DRY-RUN (geen wijzigingen)" -ForegroundColor Yellow } if ($NoBuild) { Write-Host " Mode : SKIP BUILD" -ForegroundColor Yellow } if ($SkipTests) { Write-Host " Mode : SKIP TESTS" -ForegroundColor Yellow } if ($Force) { Write-Host " Mode : FORCE (conflictcontrole overgeslagen)" -ForegroundColor Red } Write-Host "════════════════════════════════════════" -ForegroundColor Cyan # ── Stap 1: Conflictcontrole ────────────────────────────────────────────── Write-Step "[1/5] Controles (tags + registry-login)" # Geen prompts: anders blijft het script hangen wanneer SSH om een wachtwoord # vraagt. Wel de al geconfigureerde ssh gebruiken (core.sshCommand) — op # Windows praat alleen de OpenSSH van het systeem met de ssh-agent, Git's # eigen ssh niet. $sshBasis = (git config --get core.sshCommand) if (-not $sshBasis) { $sshBasis = "ssh" } $env:GIT_SSH_COMMAND = "$sshBasis -o BatchMode=yes -o ConnectTimeout=10" function Test-GitPushToegang { git ls-remote --heads origin 2>&1 | Out-Null return ($LASTEXITCODE -eq 0) } if (-not $DryRun -and -not $Force) { $localTag = git tag --list $gitTag if ($localTag) { Write-Error "Git-tag '$gitTag' bestaat al lokaal. Verhoog het versienummer in package.json of gebruik -Force." exit 1 } # Toegang tot origin nu controleren; stap 4 pusht de tag en zou anders pas # na de build stranden. if (-not (Test-GitPushToegang)) { $remoteUrl = git remote get-url origin Write-Host "" Write-Host "Geen toegang tot origin ($remoteUrl)." -ForegroundColor Red Write-Host "" Write-Host "Stap 4 pusht de Git-tag, dus dit moet eerst werken. Controleer met:" -ForegroundColor Yellow Write-Host " git ls-remote --heads origin" -ForegroundColor White Write-Host "" Write-Host "Bij 'Permission denied (publickey)' doet de SSH-sleutel het niet." -ForegroundColor Yellow Write-Host "Snelste alternatief is https, met hetzelfde Gitea-token als de registry:" -ForegroundColor Yellow Write-Host " git remote set-url origin https:////.git" -ForegroundColor White Write-Host "" Write-Host "Er is niets gepubliceerd." -ForegroundColor DarkGray exit 1 } $remoteTags = git ls-remote --tags origin "refs/tags/$gitTag" if ($remoteTags) { Write-Error "Git-tag '$gitTag' bestaat al op origin. Verhoog het versienummer in package.json of gebruik -Force." exit 1 } Write-Host "Controleer registry: $appVersionTag" -ForegroundColor DarkGray $ErrorActionPreference = "SilentlyContinue" docker manifest inspect $appVersionTag 2>$null | Out-Null $imageExists = $LASTEXITCODE -eq 0 $ErrorActionPreference = "Stop" if ($imageExists) { Write-Error "Registry-tag '$appVersionTag' bestaat al. Verhoog het versienummer in package.json of gebruik -Force." exit 1 } Write-Host "Geen conflicten gevonden." -ForegroundColor Green } elseif ($Force) { Write-Host "Conflictcontrole overgeslagen (-Force)" -ForegroundColor Yellow } else { Write-Host "[DRY-RUN] Conflictcontrole overgeslagen" -ForegroundColor Yellow } # ── Registry-login ───────────────────────────────────────────────────────── # Zonder geldige credentials klapt `buildx --push` pas ná de hele build met # "401 Unauthorized". Daarom vooraf controleren, en zelf inloggen wanneer er # een token in de omgeving staat. $registryHost = $registry.Split("/")[0] function Test-RegistryLogin { param([string]$Repo) # imagetools inspect praat rechtstreeks met de registry: 401 = niet # ingelogd, "not found" = wél ingelogd maar tag bestaat (nog) niet. $uitvoer = (docker buildx imagetools inspect "${Repo}:latest" 2>&1 | Out-String) if ($uitvoer -match "401|[Uu]nauthorized|authentication required") { return $false } return $true } function Connect-Registry { $gebruiker = $env:REGISTRY_USER $token = if ($env:REGISTRY_TOKEN) { $env:REGISTRY_TOKEN } else { $env:GITEA_TOKEN } if (-not ($gebruiker -and $token)) { return $false } Write-Host "Inloggen op $registryHost als $gebruiker..." -ForegroundColor White # Via stdin, zodat het token niet in de commandoregel of logs belandt. $token | docker login $registryHost -u $gebruiker --password-stdin return ($LASTEXITCODE -eq 0) } if (-not $NoBuild) { if (Test-RegistryLogin $appBase) { Write-Host "Registry-login in orde ($registryHost)." -ForegroundColor Green } elseif (Connect-Registry) { Write-Host "Ingelogd op $registryHost." -ForegroundColor Green } elseif ($DryRun) { Write-Host "[DRY-RUN] Niet ingelogd op $registryHost — een echte push zou hier falen." -ForegroundColor Yellow } else { Write-Host "" Write-Host "Niet ingelogd op $registryHost — de push zou met 401 Unauthorized falen." -ForegroundColor Red Write-Host "" Write-Host "Kies een van deze twee:" -ForegroundColor Yellow Write-Host "" Write-Host " 1. Eenmalig handmatig inloggen (credential blijft bewaard):" -ForegroundColor Yellow Write-Host " docker login $registryHost" -ForegroundColor White Write-Host " Gebruikersnaam = je Gitea-account, wachtwoord = een Gitea access" -ForegroundColor DarkGray Write-Host " token met scope write:package (Settings > Applications > Generate" -ForegroundColor DarkGray Write-Host " New Token)." -ForegroundColor DarkGray Write-Host "" Write-Host " 2. Het script laten inloggen door deze variabelen te zetten:" -ForegroundColor Yellow Write-Host ' $env:REGISTRY_USER = ""' -ForegroundColor White Write-Host ' $env:REGISTRY_TOKEN = ""' -ForegroundColor White Write-Host "" Write-Host "Let op: zonder poortnummer inloggen, en in dezelfde shell en onder" -ForegroundColor DarkGray Write-Host "dezelfde Windows-gebruiker als waarin je release.ps1 draait." -ForegroundColor DarkGray Write-Host "" Write-Host "Er is niets gepubliceerd." -ForegroundColor DarkGray exit 1 } } # ── Stap 2: Tests ───────────────────────────────────────────────────────── if (-not $SkipTests) { Write-Step "[2/5] Testsuite" if ($DryRun) { Write-Host "[DRY-RUN] test.ps1 -NoSmoke" -ForegroundColor Yellow } else { & (Join-Path $PSScriptRoot "test.ps1") -NoSmoke if ($LASTEXITCODE -ne 0) { Write-Error "Tests mislukt — release afgebroken." exit 1 } } } else { Write-Step "[2/5] Tests overgeslagen (-SkipTests)" } # ── Stap 3: Bouwen en pushen ────────────────────────────────────────────── if (-not $NoBuild) { Write-Step "[3/5] Multi-platform bouwen en pushen" Invoke-Cmd @( "docker", "buildx", "build", "--platform", $platforms, "--target", "production", "--push", "-t", $appVersionTag, "-t", $appLatestTag, "--build-arg", "APP_VERSION=$appVersion", "--build-arg", "VITE_API_URL=$apiUrl", "--build-arg", "VITE_APP_VERSION=$appVersion", $PSScriptRoot ) -Uitleg { # Meestal is de build zelf goed en weigert de registry de push. if (-not (Test-RegistryLogin $appBase)) { Write-Host "Oorzaak: geen geldige login op $registryHost (de push geeft dan 401 Unauthorized)." -ForegroundColor Yellow Write-Host "" Write-Host " docker login $registryHost" -ForegroundColor White Write-Host " gebruikersnaam : je Gitea-account" -ForegroundColor DarkGray Write-Host " wachtwoord : Gitea access token met scope write:package" -ForegroundColor DarkGray Write-Host " (Gitea -> Settings -> Applications -> Generate New Token)" -ForegroundColor DarkGray Write-Host "" Write-Host "Of laat het script inloggen:" -ForegroundColor Yellow Write-Host ' $env:REGISTRY_USER = ""' -ForegroundColor White Write-Host ' $env:REGISTRY_TOKEN = ""' -ForegroundColor White } else { Write-Host "De registry-login is in orde, dus dit is een build- of netwerkfout." -ForegroundColor Yellow Write-Host "Bekijk de regels boven deze melding; de eerste ERROR wijst de oorzaak aan." -ForegroundColor DarkGray } } } else { Write-Step "[3/5] Bouwen overgeslagen (-NoBuild)" } # ── Stap 4: Git-tag aanmaken en pushen ──────────────────────────────────── Write-Step "[4/5] Git-tag aanmaken en pushen" if ($Force) { # LET OP: deze twee commando's gingen niet via Invoke-Cmd en hadden dus geen # -DryRun-controle. Een `-DryRun -Force` verwijderde daardoor écht de tag, # lokaal en op origin — en Gitea ruimt de release op zodra de tag verdwijnt. # Zo is de release van 1.0.2 verloren gegaan. if ($DryRun) { Write-Host "[DRY-RUN] git tag -d $gitTag" -ForegroundColor Yellow Write-Host "[DRY-RUN] git push origin :refs/tags/$gitTag" -ForegroundColor Yellow } else { Write-Host "Bestaande lokale tag verwijderen (indien aanwezig)..." -ForegroundColor Yellow $ErrorActionPreference = "SilentlyContinue" git tag -d $gitTag 2>$null | Out-Null $ErrorActionPreference = "Stop" Write-Host "Bestaande remote tag verwijderen (indien aanwezig)..." -ForegroundColor Yellow Write-Host " Gitea verwijdert de bijbehorende release mee." -ForegroundColor DarkGray $ErrorActionPreference = "SilentlyContinue" git push origin ":refs/tags/$gitTag" 2>$null | Out-Null $ErrorActionPreference = "Stop" } } Write-Host "Git-tag aanmaken: $gitTag" -ForegroundColor White Invoke-Cmd @("git", "tag", "-a", $gitTag, "-m", "Release $gitTag") Write-Host "Git-tag pushen naar origin" -ForegroundColor White Invoke-Cmd @("git", "push", "origin", "refs/tags/$gitTag") -Uitleg { Write-Host "Kon de tag niet naar origin pushen." -ForegroundColor Yellow Write-Host "Het image staat al in de registry; alleen de tag en de Gitea-release ontbreken." -ForegroundColor DarkGray Write-Host "Controleer de toegang met: git ls-remote --heads origin" -ForegroundColor White Write-Host "Bij 'Permission denied (publickey)' is https het snelste alternatief:" -ForegroundColor DarkGray Write-Host " git remote set-url origin https:////.git" -ForegroundColor White } # ── Stap 5: Gitea-release aanmaken ──────────────────────────────────────── Write-Step "[5/5] Gitea-release aanmaken" # De update-melding in de app leest /releases/latest. Een kale Git-tag komt # daar niet in terug, dus er moet een echte release van gemaakt worden. $repoUrl = ($rootPkg.repository -replace "/+$", "") $releaseApi = $null if ($repoUrl -match "^(https?://[^/]+)/([^/]+)/([^/]+)$") { $releaseApi = "$($Matches[1])/api/v1/repos/$($Matches[2])/$($Matches[3])/releases" } $giteaToken = $env:GITEA_TOKEN if (-not $releaseApi) { Write-Host "Kon 'repository' niet parsen uit package.json — release overgeslagen." -ForegroundColor Yellow } elseif (-not $giteaToken) { Write-Host "Geen GITEA_TOKEN in de omgeving — release overgeslagen." -ForegroundColor Yellow Write-Host " Zonder release ziet de app geen update-melding voor $appVersion." -ForegroundColor DarkGray } else { # Uit $changelogTxt en niet opnieuw van schijf: in een droogloop is de kop # nog niet geschreven, en dan zou de payload hier de placeholder tonen — # precies wat je met een droogloop wilt uitsluiten. $notes = Get-ChangelogSectionFromText $changelogTxt $appVersion if (-not $notes) { $notes = "Release $appVersion" } $payload = @{ tag_name = $gitTag name = "v$appVersion" body = $notes draft = $false prerelease = $false } | ConvertTo-Json -Depth 3 if ($DryRun) { Write-Host "[DRY-RUN] POST $releaseApi" -ForegroundColor Yellow Write-Host $payload -ForegroundColor DarkGray } else { Write-Host "Release publiceren: $releaseApi" -ForegroundColor White try { $rel = Invoke-RestMethod -Method Post -Uri $releaseApi -Body $payload ` -ContentType "application/json" ` -Headers @{ Authorization = "token $giteaToken" } Write-Host "Release aangemaakt: $($rel.html_url)" -ForegroundColor Green } catch { $code = $_.Exception.Response.StatusCode.value__ if ($code -eq 409) { Write-Host "Release voor $gitTag bestaat al — overgeslagen." -ForegroundColor Yellow } else { Write-Host "Release aanmaken mislukt ($code): $($_.Exception.Message)" -ForegroundColor Red Write-Host "Tag en image staan al klaar; maak de release eventueel handmatig aan." -ForegroundColor DarkGray } } } } # ── Klaar ───────────────────────────────────────────────────────────────── $kop = if ($DryRun) { "DRY-RUN klaar: $appVersion (niets gepubliceerd)" } else { "Gepubliceerd: $appVersion" } Write-Host "" Write-Host "════════════════════════════════════════" -ForegroundColor Cyan Write-Host " $kop" -ForegroundColor Green Write-Host "" Write-Host " Registry image:" -ForegroundColor Green Write-Host " $appVersionTag" -ForegroundColor White Write-Host " $appLatestTag" -ForegroundColor White Write-Host "" Write-Host " Git-tag: $gitTag -> origin" -ForegroundColor Green Write-Host "════════════════════════════════════════" -ForegroundColor Cyan Write-Host "" # ── Versie naar de volgende dev-cyclus ──────────────────────────────────── # Was voorheen stap 4 met de hand. Gebeurt hier, ná het publiceren, zodat een # afgebroken release het versienummer niet vooruit zet. Bijkomend voordeel: de # `-dev` suffix laat dezelfde release niet nog eens draaien (de controle boven # in dit script weigert een `-dev` versie), dus een dubbele publicatie is # meteen uitgesloten. Wil je bewust opnieuw releasen, zet het nummer dan zelf # terug. if ($appVersion -match '^(\d+)\.(\d+)\.(\d+)$') { $volgende = "{0}.{1}.{2}-dev" -f $Matches[1], $Matches[2], ([int]$Matches[3] + 1) $rootPkgPath = Join-Path $PSScriptRoot "package.json" if ($DryRun) { Write-Host "[DRY-RUN] Versie in package.json: $appVersion -> $volgende" -ForegroundColor Yellow } else { # Gerichte tekstvervanging in plaats van ConvertTo-Json: dat zou het hele # bestand herschrijven en de opmaak van de andere velden aantasten. $rootTxt = [System.IO.File]::ReadAllText($rootPkgPath) $treffer = [regex]::Match($rootTxt, '"version"\s*:\s*"' + [regex]::Escape($appVersion) + '"') if ($treffer.Success) { $rootTxt = $rootTxt.Remove($treffer.Index, $treffer.Length).Insert($treffer.Index, '"version": "' + $volgende + '"') [System.IO.File]::WriteAllText($rootPkgPath, $rootTxt) Write-Host "Versie in package.json naar de volgende cyclus: $volgende" -ForegroundColor Green Write-Host " Commit dit mee, samen met de changelog-kop en de README-badge." -ForegroundColor DarkGray Write-Host "" } else { Write-Host "Kon '$appVersion' niet terugvinden in package.json — versie niet bijgewerkt." -ForegroundColor Yellow } } } else { Write-Host "Versie '$appVersion' is geen x.y.z — volgende dev-versie niet automatisch gezet." -ForegroundColor Yellow } # Expliciete 0: anders erft de caller de exitcode van het laatste # docker-commando (bijv. de registry-check). exit 0