Files
laude/scripts/upload-release-assets.ps1
AnRil 0cace2975d chore(remote): миграция Gitea-URL на сабдомен git.
Gitea переехал с path-prefix (xn--90adajar8af4h.xn--p1ai/git/) на
выделенный сабдомен (git.xn--90adajar8af4h.xn--p1ai). Старый URL теперь
отдаёт чужое приложение и для git мёртв.

- package.json: publish.url (канал авто-апдейта) -> новый хост
- scripts/release.ps1, upload-release-assets.ps1: $giteaHost (API + release URL)
- README, CHANGELOG, RELEASING.md, CLAUDE.md: ссылки на репозиторий/релизы

Прим.: уже установленные копии (<=0.5.8) запекли старый URL в бинарник —
их авто-апдейт нужно мигрировать отдельно (bridge-теги), правкой конфига
это ретроактивно не лечится.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 22:03:16 +07:00

228 lines
7.9 KiB
PowerShell

<#
.SYNOPSIS
Upload pre-built NSIS artifacts to a Gitea release.
.DESCRIPTION
Uploads installer + blockmap + latest.yml to the release identified by -Tag.
If the release does not exist it is created (only for semver-looking tags;
for non-semver tags like 'update-channel' the release must exist already).
Same-named existing assets are replaced.
.PARAMETER Tag
Release tag to upload INTO. May be a version tag (v0.5.1) or a channel
tag (update-channel). Defaults to v<package.json version>.
.PARAMETER AssetVersion
Version of the artifacts being uploaded (e.g. 0.5.1). Defaults to the
numeric part of -Tag. Specify explicitly when uploading version-X.Y.Z
artifacts into a non-version tag (channel or bridge).
.EXAMPLE
pwsh scripts/upload-release-assets.ps1
pwsh scripts/upload-release-assets.ps1 -Tag v0.5.0
pwsh scripts/upload-release-assets.ps1 -Tag update-channel -AssetVersion 0.5.1
pwsh scripts/upload-release-assets.ps1 -Tag v0.4.0 -AssetVersion 0.5.1
#>
param(
[string]$Tag,
[string]$AssetVersion
)
$ErrorActionPreference = 'Stop'
$repoOwner = 'AnRil'
$repoName = 'laude'
$giteaHost = 'git.xn--90adajar8af4h.xn--p1ai'
$apiBase = "https://$giteaHost/api/v1"
if (-not $env:GITEA_TOKEN) {
Write-Error "GITEA_TOKEN not set. Set it via [Environment]::SetEnvironmentVariable('GITEA_TOKEN', '<value>', 'User') and open a new PowerShell session."
exit 1
}
$root = Resolve-Path (Join-Path $PSScriptRoot '..')
Set-Location $root
if (-not $Tag) {
$pkgVersion = (Get-Content package.json | ConvertFrom-Json).version
$Tag = "v$pkgVersion"
}
if (-not $AssetVersion) {
# Derive from tag when possible (vX.Y.Z -> X.Y.Z); otherwise read package.json.
if ($Tag -match '^v\d+\.\d+\.\d+') {
$AssetVersion = $Tag.TrimStart('v')
} else {
$AssetVersion = (Get-Content package.json | ConvertFrom-Json).version
}
}
$version = $AssetVersion
$installer = Join-Path 'release' "Exercise-Reminder-Setup-$version.exe"
$blockmap = "$installer.blockmap"
$manifest = Join-Path 'release' 'latest.yml'
foreach ($f in @($installer, $blockmap, $manifest)) {
if (-not (Test-Path $f)) {
Write-Error "Artifact not found: $f. Build first with: npm run dist"
exit 1
}
}
$headers = @{
Authorization = "token $env:GITEA_TOKEN"
Accept = 'application/json'
}
# --- Find or create release ----------------------------------------------
Write-Host "Looking for existing release $Tag..." -ForegroundColor Cyan
$release = $null
try {
$release = Invoke-RestMethod `
-Uri "$apiBase/repos/$repoOwner/$repoName/releases/tags/$Tag" `
-Method Get `
-Headers $headers
Write-Host " Found release id=$($release.id)" -ForegroundColor DarkGray
} catch {
if ($_.Exception.Response.StatusCode.value__ -eq 404) {
if ($Tag -notmatch '^v\d+\.\d+\.\d+') {
Write-Error "Release '$Tag' not found and tag is not semver. Create it manually on Gitea (e.g. 'update-channel' is a one-time setup)."
exit 1
}
Write-Host " Not found, creating new release..." -ForegroundColor DarkGray
$prev = $null
try {
$prevTagOutput = & git describe --tags --abbrev=0 "$Tag^" 2>$null
if ($LASTEXITCODE -eq 0 -and $prevTagOutput) {
$prev = $prevTagOutput.Trim()
}
} catch {
$prev = $null
}
if ($prev) {
$log = (& git log --pretty=format:"- %s" "$prev..$Tag") -join "`n"
} else {
# No prior tag - list last 10 commits up to this tag.
$log = (& git log --pretty=format:"- %s" -n 10 "$Tag") -join "`n"
}
$body = "### Changes`n`n$log`n`n---`n`nInstaller below: run it; if app is already installed, it updates in place and keeps your settings."
$payload = @{
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 $payload `
-ContentType 'application/json'
Write-Host " Created release id=$($release.id)" -ForegroundColor Green
} else {
throw
}
}
# --- Delete existing assets with same names (to allow re-upload) ---------
$existing = Invoke-RestMethod `
-Uri "$apiBase/repos/$repoOwner/$repoName/releases/$($release.id)/assets" `
-Method Get `
-Headers $headers
foreach ($asset in @($installer, $blockmap, $manifest)) {
$name = Split-Path $asset -Leaf
$found = $existing | Where-Object { $_.name -eq $name }
if ($found) {
Write-Host "Removing existing asset $name (id=$($found.id))..." -ForegroundColor Yellow
Invoke-RestMethod `
-Uri "$apiBase/repos/$repoOwner/$repoName/releases/$($release.id)/assets/$($found.id)" `
-Method Delete `
-Headers $headers | Out-Null
}
}
# --- Upload assets -------------------------------------------------------
# Use curl.exe (bundled with Win10+) because Invoke-RestMethod in PS 5.1
# chokes on large multipart uploads (>50MB) over slower connections.
$curlCmd = Get-Command curl.exe -ErrorAction SilentlyContinue
if ($curlCmd) {
$curl = $curlCmd.Source
} else {
$curl = "$env:SystemRoot\System32\curl.exe"
if (-not (Test-Path $curl)) {
Write-Error "curl.exe not found. Install via 'winget install curl' or add to PATH."
exit 1
}
}
$maxRetries = 4
$backoffs = @(15, 45, 120, 300) # seconds between attempts
foreach ($asset in @($installer, $blockmap, $manifest)) {
$name = Split-Path $asset -Leaf
$size = (Get-Item $asset).Length
$uri = "$apiBase/repos/$repoOwner/$repoName/releases/$($release.id)/assets?name=$([uri]::EscapeDataString($name))"
$attempt = 0
$uploaded = $false
while (-not $uploaded -and $attempt -le $maxRetries) {
if ($attempt -gt 0) {
$wait = $backoffs[[Math]::Min($attempt - 1, $backoffs.Length - 1)]
Write-Host (" Retrying in {0}s (attempt {1}/{2})..." -f $wait, ($attempt + 1), ($maxRetries + 1)) -ForegroundColor Yellow
Start-Sleep -Seconds $wait
# Re-check whether prior attempt actually succeeded server-side before
# 504-ing the client. If asset is already there, treat as success.
try {
$check = Invoke-RestMethod `
-Uri "$apiBase/repos/$repoOwner/$repoName/releases/$($release.id)/assets" `
-Method Get -Headers $headers
$existing = $check | Where-Object { $_.name -eq $name }
if ($existing -and $existing.size -eq $size) {
Write-Host " Asset already present server-side ($($existing.size) bytes) - skipping retry." -ForegroundColor DarkGray
$uploaded = $true
break
}
# If asset is present but with wrong size (half-uploaded), delete first.
if ($existing) {
Write-Host " Removing partial asset id=$($existing.id) ($($existing.size) bytes) before retry..." -ForegroundColor DarkGray
Invoke-RestMethod `
-Uri "$apiBase/repos/$repoOwner/$repoName/releases/$($release.id)/assets/$($existing.id)" `
-Method Delete -Headers $headers | Out-Null
}
} catch {
# If the list call itself fails, just proceed with the retry.
}
}
Write-Host ("Uploading {0} ({1:N1} MB)..." -f $name, ($size / 1MB)) -ForegroundColor Cyan
& $curl `
--fail-with-body `
--silent --show-error `
--connect-timeout 30 `
--max-time 900 `
-H "Authorization: token $env:GITEA_TOKEN" `
-H "Content-Type: application/octet-stream" `
--data-binary "@$asset" `
$uri
if ($LASTEXITCODE -eq 0) {
$uploaded = $true
} else {
Write-Host " curl exit $LASTEXITCODE - will retry." -ForegroundColor Yellow
$attempt++
}
}
if (-not $uploaded) {
Write-Error "Upload failed for $name after $($maxRetries + 1) attempts."
exit 1
}
}
$releaseUrl = "https://$giteaHost/$repoOwner/$repoName/releases/tag/$Tag"
Write-Host ""
Write-Host "Release assets uploaded" -ForegroundColor Green
Write-Host " $releaseUrl"