Files
laude/scripts/upload-release-assets.ps1
AnRil d6f94ee1c9 docs+chore: retry upload on TLS/504 + refresh README/RELEASING
Upload script:
- Retry curl on transient network failures (504, schannel TLS abrupt
  close): up to 4 retries with 15s/45s/2m/5m backoff. Before each retry,
  list the release assets server-side — Gitea sometimes commits the
  body but times out the response, so the file may already be there at
  the expected size (skip retry). If present at wrong size (partial),
  delete before re-uploading. ASCII-only (PS5.1 reads files in CP1251
  without BOM).

Docs:
- README: bump release/test badges to v0.5.1 / 51 tests; mention silent
  retry in the auto-update feature line.
- RELEASING: rewrite around the new update-channel architecture, bridge
  tags, and dropped Gitea Actions workflows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 22:37:33 +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 = 'xn--90adajar8af4h.xn--p1ai/git'
$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"