Il progetto viveva in una cartella senza alcun commit: tutto il lavoro stava
solo sul disco. Trasferito qui, dove esiste una storia e un remoto.
Mancava soltanto build/: i sorgenti erano gia' allineati e identici. Nessun
percorso e' stato toccato — Release.proj usa MSBuildThisFileDirectory e
tasks.json usa ${workspaceFolder}, quindi il trasloco non li riguarda.
- build/Release.proj: verifica, backtest, pacchetto e rilascio in un solo file
MSBuild, senza script. Provato dalla nuova posizione: 287 test verdi,
installatore prodotto, backtest eseguito.
- build/gitea.example.json: url, owner e repo veri; resta da mettere il token
in build/gitea.json, che e' ignorato da git.
- build/sostituiti/: i quattro .ps1 rimpiazzati da Release.proj. Entrano qui
solo per non perderli con la vecchia cartella: da ora sono recuperabili
dalla storia e si possono cancellare.
- .gitignore: gitea.json (contiene una credenziale), i riepiloghi del backtest
e i pacchetti in bin/installer.
Versione 4.12.0 -> 4.13.0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
285 lines
10 KiB
PowerShell
285 lines
10 KiB
PowerShell
<#
|
|
rilascia.ps1 — dalla sorgente al pacchetto pubblicato su Gitea, in un comando.
|
|
|
|
Passi:
|
|
1. verifica.ps1 (compila + test) — saltabile con -SaltaVerifica
|
|
2. pubblica l'eseguibile self-contained
|
|
3. compila l'installatore con Inno Setup
|
|
4. crea il tag, la release su Gitea e carica i file
|
|
|
|
Uso:
|
|
powershell -ExecutionPolicy Bypass -File .\rilascia.ps1
|
|
powershell -ExecutionPolicy Bypass -File .\rilascia.ps1 -SoloPacchetto
|
|
powershell -ExecutionPolicy Bypass -File .\rilascia.ps1 -Note "Cosa cambia"
|
|
|
|
Configurazione di Gitea (una volta sola): copia gitea.example.json in
|
|
gitea.json e riempilo. Il file è escluso dal controllo di versione perché
|
|
contiene un token. In alternativa, le variabili d'ambiente
|
|
GITEA_URL / GITEA_OWNER / GITEA_REPO / GITEA_TOKEN hanno la precedenza.
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
# Ferma dopo aver creato l'installatore, senza toccare Gitea.
|
|
[switch] $SoloPacchetto,
|
|
|
|
# Salta compilazione e test. Da usare solo se li hai appena eseguiti.
|
|
[switch] $SaltaVerifica,
|
|
|
|
# Testo delle note di rilascio. Vuoto = generate dai commit dall'ultimo tag.
|
|
[string] $Note = "",
|
|
|
|
# Contrassegna la release come bozza o come anteprima.
|
|
[switch] $Bozza,
|
|
[switch] $Anteprima,
|
|
|
|
# Sovrascrive una release con lo stesso tag invece di fermarsi.
|
|
[switch] $Sovrascrivi
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
Set-Location $PSScriptRoot
|
|
|
|
function Titolo($t) { Write-Host "`n== $t ==" -ForegroundColor Cyan }
|
|
function Ok($t) { Write-Host " $t" -ForegroundColor Green }
|
|
function Nota($t) { Write-Host " $t" -ForegroundColor DarkGray }
|
|
function Errore($t) { Write-Host "`n$t" -ForegroundColor Red }
|
|
|
|
# ── 0. Versione ──────────────────────────────────────────────────────────────
|
|
# La versione ha una sola origine: il .csproj. Ripeterla qui vorrebbe dire
|
|
# vederle divergere al primo rilascio fatto di fretta.
|
|
[xml] $csproj = Get-Content .\AutoBidder.csproj
|
|
$versione = ($csproj.Project.PropertyGroup | Where-Object { $_.Version } | Select-Object -First 1).Version
|
|
|
|
if (-not $versione) {
|
|
Errore "Versione non trovata in AutoBidder.csproj (<Version>)."
|
|
exit 1
|
|
}
|
|
|
|
$tag = "v$versione"
|
|
Titolo "AutoBidder $versione"
|
|
|
|
# ── 1. Verifica ──────────────────────────────────────────────────────────────
|
|
if ($SaltaVerifica) {
|
|
Nota "verifica saltata su richiesta"
|
|
} else {
|
|
Titolo "Verifica (compilazione + test)"
|
|
& powershell -ExecutionPolicy Bypass -File .\verifica.ps1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Errore "Verifica fallita: niente da rilasciare."
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# ── 2. Eseguibile ────────────────────────────────────────────────────────────
|
|
Titolo "Pubblicazione dell'eseguibile"
|
|
|
|
$pubDir = Join-Path $PSScriptRoot "bin\publish\win-x64"
|
|
$exe = Join-Path $pubDir "AutoBidder.exe"
|
|
|
|
dotnet publish .\AutoBidder.csproj -c Release -r win-x64 --nologo -v q `
|
|
--self-contained true `
|
|
-p:PublishSingleFile=true `
|
|
-p:IncludeNativeLibrariesForSelfExtract=true `
|
|
-p:EnableCompressionInSingleFile=true `
|
|
-p:PublishReadyToRun=true `
|
|
-p:PublishTrimmed=false `
|
|
-o $pubDir
|
|
|
|
if ($LASTEXITCODE -ne 0 -or -not (Test-Path $exe)) {
|
|
Errore "Pubblicazione fallita."
|
|
exit 1
|
|
}
|
|
|
|
Ok ("AutoBidder.exe ({0:N1} MB)" -f ((Get-Item $exe).Length / 1MB))
|
|
|
|
# La versione scritta nell'eseguibile deve corrispondere: se qualcuno cambia il
|
|
# .csproj senza ricompilare, il pacchetto porterebbe un numero e il programma un altro.
|
|
$versioneExe = (Get-Item $exe).VersionInfo.ProductVersion
|
|
if ($versioneExe -and -not $versioneExe.StartsWith($versione)) {
|
|
Errore "L'eseguibile dichiara $versioneExe ma il progetto dice $versione."
|
|
exit 1
|
|
}
|
|
|
|
# ── 3. Installatore ──────────────────────────────────────────────────────────
|
|
Titolo "Creazione dell'installatore"
|
|
|
|
$iscc = @(
|
|
"$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe",
|
|
"$env:ProgramFiles\Inno Setup 6\ISCC.exe",
|
|
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe"
|
|
) | Where-Object { Test-Path $_ } | Select-Object -First 1
|
|
|
|
if (-not $iscc) {
|
|
Errore "Inno Setup 6 non trovato."
|
|
Write-Host "Installalo con: winget install -e --id JRSoftware.InnoSetup" -ForegroundColor Yellow
|
|
exit 1
|
|
}
|
|
|
|
$outDir = Join-Path $PSScriptRoot "bin\installer"
|
|
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
|
|
|
|
& $iscc /Qp `
|
|
"/DAppVersion=$versione" `
|
|
"/DSourceExe=$exe" `
|
|
"/DOutputDir=$outDir" `
|
|
".\installer\AutoBidder.iss"
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Errore "Inno Setup ha restituito un errore."
|
|
exit 1
|
|
}
|
|
|
|
$setup = Join-Path $outDir "AutoBidder-$versione-setup.exe"
|
|
if (-not (Test-Path $setup)) {
|
|
Errore "Installatore non trovato: $setup"
|
|
exit 1
|
|
}
|
|
|
|
Ok ("{0} ({1:N1} MB)" -f (Split-Path $setup -Leaf), ((Get-Item $setup).Length / 1MB))
|
|
|
|
if ($SoloPacchetto) {
|
|
Write-Host "`nPacchetto pronto in $outDir" -ForegroundColor Green
|
|
Write-Host "Gitea non è stato toccato (-SoloPacchetto)." -ForegroundColor DarkGray
|
|
exit 0
|
|
}
|
|
|
|
# ── 4. Gitea ─────────────────────────────────────────────────────────────────
|
|
Titolo "Pubblicazione su Gitea"
|
|
|
|
$cfgFile = Join-Path $PSScriptRoot "gitea.json"
|
|
$cfg = if (Test-Path $cfgFile) { Get-Content $cfgFile -Raw | ConvertFrom-Json } else { $null }
|
|
|
|
function Impostazione($nomeEnv, $campo) {
|
|
$v = [Environment]::GetEnvironmentVariable($nomeEnv)
|
|
if ($v) { return $v }
|
|
if ($cfg -and $cfg.PSObject.Properties.Name -contains $campo) { return $cfg.$campo }
|
|
return $null
|
|
}
|
|
|
|
$url = Impostazione 'GITEA_URL' 'url'
|
|
$owner = Impostazione 'GITEA_OWNER' 'owner'
|
|
$repo = Impostazione 'GITEA_REPO' 'repo'
|
|
$token = Impostazione 'GITEA_TOKEN' 'token'
|
|
|
|
if (-not $url -or -not $owner -or -not $repo -or -not $token) {
|
|
Errore "Configurazione di Gitea incompleta."
|
|
Write-Host @"
|
|
Servono quattro valori: url, owner, repo, token.
|
|
|
|
1. copia gitea.example.json in gitea.json e riempilo, oppure
|
|
2. imposta GITEA_URL, GITEA_OWNER, GITEA_REPO, GITEA_TOKEN.
|
|
|
|
Il token si crea in Gitea da Impostazioni > Applicazioni > Genera nuovo token,
|
|
con il permesso 'repository: read and write'.
|
|
|
|
L'installatore è comunque pronto in:
|
|
$setup
|
|
"@ -ForegroundColor Yellow
|
|
exit 1
|
|
}
|
|
|
|
$url = $url.TrimEnd('/')
|
|
$api = "$url/api/v1/repos/$owner/$repo"
|
|
$headers = @{ Authorization = "token $token" }
|
|
|
|
Nota "$url/$owner/$repo tag $tag"
|
|
|
|
# Note di rilascio: se non le detta l'utente, si prendono dai commit.
|
|
if (-not $Note) {
|
|
$ultimo = & git describe --tags --abbrev=0 2>$null
|
|
$intervallo = if ($LASTEXITCODE -eq 0 -and $ultimo) { "$ultimo..HEAD" } else { "HEAD" }
|
|
$commit = & git log $intervallo --pretty=format:"- %s" --no-merges 2>$null
|
|
|
|
$Note = if ($commit) { ($commit | Out-String).Trim() } else { "Versione $versione." }
|
|
}
|
|
|
|
# Il tag deve esistere sul server: la release ci si appoggia.
|
|
$rami = & git rev-parse --git-dir 2>$null
|
|
if ($LASTEXITCODE -eq 0) {
|
|
$esiste = & git tag -l $tag
|
|
if (-not $esiste) {
|
|
& git tag -a $tag -m "AutoBidder $versione"
|
|
Ok "tag $tag creato"
|
|
}
|
|
|
|
$remoto = & git remote 2>$null | Select-Object -First 1
|
|
if ($remoto) {
|
|
& git push $remoto $tag 2>&1 | Out-Null
|
|
if ($LASTEXITCODE -eq 0) { Ok "tag inviato a $remoto" }
|
|
else { Nota "tag non inviato (probabilmente c'era già)" }
|
|
} else {
|
|
Nota "nessun remoto git configurato: la release userà il tag come nome"
|
|
}
|
|
}
|
|
|
|
# Release già presente?
|
|
$esistente = $null
|
|
try {
|
|
$esistente = Invoke-RestMethod -Uri "$api/releases/tags/$tag" -Headers $headers -Method Get
|
|
} catch {
|
|
# 404 = non c'è, ed è il caso normale.
|
|
}
|
|
|
|
if ($esistente) {
|
|
if (-not $Sovrascrivi) {
|
|
Errore "La release $tag esiste già su Gitea."
|
|
Write-Host "Alza <Version> in AutoBidder.csproj, oppure rilancia con -Sovrascrivi." -ForegroundColor Yellow
|
|
exit 1
|
|
}
|
|
|
|
Nota "release $tag esistente: verrà sostituita"
|
|
Invoke-RestMethod -Uri "$api/releases/$($esistente.id)" -Headers $headers -Method Delete | Out-Null
|
|
}
|
|
|
|
$corpo = @{
|
|
tag_name = $tag
|
|
name = "AutoBidder $versione"
|
|
body = $Note
|
|
draft = [bool]$Bozza
|
|
prerelease = [bool]$Anteprima
|
|
} | ConvertTo-Json
|
|
|
|
try {
|
|
$release = Invoke-RestMethod -Uri "$api/releases" -Headers $headers -Method Post `
|
|
-ContentType 'application/json' -Body $corpo
|
|
} catch {
|
|
Errore "Creazione della release non riuscita: $($_.Exception.Message)"
|
|
if ($_.ErrorDetails.Message) { Write-Host $_.ErrorDetails.Message -ForegroundColor DarkGray }
|
|
Write-Host "`nL'installatore resta disponibile in:`n $setup" -ForegroundColor Yellow
|
|
exit 1
|
|
}
|
|
|
|
Ok "release creata"
|
|
|
|
# ── 5. File allegati ─────────────────────────────────────────────────────────
|
|
# Si carica sia l'installatore sia l'eseguibile nudo: chi non vuole installare
|
|
# nulla continua a poter scaricare il solo .exe, che è come l'app è nata.
|
|
function Carica($percorso) {
|
|
$nome = Split-Path $percorso -Leaf
|
|
$uri = "$api/releases/$($release.id)/assets?name=$([uri]::EscapeDataString($nome))"
|
|
|
|
try {
|
|
Invoke-RestMethod -Uri $uri -Headers $headers -Method Post -Form @{
|
|
attachment = Get-Item $percorso
|
|
} | Out-Null
|
|
Ok ("caricato {0} ({1:N1} MB)" -f $nome, ((Get-Item $percorso).Length / 1MB))
|
|
return $true
|
|
} catch {
|
|
Errore "Caricamento di $nome non riuscito: $($_.Exception.Message)"
|
|
if ($_.ErrorDetails.Message) { Write-Host $_.ErrorDetails.Message -ForegroundColor DarkGray }
|
|
return $false
|
|
}
|
|
}
|
|
|
|
$tuttoOk = (Carica $setup) -and (Carica $exe)
|
|
|
|
if (-not $tuttoOk) {
|
|
Errore "Release creata ma senza tutti i file. Caricali a mano da $url/$owner/$repo/releases"
|
|
exit 1
|
|
}
|
|
|
|
Write-Host "`nRilascio completato." -ForegroundColor Green
|
|
Write-Host " $url/$owner/$repo/releases/tag/$tag" -ForegroundColor Green
|
|
exit 0
|