<# .SYNOPSIS Локальный релиз: бамп версии → коммит → тег → push → сборка → upload в Gitea release. .DESCRIPTION Один скрипт от и до. Если Gitea Actions не настроено, это рабочая альтернатива. .PARAMETER Bump Какую часть semver инкрементировать: patch (по умолчанию), minor, major. Альтернатива — указать -Version явно. .PARAMETER Version Точная версия (напр. "0.3.0"). Если задана, -Bump игнорируется. .PARAMETER SkipBuild Пропустить сборку (если уже собрано вручную, .exe лежит в release/). .PARAMETER DryRun Показать что произойдёт, но ничего не делать. .EXAMPLE pwsh scripts/release.ps1 -Bump minor pwsh scripts/release.ps1 -Version 0.3.0 pwsh scripts/release.ps1 -Bump patch -DryRun .NOTES Требует переменную окружения GITEA_TOKEN с правом write:repository (создаётся в Gitea: Settings → Applications → Generate New Token). #> param( [ValidateSet('patch', 'minor', 'major')] [string]$Bump = 'patch', [string]$Version, [switch]$SkipBuild, [switch]$DryRun ) $ErrorActionPreference = 'Stop' # --- Config --------------------------------------------------------------- $repoOwner = 'AnRil' $repoName = 'laude' $giteaHost = 'xn--90adajar8af4h.xn--p1ai/git' $apiBase = "https://$giteaHost/api/v1" # --- Pre-flight checks --------------------------------------------------- $root = Resolve-Path (Join-Path $PSScriptRoot '..') Set-Location $root if (-not $env:GITEA_TOKEN -and -not $DryRun) { Write-Error 'GITEA_TOKEN не задан. Создай в Gitea Settings → Applications и export GITEA_TOKEN=...' exit 1 } $status = git status --porcelain if ($status) { Write-Error "Есть незакоммиченные изменения. Сначала закоммить или stash." exit 1 } $branch = git rev-parse --abbrev-ref HEAD if ($branch -ne 'main') { Write-Warning "Текущая ветка не main, а $branch. Продолжить? (Ctrl+C для отмены)" Read-Host 'Press Enter' } # --- 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 " bump : $Bump" Write-Host "" if ($DryRun) { Write-Host '(dry run — exiting)' -ForegroundColor Yellow exit 0 } # --- Bump version in package.json --------------------------------------- Write-Host "→ Bumping package.json to $next…" -ForegroundColor Cyan $pkgJson = (Get-Content package.json -Raw) -replace "`"version`":\s*`"$current`"", "`"version`": `"$next`"" Set-Content -Path package.json -Value $pkgJson -NoNewline -Encoding utf8 git add package.json git commit -m "chore(release): $tag" # --- Build (typecheck + tests + dist) ------------------------------------ if (-not $SkipBuild) { Write-Host "→ Running typecheck…" -ForegroundColor Cyan npm run typecheck if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Write-Host "→ Running tests…" -ForegroundColor Cyan npm run test:run if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Write-Host "→ Building installer (npm run dist)…" -ForegroundColor Cyan npm run dist if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } # --- Verify artifacts exist --------------------------------------------- $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 "Не найден артефакт: $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 # --- Create release via Gitea API ---------------------------------------- Write-Host "→ Creating Gitea release $tag…" -ForegroundColor Cyan $headers = @{ Authorization = "token $env:GITEA_TOKEN" Accept = 'application/json' } # Release notes from commits since previous tag $prev = git describe --tags --abbrev=0 "$tag^" 2>$null if ($prev) { $log = git log --pretty=format:"- %s" "$prev..$tag" | Out-String } else { $log = git log --pretty=format:"- %s" "$tag" | Out-String } $body = @" ### Изменения $log --- **Установщик ниже** — запустить и следовать мастеру. Если приложение уже стояло — обновится поверх, настройки сохранятся. "@ $releaseBody = @{ tag_name = $tag name = "Exercise Reminder $tag" body = $body draft = $false prerelease = $false } | ConvertTo-Json -Depth 5 $release = Invoke-RestMethod ` -Uri "$apiBase/repos/$repoOwner/$repoName/releases" ` -Method Post ` -Headers $headers ` -Body $releaseBody ` -ContentType 'application/json' Write-Host " Release id: $($release.id)" -ForegroundColor DarkGray # --- Upload assets ------------------------------------------------------- foreach ($asset in @($installer, $blockmap, $manifest)) { $name = Split-Path $asset -Leaf Write-Host "→ Uploading $name…" -ForegroundColor Cyan $uri = "$apiBase/repos/$repoOwner/$repoName/releases/$($release.id)/assets?name=$([uri]::EscapeDataString($name))" Invoke-RestMethod ` -Uri $uri ` -Method Post ` -Headers $headers ` -InFile $asset ` -ContentType 'application/octet-stream' | Out-Null } $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 подхватит обновление на установленных копиях в течение ~6 часов."