feat(updater): fixed-URL auto-update channel + silent retries
The auto-update system used a per-version publish URL
(releases/download/v${version}), so each installed build only ever
checked its own release page for new versions. To deliver an update we
had to manually copy the new manifest into every old release — easy to
forget, and any half-uploaded state showed users red "check failed"
banners.
Architectural fix:
- New rolling 'update-channel' Gitea release. publish.url is now a
fixed path (.../releases/download/update-channel) that never moves.
- release.ps1 uploads each new build to three places:
1. vX.Y.Z (historical archive + changelog)
2. update-channel (what every client polls)
3. -BridgeTags (transition: also fill in old releases so users
still on those versions can find the new build)
- upload-release-assets.ps1 gains -AssetVersion to upload version-X.Y.Z
artifacts into a non-version tag (channel/bridge).
Resilience fixes for the updater itself:
- Hourly checks and the boot check now run in SILENT mode: network
errors don't promote to a red error state, they're logged and
retried on the next tick. Only user-initiated "Check now" surfaces
errors. This prevents the cascade of "Ошибка проверки" cards on
flaky networks or partial uploads.
- Boot check retries up to 3 times (30s/2m/5m backoff) before giving
up until the hourly tick.
- Track lastCheckedAt; "Up to date" subtitle now shows "checked Nm ago".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,67 +1,78 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Локальный релиз: бамп версии → коммит → тег → push → сборка → upload в Gitea release.
|
||||
Локальный релиз: бамп версии -> коммит -> тег -> push -> сборка -> upload в Gitea.
|
||||
|
||||
.DESCRIPTION
|
||||
Один скрипт от и до. Если Gitea Actions не настроено, это рабочая альтернатива.
|
||||
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.
|
||||
Альтернатива — указать -Version явно.
|
||||
|
||||
.PARAMETER Version
|
||||
Точная версия (напр. "0.3.0"). Если задана, -Bump игнорируется.
|
||||
Точная версия (напр. "0.5.1"). Если задана, -Bump игнорируется.
|
||||
|
||||
.PARAMETER SkipBuild
|
||||
Пропустить сборку (если уже собрано вручную, .exe лежит в release/).
|
||||
|
||||
.PARAMETER BridgeTags
|
||||
Список старых тегов, в которые нужно ТАКЖЕ перезалить новые артефакты,
|
||||
чтобы пользователи на этих версиях нашли апдейт. Например: v0.4.0,v0.5.0.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Показать что произойдёт, но ничего не делать.
|
||||
Показать что произойдёт, ничего не делая.
|
||||
|
||||
.EXAMPLE
|
||||
pwsh scripts/release.ps1 -Bump minor
|
||||
pwsh scripts/release.ps1 -Version 0.3.0
|
||||
pwsh scripts/release.ps1 -Bump patch -DryRun
|
||||
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
|
||||
(создаётся в Gitea: Settings → Applications → Generate New Token).
|
||||
Требует 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'
|
||||
|
||||
# --- Config ---------------------------------------------------------------
|
||||
$repoOwner = 'AnRil'
|
||||
$repoName = 'laude'
|
||||
$giteaHost = 'xn--90adajar8af4h.xn--p1ai/git'
|
||||
$apiBase = "https://$giteaHost/api/v1"
|
||||
$channelTag = 'update-channel'
|
||||
|
||||
# --- Pre-flight checks ---------------------------------------------------
|
||||
# --- Pre-flight ----------------------------------------------------------
|
||||
$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=...'
|
||||
Write-Error 'GITEA_TOKEN not set.'
|
||||
exit 1
|
||||
}
|
||||
|
||||
$status = git status --porcelain
|
||||
if ($status) {
|
||||
Write-Error "Есть незакоммиченные изменения. Сначала закоммить или stash."
|
||||
Write-Error "Uncommitted changes. Commit or stash first."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$branch = git rev-parse --abbrev-ref HEAD
|
||||
if ($branch -ne 'main') {
|
||||
Write-Warning "Текущая ветка не main, а $branch. Продолжить? (Ctrl+C для отмены)"
|
||||
Read-Host 'Press Enter'
|
||||
Write-Warning "Branch is $branch, not main. Press Enter to continue or Ctrl+C to cancel."
|
||||
Read-Host
|
||||
}
|
||||
|
||||
# --- Compute next version ------------------------------------------------
|
||||
@@ -83,109 +94,69 @@ if ($Version) {
|
||||
|
||||
$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 "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
|
||||
Write-Host '(dry run - exiting)' -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- Bump version in package.json ---------------------------------------
|
||||
Write-Host "→ Bumping package.json to $next…" -ForegroundColor Cyan
|
||||
# --- Bump 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) ------------------------------------
|
||||
# --- Quality gates ------------------------------------------------------
|
||||
if (-not $SkipBuild) {
|
||||
Write-Host "→ Running typecheck…" -ForegroundColor Cyan
|
||||
Write-Host "Typecheck..." -ForegroundColor Cyan
|
||||
npm run typecheck
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
Write-Host "→ Running tests…" -ForegroundColor Cyan
|
||||
Write-Host "Tests..." -ForegroundColor Cyan
|
||||
npm run test:run
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
Write-Host "→ Building installer (npm run dist)…" -ForegroundColor Cyan
|
||||
Write-Host "Building installer..." -ForegroundColor Cyan
|
||||
npm run dist
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
# --- Verify artifacts exist ---------------------------------------------
|
||||
# --- 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 "Не найден артефакт: $f"
|
||||
Write-Error "Artifact missing: $f"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# --- Tag + push ----------------------------------------------------------
|
||||
Write-Host "→ Tagging $tag and pushing…" -ForegroundColor Cyan
|
||||
# --- 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'
|
||||
}
|
||||
# --- Upload to all target releases --------------------------------------
|
||||
$uploadScript = Join-Path $PSScriptRoot 'upload-release-assets.ps1'
|
||||
|
||||
# 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
|
||||
$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"
|
||||
@@ -193,4 +164,4 @@ Write-Host ""
|
||||
Write-Host "Release published" -ForegroundColor Green
|
||||
Write-Host " $releaseUrl"
|
||||
Write-Host ""
|
||||
Write-Host "Auto-updater подхватит обновление на установленных копиях в течение ~6 часов."
|
||||
Write-Host "Auto-updater will pick up the new version within ~1 hour on all installed copies."
|
||||
|
||||
Reference in New Issue
Block a user