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>
228 lines
7.9 KiB
PowerShell
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"
|