Files
laude/scripts/release.ps1
AnRil 92e15e69a3
Some checks failed
CI / Typecheck + Tests (push) Has been cancelled
CI / Build (Windows) (push) Has been cancelled
feat: auto-update, тесты и CI/CD
Полная автоматизация релизного цикла.

== Auto-update (electron-updater) ==
- src/main/updater.ts — обёртка над autoUpdater с дискриминированным
  UpdaterStatus union и broadcast через IPC. autoDownload=false,
  пользователь сам жмёт «Скачать». allowDowngrade=false. Проверка
  каждые 6 часов, первая через 5с после старта.
- В dev-режиме (app.isPackaged=false) статус сразу становится
  'unsupported' с пояснением — никаких exceptions из updater'а.
- build.publish в package.json: provider=generic, url указывает на
  Gitea release assets конкретной версии.
- src/main/ipc.ts: 4 новых канала — status/check/download/install.
- src/preload: API window.api.updater* + onUpdaterStatus.
- src/renderer/src/components/UpdaterCard.tsx: HUD-карточка в Settings
  с состояниями idle/checking/available/downloading/downloaded/error,
  прогресс-бар с скоростью в МБ/с.

== Тесты (vitest) ==
- vitest.config.ts с алиасами @shared / @renderer
- 23 теста, все зелёные:
  * format.test.ts — formatCountdown, formatInterval (8 cases)
  * vdf.test.ts — parseVdf / stringifyVdf / round-trip (11 cases)
  * types.test.ts — DEFAULT_SETTINGS, SAMPLE_EXERCISES sanity (4)
- npm scripts: test (watch), test:run (CI)

== CI/CD (Gitea Actions) ==
- .gitea/workflows/ci.yml — на push/PR: typecheck + тесты + smoke-сборка
- .gitea/workflows/release.yml — на тег v*.*.*: сборка NSIS + Gitea release

== Локальный релизный скрипт ==
- scripts/release.ps1 — один скрипт от бампа версии до публикации
  через Gitea API (params: -Bump patch/minor/major, -Version, -DryRun)
- npm run release — обёртка
- RELEASING.md — полная инструкция

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 20:32:59 +07:00

197 lines
6.4 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.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 часов."