<# .SYNOPSIS Локальный релиз: бамп версии -> коммит -> тег -> push -> сборка -> upload в Gitea. .DESCRIPTION Single-command release flow. Каждый релиз публикует артефакты в ТРИ места: 1. Тег vX.Y.Z (исторический архив + changelog) 2. Тег update-channel (фиксированный URL для auto-updater) 3. Bridge-теги, указанные в -BridgeTags (для миграции пользователей со старых версий, у которых запечён старый publish.url). После того как все пользователи получили версию с новым (фиксированным) publish.url, аргумент -BridgeTags можно перестать указывать. .PARAMETER Bump Какую часть semver инкрементировать: patch (по умолчанию), minor, major. .PARAMETER Version Точная версия (напр. "0.5.1"). Если задана, -Bump игнорируется. .PARAMETER SkipBuild Пропустить сборку (если уже собрано вручную, .exe лежит в release/). .PARAMETER BridgeTags Список старых тегов, в которые нужно ТАКЖЕ перезалить новые артефакты, чтобы пользователи на этих версиях нашли апдейт. Например: v0.4.0,v0.5.0. .PARAMETER DryRun Показать что произойдёт, ничего не делая. .EXAMPLE pwsh scripts/release.ps1 -Bump patch pwsh scripts/release.ps1 -Version 0.5.1 -BridgeTags v0.4.0,v0.5.0 .NOTES Требует GITEA_TOKEN с правом write:repository. Канал 'update-channel' должен существовать на Gitea (создаётся однократно). #> param( [ValidateSet('patch', 'minor', 'major')] [string]$Bump = 'patch', [string]$Version, [switch]$SkipBuild, [string[]]$BridgeTags = @(), [switch]$DryRun ) $ErrorActionPreference = 'Stop' $repoOwner = 'AnRil' $repoName = 'laude' $giteaHost = 'git.xn--90adajar8af4h.xn--p1ai' $channelTag = 'update-channel' # --- Pre-flight ---------------------------------------------------------- $root = Resolve-Path (Join-Path $PSScriptRoot '..') Set-Location $root if (-not $env:GITEA_TOKEN -and -not $DryRun) { Write-Error 'GITEA_TOKEN not set.' exit 1 } $status = git status --porcelain if ($status) { Write-Error "Uncommitted changes. Commit or stash first." exit 1 } $branch = git rev-parse --abbrev-ref HEAD if ($branch -ne 'main') { Write-Warning "Branch is $branch, not main. Press Enter to continue or Ctrl+C to cancel." Read-Host } # --- Compute next version ------------------------------------------------ $pkg = Get-Content package.json | ConvertFrom-Json $current = $pkg.version if ($Version) { $next = $Version } else { $parts = $current.Split('.') $major = [int]$parts[0]; $minor = [int]$parts[1]; $patch = [int]$parts[2] switch ($Bump) { 'major' { $major++; $minor = 0; $patch = 0 } 'minor' { $minor++; $patch = 0 } 'patch' { $patch++ } } $next = "$major.$minor.$patch" } $tag = "v$next" Write-Host "" Write-Host "Release plan" -ForegroundColor Cyan Write-Host " current : v$current" Write-Host " next : $tag" Write-Host " publish into : $tag, $channelTag$(if ($BridgeTags) { ', ' + ($BridgeTags -join ', ') })" Write-Host "" if ($DryRun) { Write-Host '(dry run - exiting)' -ForegroundColor Yellow exit 0 } # --- Bump package.json -------------------------------------------------- # IMPORTANT: read+write as UTF-8 WITHOUT BOM. PS5.1 defaults will (a) read the # file as CP1251 and mangle non-ASCII chars like em-dash, and (b) write back # with a BOM that breaks PostCSS / electron-builder reads of package.json. Write-Host "Bumping package.json to $next..." -ForegroundColor Cyan $pkgPath = Join-Path $root 'package.json' $utf8NoBom = New-Object System.Text.UTF8Encoding $false $pkgJson = [System.IO.File]::ReadAllText($pkgPath, $utf8NoBom) $pkgJson = $pkgJson -replace "`"version`":\s*`"$current`"", "`"version`": `"$next`"" [System.IO.File]::WriteAllText($pkgPath, $pkgJson, $utf8NoBom) git add package.json git commit -m "chore(release): $tag" # --- Quality gates ------------------------------------------------------ if (-not $SkipBuild) { Write-Host "Typecheck..." -ForegroundColor Cyan npm run typecheck if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Write-Host "Tests..." -ForegroundColor Cyan npm run test:run if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Write-Host "Building installer..." -ForegroundColor Cyan npm run dist if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } # --- Verify artifacts --------------------------------------------------- $installer = Join-Path 'release' "Exercise-Reminder-Setup-$next.exe" $blockmap = "$installer.blockmap" $manifest = Join-Path 'release' 'latest.yml' foreach ($f in @($installer, $blockmap, $manifest)) { if (-not (Test-Path $f)) { Write-Error "Artifact missing: $f" exit 1 } } # --- Tag + push --------------------------------------------------------- Write-Host "Tagging $tag and pushing..." -ForegroundColor Cyan git tag -a $tag -m "Release $tag" git push origin main git push origin $tag # --- Upload to all target releases -------------------------------------- $uploadScript = Join-Path $PSScriptRoot 'upload-release-assets.ps1' $targets = @($tag, $channelTag) + $BridgeTags foreach ($target in $targets) { Write-Host "" Write-Host "==> Uploading $next artifacts into release '$target'" -ForegroundColor Cyan & powershell -ExecutionPolicy Bypass -File $uploadScript -Tag $target -AssetVersion $next if ($LASTEXITCODE -ne 0) { Write-Error "Upload to '$target' failed (exit $LASTEXITCODE)" exit $LASTEXITCODE } } $releaseUrl = "https://$giteaHost/$repoOwner/$repoName/releases/tag/$tag" Write-Host "" Write-Host "Release published" -ForegroundColor Green Write-Host " $releaseUrl" Write-Host "" Write-Host "Auto-updater will pick up the new version within ~1 hour on all installed copies."