Porta la catena di rilascio nel repository ufficiale

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>
This commit is contained in:
2026-08-04 22:15:53 +02:00
co-authored by Claude Opus 5
parent 7ca504a70a
commit 99b3030180
10 changed files with 1191 additions and 4 deletions
+4 -4
View File
@@ -17,10 +17,10 @@
WebView2 runtime è preinstallato su Windows 11. -->
<!-- Unico numero da toccare a ogni nuova versione: titolo finestra e log d'avvio
lo leggono a runtime tramite Utilities/AppInfo. -->
<Version>4.12.0</Version>
<AssemblyVersion>4.12.0.0</AssemblyVersion>
<FileVersion>4.12.0.0</FileVersion>
<InformationalVersion>4.12.0</InformationalVersion>
<Version>4.13.0</Version>
<AssemblyVersion>4.13.0.0</AssemblyVersion>
<FileVersion>4.13.0.0</FileVersion>
<InformationalVersion>4.13.0</InformationalVersion>
<!-- Runtime unico supportato per la pubblicazione self-contained -->
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
+123
View File
@@ -0,0 +1,123 @@
; ─────────────────────────────────────────────────────────────────────────────
; AutoBidder — script di installazione (Inno Setup 6)
;
; Non si compila a mano: lo lancia build/Release.proj, che prima pubblica
; l'eseguibile e poi passa qui versione e percorsi con /D. Compilarlo da solo
; produrrebbe un pacchetto con la versione sbagliata, perché il numero vive
; nel .csproj.
;
; dotnet msbuild build/Release.proj -t:Pacchetto
;
; L'applicazione è un unico .exe self-contained: l'installazione copia quel file,
; crea i collegamenti e registra la voce per "App installate". I dati dell'utente
; stanno in %LocalAppData%\AutoBidder e NON vengono toccati né dall'aggiornamento
; né dalla disinstallazione — sono aste, statistiche e sessione, cioè mesi di
; raccolta che nessun disinstallatore ha il diritto di buttare via.
; ─────────────────────────────────────────────────────────────────────────────
#ifndef AppVersion
#define AppVersion "0.0.0"
#endif
#ifndef SourceExe
#define SourceExe "..\bin\publish\win-x64\AutoBidder.exe"
#endif
#ifndef OutputDir
#define OutputDir "..\bin\installer"
#endif
#define AppName "AutoBidder"
#define AppPublisher "Alberto Balbo"
#define AppExeName "AutoBidder.exe"
[Setup]
; L'AppId identifica il prodotto fra una versione e l'altra: cambiarlo farebbe
; comparire due voci in "App installate" invece di un aggiornamento.
AppId={{9F2C6E14-8B3D-4A57-9E10-6C4B0D7A1F52}
AppName={#AppName}
AppVersion={#AppVersion}
AppVerName={#AppName} {#AppVersion}
AppPublisher={#AppPublisher}
VersionInfoVersion={#AppVersion}
; Installazione per utente: niente richiesta di amministratore, e i dati stanno
; comunque nel profilo di chi la usa.
PrivilegesRequired=lowest
DefaultDirName={autopf}\{#AppName}
DefaultGroupName={#AppName}
DisableProgramGroupPage=yes
DisableDirPage=auto
OutputDir={#OutputDir}
OutputBaseFilename=AutoBidder-{#AppVersion}-setup
SetupIconFile=..\Icon\favicon.ico
UninstallDisplayIcon={app}\{#AppExeName}
UninstallDisplayName={#AppName} {#AppVersion}
; L'eseguibile è già compresso (single-file compresso): comprimerlo di nuovo
; costa minuti di build e guadagna poco.
Compression=lzma2/normal
SolidCompression=no
WizardStyle=modern
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
[Languages]
Name: "italiano"; MessagesFile: "compiler:Languages\Italian.isl"
[Tasks]
Name: "desktopicon"; Description: "Crea un collegamento sul desktop"; GroupDescription: "Collegamenti:"
Name: "avvioautomatico"; Description: "Avvia AutoBidder all'accesso a Windows"; GroupDescription: "Avvio:"; Flags: unchecked
[Files]
Source: "{#SourceExe}"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"
Name: "{group}\Disinstalla {#AppName}"; Filename: "{uninstallexe}"
Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon
[Registry]
Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; \
ValueType: string; ValueName: "{#AppName}"; ValueData: """{app}\{#AppExeName}"""; \
Flags: uninsdeletevalue; Tasks: avvioautomatico
[Run]
Filename: "{app}\{#AppExeName}"; Description: "Avvia {#AppName}"; \
Flags: nowait postinstall skipifsilent
[UninstallDelete]
; Solo quello che ha creato l'installazione. I dati restano.
Type: dirifempty; Name: "{app}"
[Code]
{ Un'installazione sopra una copia in esecuzione lascerebbe il vecchio eseguibile
al suo posto senza dirlo, e l'utente si ritroverebbe la versione precedente
convinto di aver aggiornato. Meglio fermarsi e chiedere. }
function IsAppRunning(): Boolean;
var
ResultCode: Integer;
begin
Result := Exec('cmd.exe',
'/C tasklist /FI "IMAGENAME eq {#AppExeName}" | find /I "{#AppExeName}"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
end;
function InitializeSetup(): Boolean;
begin
Result := True;
if IsAppRunning() then
begin
if MsgBox('AutoBidder è in esecuzione e sta forse seguendo delle aste.' + #13#10#13#10 +
'Chiudilo prima di continuare, altrimenti il file non può essere sostituito.' + #13#10#13#10 +
'Riprovo?', mbConfirmation, MB_YESNO) = IDYES then
Result := not IsAppRunning()
else
Result := False;
if not Result then
MsgBox('Installazione annullata: AutoBidder è ancora aperto.', mbInformation, MB_OK);
end;
end;
+87
View File
@@ -0,0 +1,87 @@
# Catena di verifica, pacchetto e rilascio
Tutto quello che serve a controllare, impacchettare e pubblicare AutoBidder sta in questa
cartella. La radice del progetto non contiene script.
| File | Cos'è |
|---|---|
| `Release.proj` | La catena. Un solo file MSBuild, nessuno script. |
| `AutoBidder.iss` | Lo script di Inno Setup. Non si compila a mano: lo lancia `Release.proj`. |
| `gitea.example.json` | Modello per `gitea.json` (che è escluso dal controllo di versione). |
| `sostituiti/` | I vecchi script PowerShell, tenuti finché non si fa la pulizia. Non servono più. |
## Da VS Code
**Terminale ▸ Esegui attività…**
| Attività | Cosa fa |
|---|---|
| `verifica` | Compila e lancia i test. |
| `backtest` | Rigioca i dossier delle aste concluse. |
| `backtest (giro veloce, 400 aste)` | Come sopra, su un campione. |
| `crea installatore` | Chiede la versione, verifica, pubblica, esegue Inno Setup. |
| `crea installatore (senza rieseguire i test)` | Solo pubblicazione e installatore. |
| `rilascia su Gitea` | Tutto quanto sopra, più tag e release con i file allegati. |
## Da riga di comando
```powershell
dotnet msbuild build/Release.proj -t:Verifica
dotnet msbuild build/Release.proj -t:Backtest -p:MaxDossier=400
dotnet msbuild build/Release.proj -t:Pacchetto
dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=4.13.0 -p:Note="Cosa cambia"
```
| Proprietà | Predefinito | A cosa serve |
|---|---|---|
| `Versione` | vuoto | Vuoto = incrementa la minor (4.12.0 → 4.13.0). Altrimenti la scrive, se ha la forma `X.Y.Z`. |
| `Note` | vuoto | Note di rilascio. |
| `SaltaVerifica` | `false` | Non rieseguire i test. |
| `Sovrascrivi` | `false` | Sostituisci una release Gitea con lo stesso tag. |
| `Bozza` | `false` | Crea la release come bozza. |
| `MaxDossier` | `0` | Quanti dossier leggere nel backtest (0 = tutti). |
| `CartellaDossier` | `%LocalAppData%\AutoBidder\Dati\Registri\Aste` | Dove stanno i dossier. |
## Chi chiede la versione
MSBuild non può chiedere niente a nessuno: è un motore di compilazione. La domanda la fa
l'attività di VS Code (`inputs` in `.vscode/tasks.json`) e passa la risposta in
`-p:Versione=`. Lasciando il campo vuoto si prende la minor successiva, che è il caso
normale di fine sessione.
**La versione si scrive solo dopo che i test sono passati.** Il contrario sembra più
naturale — decidi il numero, poi costruisci — ma lascia il `.csproj` alzato quando la
verifica fallisce, e il tentativo dopo riparte da lì: il numero sale senza che sia mai
esistito un pacchetto con quella versione.
## Gitea
Servono quattro valori. Le variabili d'ambiente hanno la precedenza sul file, così una
macchina condivisa può rilasciare senza scrivere un token su disco:
- `GITEA_URL`, `GITEA_OWNER`, `GITEA_REPO`, `GITEA_TOKEN`
- oppure `build/gitea.json`, copiato da `gitea.example.json`
Il token si crea in Gitea da *Impostazioni ▸ Applicazioni ▸ Genera nuovo token*, con il
permesso `repository: read and write`.
Nella release vengono caricati **sia l'installatore sia l'eseguibile nudo**: chi non vuole
installare niente deve continuare a poter scaricare il solo `.exe`.
## Una trappola già pagata
`dotnet test` e `dotnet publish` lanciati **da dentro** MSBuild ereditano l'ambiente del
processo padre. La compilazione WPF crea un progetto temporaneo (`_wpftmp.csproj`) e con
`MSBUILD_EXE_PATH` puntata al build in corso non genera più le classi parziali dello XAML:
si ottengono decine di *«AuctionMonitorControl non contiene una definizione di …»* che non
hanno niente a che vedere col codice.
Per questo gli `Exec` azzerano `MSBUILD_EXE_PATH` e `MSBuildLoadMicrosoftTargetsReadOnly`.
**Solo quelle due**: la ricetta che gira in rete azzera anche `MSBuildExtensionsPath` e
`MSBuildSDKsPath`, e così il figlio perde la posizione dell'SDK — *«l'SDK Microsoft.NET.Sdk
specificato non è stato trovato»*. Serve isolare il motore, non nascondergli dove abita.
## Prerequisiti
- .NET SDK 10
- [Inno Setup 6](https://jrsoftware.org/isinfo.php) — `winget install -e --id JRSoftware.InnoSetup`
+513
View File
@@ -0,0 +1,513 @@
<!--
═══════════════════════════════════════════════════════════════════════════
AutoBidder — catena di verifica, pacchetto e rilascio
═══════════════════════════════════════════════════════════════════════════
Un solo file, nessuno script. Si richiama con `dotnet msbuild`, dalle attività
di VS Code (Terminale ▸ Esegui attività…) oppure a mano:
dotnet msbuild build/Release.proj -t:Verifica
dotnet msbuild build/Release.proj -t:Backtest
dotnet msbuild build/Release.proj -t:Pacchetto
dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=4.12.0
── Perché MSBuild e non uno script ──────────────────────────────────────
La catena vive accanto al codice che rilascia ed è versionata con lui: fra
sei mesi, ripescato un tag, questo file ricostruisce quel pacchetto e non
quello di oggi. La logica non banale (leggere e riscrivere la versione,
parlare con Gitea) sta in attività C# in linea: si legge come codice, non
come una successione di comandi.
── Chi chiede la versione ───────────────────────────────────────────────
MSBuild non può chiedere niente a nessuno: è un motore di compilazione, non
un programma interattivo. La domanda la fa l'attività di VS Code, che passa
qui la risposta in `-p:Versione=`. Lasciandola vuota si prende la minor
successiva a quella scritta nel .csproj — che è il caso normale, e infatti è
il predefinito del prompt.
-->
<Project DefaultTargets="Pacchetto" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Radice>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..'))</Radice>
<Csproj>$(Radice)\AutoBidder.csproj</Csproj>
<TestProj>$(Radice)\Tests\AutoBidder.Tests.csproj</TestProj>
<Iss>$(MSBuildThisFileDirectory)AutoBidder.iss</Iss>
<CartellaPubblicazione>$(Radice)\bin\publish\win-x64</CartellaPubblicazione>
<CartellaPacchetti>$(Radice)\bin\installer</CartellaPacchetti>
<!-- ── Perché una cartella a parte per verifica e backtest ─────────────
Due ragioni, e servono entrambe.
La prima: l'applicazione può essere aperta mentre si lavora, e tiene
bloccato AutoBidder.exe — la compilazione si fermerebbe su MSB3027.
La seconda: l'opzione artifacts-path sposta anche gli INTERMEDI, non
solo il risultato. La sola -o li lascia nella obj/ condivisa, e la
compilazione WPF — che genera un progetto temporaneo `_wpftmp.csproj` a
ogni giro — ogni tanto ci trovava stato altrui e smetteva di produrre
le classi parziali dello XAML. Il sintomo era una raffica di
"AuctionMonitorControl non contiene una definizione di ...", a giri
alterni, senza che il codice fosse cambiato. -->
<CartellaProve>$([System.IO.Path]::GetTempPath())AutoBidder.Verifica</CartellaProve>
<!-- Vuoto = incrementa la minor. Vedi TrovaVersione. -->
<Versione Condition="'$(Versione)' == ''"></Versione>
<!-- Note di rilascio; vuote = ricavate dai commit dall'ultimo tag. -->
<Note Condition="'$(Note)' == ''"></Note>
<!-- ── Perché serve azzerare queste variabili ──────────────────────────
`dotnet test` e `dotnet publish` lanciati da dentro MSBuild ereditano
l'ambiente del processo padre. La compilazione WPF crea un progetto
temporaneo (_wpftmp.csproj) e con quelle variabili puntate al build in
corso non genera più le classi parziali dello XAML: si ottengono decine
di "AuctionMonitorControl non contiene una definizione di ..." che non
hanno niente a che vedere col codice.
Si azzerano SOLO queste due. Togliere anche MSBuildExtensionsPath o
MSBuildSDKsPath — la ricetta che gira in rete — fa perdere al figlio la
posizione dell'SDK: "l'SDK Microsoft.NET.Sdk specificato non è stato
trovato". Serve isolare il motore, non nascondergli dove abita. -->
<AmbientePulito>MSBUILD_EXE_PATH=;MSBuildLoadMicrosoftTargetsReadOnly=</AmbientePulito>
<SaltaVerifica Condition="'$(SaltaVerifica)' == ''">false</SaltaVerifica>
<Sovrascrivi Condition="'$(Sovrascrivi)' == ''">false</Sovrascrivi>
<Bozza Condition="'$(Bozza)' == ''">false</Bozza>
</PropertyGroup>
<!-- ═════════════════════ Attività in linea ═════════════════════ -->
<UsingTask TaskName="TrovaVersione" TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Csproj ParameterType="System.String" Required="true" />
<Richiesta ParameterType="System.String" />
<SoloLettura ParameterType="System.Boolean" />
<Attuale ParameterType="System.String" Output="true" />
<Effettiva ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Text.RegularExpressions" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var testo = File.ReadAllText(Csproj);
var m = Regex.Match(testo, @"<Version>([^<]+)</Version>");
if (!m.Success) { Log.LogError("<Version> non trovato in " + Csproj); return false; }
Attuale = m.Groups[1].Value.Trim();
// Rilettura secca: serve solo a sapere cosa c'e' scritto adesso, senza
// proporre incrementi e senza scrivere una riga di registro fuorviante.
if (SoloLettura) { Effettiva = Attuale; return true; }
var chiesta = (Richiesta ?? "").Trim();
if (chiesta.Length > 0)
{
// Una versione scritta a mano si accetta solo se ha la forma giusta:
// un refuso qui produrrebbe un tag e un pacchetto sbagliati.
if (!Regex.IsMatch(chiesta, @"^\d+\.\d+\.\d+$"))
{
Log.LogError("Versione '" + chiesta + "' non valida: serve la forma X.Y.Z");
return false;
}
Effettiva = chiesta;
}
else
{
// Vuota: la minor successiva. E' il caso normale di una sessione di lavoro.
var p = Attuale.Split('.');
if (p.Length < 2) { Log.LogError("Versione attuale illeggibile: " + Attuale); return false; }
Effettiva = p[0] + "." + (int.Parse(p[1]) + 1) + ".0";
}
Log.LogMessage(MessageImportance.High, " versione: " + Attuale + " -> " + Effettiva);
]]>
</Code>
</Task>
</UsingTask>
<UsingTask TaskName="ScriviVersione" TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Csproj ParameterType="System.String" Required="true" />
<Versione ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Text.RegularExpressions" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var testo = File.ReadAllText(Csproj);
// Quattro elementi, un solo numero: Utilities/AppInfo li legge a runtime, e
// vederne divergere uno significa un titolo finestra che mente.
testo = Regex.Replace(testo, @"<Version>[^<]+</Version>", "<Version>" + Versione + "</Version>");
testo = Regex.Replace(testo, @"<AssemblyVersion>[^<]+</AssemblyVersion>", "<AssemblyVersion>" + Versione + ".0</AssemblyVersion>");
testo = Regex.Replace(testo, @"<FileVersion>[^<]+</FileVersion>", "<FileVersion>" + Versione + ".0</FileVersion>");
testo = Regex.Replace(testo, @"<InformationalVersion>[^<]+</InformationalVersion>", "<InformationalVersion>" + Versione + "</InformationalVersion>");
File.WriteAllText(Csproj, testo);
Log.LogMessage(MessageImportance.High, " .csproj aggiornato a " + Versione);
]]>
</Code>
</Task>
</UsingTask>
<UsingTask TaskName="TrovaInnoSetup" TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Percorso ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var candidati = new[]
{
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Inno Setup 6\ISCC.exe"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Inno Setup 6\ISCC.exe"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Inno Setup 6\ISCC.exe"),
};
foreach (var c in candidati)
if (File.Exists(c)) { Percorso = c; break; }
if (string.IsNullOrEmpty(Percorso))
Log.LogError("Inno Setup 6 non trovato. Installalo con: winget install -e --id JRSoftware.InnoSetup");
]]>
</Code>
</Task>
</UsingTask>
<!--
Gitea si raggiunge con curl, di serie in Windows 10 e 11.
L'alternativa naturale sarebbe HttpClient in un'attività C# in linea, ma
RoslynCodeTaskFactory referenzia solo gli assembly di base: System.Net.Http e
System.Text.Json andrebbero indicati per percorso assoluto, dentro il runtime
condiviso, con il numero di versione nel mezzo. Un percorso che oggi funziona e
al prossimo aggiornamento di .NET no. curl non ha questo problema.
Il token NON passa mai dalla riga di comando: sta in un file di configurazione
di curl, che viene cancellato subito dopo. Gli Exec hanno EchoOff perché un
registro di compilazione è la classica cosa che si incolla in una chat.
-->
<UsingTask TaskName="LeggiConfigGitea" TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Percorso ParameterType="System.String" Required="true" />
<Url ParameterType="System.String" Output="true" />
<Owner ParameterType="System.String" Output="true" />
<Repo ParameterType="System.String" Output="true" />
<Token ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Text.RegularExpressions" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var testo = File.Exists(Percorso) ? File.ReadAllText(Percorso) : "";
// Le variabili d'ambiente hanno la precedenza sul file: una macchina
// condivisa deve poter rilasciare senza scrivere un token su disco, e un
// file dimenticato non deve vincere su una scelta esplicita.
Func<string,string,string> leggi = (env, campo) =>
{
var v = Environment.GetEnvironmentVariable(env);
if (!string.IsNullOrWhiteSpace(v)) return v.Trim();
var m = Regex.Match(testo, "\"" + campo + "\"\\s*:\\s*\"([^\"]*)\"");
return m.Success ? m.Groups[1].Value.Trim() : "";
};
Url = leggi("GITEA_URL", "url").TrimEnd('/');
Owner = leggi("GITEA_OWNER", "owner");
Repo = leggi("GITEA_REPO", "repo");
Token = leggi("GITEA_TOKEN", "token");
if (Url.Length == 0 || Owner.Length == 0 || Repo.Length == 0 || Token.Length == 0)
{
Log.LogError(
"Configurazione di Gitea incompleta. Servono url, owner, repo, token:\n" +
" copia build/gitea.example.json in build/gitea.json e riempilo,\n" +
" oppure imposta GITEA_URL, GITEA_OWNER, GITEA_REPO, GITEA_TOKEN.\n" +
"Il pacchetto e' comunque pronto in bin/installer.");
// Senza questo il target prosegue lo stesso: git tag, poi curl con
// l'indirizzo vuoto, e infine un "codice 3" che non dice niente a
// nessuno. Un errore va fermato dove si capisce ancora cos'era.
return false;
}
]]>
</Code>
</Task>
</UsingTask>
<UsingTask TaskName="PreparaCorpoRelease" TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Destinazione ParameterType="System.String" Required="true" />
<Tag ParameterType="System.String" Required="true" />
<Versione ParameterType="System.String" Required="true" />
<Note ParameterType="System.String" />
<Bozza ParameterType="System.Boolean" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Code Type="Fragment" Language="cs">
<![CDATA[
// Le note arrivano da un prompt: possono contenere virgolette, barre e
// a-capo. Scritte grezze romperebbero il JSON, o peggio lo cambierebbero.
Func<string,string> esc = t => (t ?? "")
.Replace("\\", "\\\\").Replace("\"", "\\\"")
.Replace("\r", "").Replace("\n", "\\n").Replace("\t", " ");
var note = string.IsNullOrWhiteSpace(Note) ? "Versione " + Versione + "." : Note;
File.WriteAllText(Destinazione,
"{\"tag_name\":\"" + esc(Tag) + "\"," +
"\"name\":\"AutoBidder " + esc(Versione) + "\"," +
"\"body\":\"" + esc(note) + "\"," +
"\"draft\":" + (Bozza ? "true" : "false") + "," +
"\"prerelease\":false}");
]]>
</Code>
</Task>
</UsingTask>
<UsingTask TaskName="LeggiIdRelease" TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Risposta ParameterType="System.String" Required="true" />
<Id ParameterType="System.String" Output="true" />
<Errore ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Text.RegularExpressions" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var testo = File.Exists(Risposta) ? File.ReadAllText(Risposta) : "";
// L'id della release e' il primo campo "id" della risposta; quelli annidati
// (autore, allegati) vengono dopo. Si prende il primo e basta.
var m = Regex.Match(testo, "\"id\"\\s*:\\s*(\\d+)");
Id = m.Success ? m.Groups[1].Value : "";
if (Id.Length == 0)
{
var msg = Regex.Match(testo, "\"message\"\\s*:\\s*\"([^\"]*)\"");
Errore = msg.Success ? msg.Groups[1].Value
: (testo.Length > 200 ? testo.Substring(0, 200) : testo);
}
]]>
</Code>
</Task>
</UsingTask>
<!-- ═════════════════════ Verifica ═════════════════════ -->
<Target Name="Verifica">
<Message Importance="High" Text="== Verifica (compilazione + test) ==" />
<!-- In una cartella a parte: l'applicazione puo' essere aperta e tenere
bloccato AutoBidder.exe. -->
<Exec Command="dotnet test &quot;$(TestProj)&quot; --nologo -v q --artifacts-path &quot;$(CartellaProve)&quot;"
WorkingDirectory="$(Radice)"
EnvironmentVariables="$(AmbientePulito)" />
<Message Importance="High" Text=" tutto a posto" />
</Target>
<!-- ═════════════════════ Rigiocata sui dossier ═════════════════════ -->
<Target Name="Backtest">
<PropertyGroup>
<CartellaDossier Condition="'$(CartellaDossier)' == ''">$(LOCALAPPDATA)\AutoBidder\Dati\Registri\Aste</CartellaDossier>
<MaxDossier Condition="'$(MaxDossier)' == ''">0</MaxDossier>
<Riepilogo Condition="'$(Riepilogo)' == ''">$(Radice)\bin\backtest-report.txt</Riepilogo>
</PropertyGroup>
<Error Condition="!Exists('$(CartellaDossier)')"
Text="Cartella dei dossier non trovata: $(CartellaDossier). Indicala con -p:CartellaDossier=..., oppure accendi «Dossier per ogni asta» nelle Impostazioni." />
<Message Importance="High" Text="== Rigiocata sui dossier ==" />
<Message Importance="High" Text=" cartella : $(CartellaDossier)" />
<Exec WorkingDirectory="$(Radice)"
Command="dotnet test &quot;$(TestProj)&quot; --nologo -v q --artifacts-path &quot;$(CartellaProve)&quot; --filter &quot;FullyQualifiedName~RealDossierBacktest&quot; --logger &quot;console;verbosity=detailed&quot;"
EnvironmentVariables="$(AmbientePulito);AUTOBIDDER_BACKTEST_DIR=$(CartellaDossier);AUTOBIDDER_BACKTEST_MAX=$(MaxDossier);AUTOBIDDER_BACKTEST_OUT=$(Riepilogo)" />
<Message Importance="High" Text=" riepilogo in $(Riepilogo)" />
</Target>
<!-- ═════════════════════ Eseguibile ═════════════════════ -->
<Target Name="Pubblica">
<Message Importance="High" Text="== Pubblicazione dell'eseguibile ==" />
<Exec WorkingDirectory="$(Radice)"
EnvironmentVariables="$(AmbientePulito)"
Command="dotnet publish &quot;$(Csproj)&quot; -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 &quot;$(CartellaPubblicazione)&quot;" />
<Error Condition="!Exists('$(CartellaPubblicazione)\AutoBidder.exe')"
Text="Pubblicazione fallita: AutoBidder.exe non trovato." />
</Target>
<!-- ═════════════════════ Pacchetto ═════════════════════ -->
<!--
L'ordine conta: prima si verifica, POI si scrive la versione.
Il contrario sembra piu' naturale — decidi il numero, poi costruisci — ma
lascia il .csproj alzato quando i test falliscono, e il tentativo successivo
riparte da li'. Un giro andato male e il numero e' salito lo stesso, senza
che sia mai esistito un pacchetto con quella versione.
-->
<Target Name="Pacchetto">
<CallTarget Targets="Verifica" Condition="'$(SaltaVerifica)' != 'true'" />
<CallTarget Targets="ImpostaVersione" />
<CallTarget Targets="Pubblica" />
<Message Importance="High" Text="== Creazione dell'installatore ==" />
<!-- CallTarget non restituisce le proprieta' impostate dal target chiamato:
la versione si rilegge dal .csproj, che ImpostaVersione ha appena scritto. -->
<TrovaVersione Csproj="$(Csproj)" SoloLettura="true">
<Output TaskParameter="Attuale" PropertyName="VersioneEffettiva" />
</TrovaVersione>
<TrovaInnoSetup>
<Output TaskParameter="Percorso" PropertyName="Iscc" />
</TrovaInnoSetup>
<MakeDir Directories="$(CartellaPacchetti)" />
<Exec WorkingDirectory="$(MSBuildThisFileDirectory)"
Command="&quot;$(Iscc)&quot; /Qp &quot;/DAppVersion=$(VersioneEffettiva)&quot; &quot;/DSourceExe=$(CartellaPubblicazione)\AutoBidder.exe&quot; &quot;/DOutputDir=$(CartellaPacchetti)&quot; &quot;$(Iss)&quot;" />
<PropertyGroup>
<Setup>$(CartellaPacchetti)\AutoBidder-$(VersioneEffettiva)-setup.exe</Setup>
</PropertyGroup>
<Error Condition="!Exists('$(Setup)')" Text="Installatore non trovato: $(Setup)" />
<Message Importance="High" Text=" " />
<Message Importance="High" Text="Pacchetto pronto:" />
<Message Importance="High" Text=" $(Setup)" />
</Target>
<Target Name="ImpostaVersione">
<TrovaVersione Csproj="$(Csproj)" Richiesta="$(Versione)">
<Output TaskParameter="Attuale" PropertyName="VersionePrecedente" />
<Output TaskParameter="Effettiva" PropertyName="VersioneEffettiva" />
</TrovaVersione>
<ScriviVersione Csproj="$(Csproj)" Versione="$(VersioneEffettiva)"
Condition="'$(VersioneEffettiva)' != '$(VersionePrecedente)'" />
</Target>
<!-- ═════════════════════ Rilascio ═════════════════════ -->
<Target Name="Rilascia" DependsOnTargets="Pacchetto">
<Message Importance="High" Text="== Pubblicazione su Gitea ==" />
<TrovaVersione Csproj="$(Csproj)" SoloLettura="true">
<Output TaskParameter="Attuale" PropertyName="V" />
</TrovaVersione>
<LeggiConfigGitea Percorso="$(MSBuildThisFileDirectory)gitea.json">
<Output TaskParameter="Url" PropertyName="GUrl" />
<Output TaskParameter="Owner" PropertyName="GOwner" />
<Output TaskParameter="Repo" PropertyName="GRepo" />
<Output TaskParameter="Token" PropertyName="GToken" />
</LeggiConfigGitea>
<PropertyGroup>
<Tag>v$(V)</Tag>
<Api>$(GUrl)/api/v1/repos/$(GOwner)/$(GRepo)</Api>
<Tmp>$([System.IO.Path]::GetTempPath())AutoBidder.Rilascio</Tmp>
<CurlCfg>$(Tmp)\curl.cfg</CurlCfg>
<CorpoJson>$(Tmp)\release.json</CorpoJson>
<RispostaJson>$(Tmp)\risposta.json</RispostaJson>
<Setup>$(CartellaPacchetti)\AutoBidder-$(V)-setup.exe</Setup>
</PropertyGroup>
<MakeDir Directories="$(Tmp)" />
<!-- Il token vive qui e solo qui, per il tempo del rilascio. -->
<WriteLinesToFile File="$(CurlCfg)" Overwrite="true"
Lines="header = &quot;Authorization: token $(GToken)&quot;" />
<!-- Il tag e' locale: senza remoto la release lo crea da se', quindi un
fallimento qui non deve fermare il rilascio. -->
<Exec Command="git tag -a $(Tag) -m &quot;AutoBidder $(V)&quot;"
WorkingDirectory="$(Radice)" ContinueOnError="true"
StandardOutputImportance="low" StandardErrorImportance="low" />
<Exec Command="git push --tags" WorkingDirectory="$(Radice)" ContinueOnError="true"
StandardOutputImportance="low" StandardErrorImportance="low" />
<!-- Release gia' presente? -->
<Exec EchoOff="true" ContinueOnError="true" StandardOutputImportance="low"
Command="curl -s -K &quot;$(CurlCfg)&quot; -o &quot;$(RispostaJson)&quot; &quot;$(Api)/releases/tags/$(Tag)&quot;" />
<LeggiIdRelease Risposta="$(RispostaJson)">
<Output TaskParameter="Id" PropertyName="IdEsistente" />
</LeggiIdRelease>
<Error Condition="'$(IdEsistente)' != '' AND '$(Sovrascrivi)' != 'true'"
Text="La release $(Tag) esiste gia'. Alza la versione, oppure rilancia con -p:Sovrascrivi=true." />
<Exec Condition="'$(IdEsistente)' != ''" EchoOff="true" StandardOutputImportance="low"
Command="curl -s -K &quot;$(CurlCfg)&quot; -X DELETE &quot;$(Api)/releases/$(IdEsistente)&quot;" />
<Message Condition="'$(IdEsistente)' != ''" Importance="High"
Text=" release $(Tag) esistente: sostituita" />
<!-- Creazione -->
<PreparaCorpoRelease Destinazione="$(CorpoJson)" Tag="$(Tag)" Versione="$(V)"
Note="$(Note)" Bozza="$(Bozza)" />
<Exec EchoOff="true" StandardOutputImportance="low"
Command="curl -s -K &quot;$(CurlCfg)&quot; -X POST -H &quot;Content-Type: application/json&quot; --data-binary &quot;@$(CorpoJson)&quot; -o &quot;$(RispostaJson)&quot; &quot;$(Api)/releases&quot;" />
<LeggiIdRelease Risposta="$(RispostaJson)">
<Output TaskParameter="Id" PropertyName="IdRelease" />
<Output TaskParameter="Errore" PropertyName="ErroreRelease" />
</LeggiIdRelease>
<Error Condition="'$(IdRelease)' == ''"
Text="Creazione della release non riuscita: $(ErroreRelease)" />
<Message Importance="High" Text=" release creata" />
<!-- Allegati: sia l'installatore sia l'eseguibile nudo. Chi non vuole
installare niente deve continuare a poter scaricare il solo .exe. -->
<ItemGroup>
<Allegato Include="$(Setup)" />
<Allegato Include="$(CartellaPubblicazione)\AutoBidder.exe" />
</ItemGroup>
<Exec Condition="Exists('%(Allegato.FullPath)')" EchoOff="true" StandardOutputImportance="low"
Command="curl -s -K &quot;$(CurlCfg)&quot; -X POST -F &quot;attachment=@%(Allegato.FullPath)&quot; &quot;$(Api)/releases/$(IdRelease)/assets?name=%(Allegato.Filename)%(Allegato.Extension)&quot;" />
<Message Importance="High" Text=" caricato %(Allegato.Filename)%(Allegato.Extension)"
Condition="Exists('%(Allegato.FullPath)')" />
<!-- Il token non deve sopravvivere al rilascio. -->
<Delete Files="$(CurlCfg);$(CorpoJson);$(RispostaJson)" ContinueOnError="true" />
<Message Importance="High" Text=" " />
<Message Importance="High" Text="Rilascio completato:" />
<Message Importance="High" Text=" $(GUrl)/$(GOwner)/$(GRepo)/releases/tag/$(Tag)" />
</Target>
</Project>
+10
View File
@@ -0,0 +1,10 @@
{
"_commento": "Copia questo file in gitea.json e metti il token. gitea.json e' escluso dal controllo di versione perche' contiene una credenziale. In alternativa usa le variabili d'ambiente GITEA_URL, GITEA_OWNER, GITEA_REPO, GITEA_TOKEN, che hanno la precedenza su questo file.",
"url": "http://192.168.30.23:3000",
"owner": "Alby96",
"repo": "Mimante",
"_token": "Gitea > Impostazioni > Applicazioni > Genera nuovo token, permesso 'repository: read and write'.",
"token": "INSERISCI_QUI_IL_TOKEN"
}
+83
View File
@@ -0,0 +1,83 @@
# Rigioca i dossier delle aste concluse e stampa quanto costerebbe ciascun anticipo.
#
# powershell -ExecutionPolicy Bypass -File .\backtest.ps1
# powershell -ExecutionPolicy Bypass -File .\backtest.ps1 -Max 200
#
# Il motore punta solo nei cicli che arrivano fino all'anticipo senza che nessun altro
# abbia puntato: la rigiocata conta quei cicli, quindi dice quante puntate si
# spenderebbero e quante ne bloccherebbero le strategie.
#
# NON dice se avresti vinto: una nostra puntata rimette in gioco l'asta, e la reazione
# degli avversari non e' registrata da nessuna parte.
[CmdletBinding()]
param(
# Cartella dei dossier. Vuota = quella in uso dall'applicazione.
[string] $Cartella = "",
# Quanti dossier al massimo (0 = tutti). I file sono grossi: per un giro veloce, 200.
[int] $Max = 0,
# Dove scrivere il riepilogo.
[string] $Report = ""
)
$ErrorActionPreference = 'Stop'
Set-Location $PSScriptRoot
if (-not $Cartella) {
# Stessa struttura di Utilities/AppPaths: Dati\Registri\Aste sotto %LocalAppData%.
$Cartella = Join-Path $env:LOCALAPPDATA "AutoBidder\Dati\Registri\Aste"
}
if (-not (Test-Path $Cartella)) {
Write-Host "Cartella dei dossier non trovata: $Cartella" -ForegroundColor Red
Write-Host "Indicala con -Cartella, oppure accendi 'Dossier per ogni asta' nelle Impostazioni." -ForegroundColor Yellow
exit 1
}
$conteggio = (Get-ChildItem -Path $Cartella -Filter *.jsonl -File | Measure-Object).Count
if ($conteggio -eq 0) {
Write-Host "Nessun dossier (*.jsonl) in $Cartella" -ForegroundColor Red
exit 1
}
if (-not $Report) {
$Report = Join-Path $PSScriptRoot "backtest-report.txt"
}
Write-Host "== Rigiocata sui dossier ==" -ForegroundColor Cyan
Write-Host " cartella : $Cartella"
Write-Host " dossier : $conteggio$(if ($Max -gt 0) { " (ne verranno letti $Max)" })"
Write-Host " riepilogo: $Report"
Write-Host ""
$env:AUTOBIDDER_BACKTEST_DIR = $Cartella
$env:AUTOBIDDER_BACKTEST_MAX = "$Max"
$env:AUTOBIDDER_BACKTEST_OUT = $Report
# L'applicazione potrebbe essere aperta e tenere bloccato AutoBidder.exe: si compila
# in una cartella a parte, come fa la verifica.
$out = Join-Path ([System.IO.Path]::GetTempPath()) "AutoBidder.Backtest"
& dotnet test Tests\AutoBidder.Tests.csproj --nologo -v q -o $out `
--filter "FullyQualifiedName~RealDossierBacktest" `
--logger "console;verbosity=detailed" 2>&1 |
Where-Object { $_ -notmatch '^\s*$' } |
ForEach-Object { Write-Host $_ }
$esito = $LASTEXITCODE
Write-Host ""
if (Test-Path $Report) {
Write-Host "== Riepilogo ==" -ForegroundColor Cyan
Get-Content $Report | ForEach-Object { Write-Host $_ }
}
if ($esito -ne 0) {
Write-Host "`nLa rigiocata ha trovato dei problemi (vedi sopra)." -ForegroundColor Red
exit 1
}
Write-Host "`nRigiocata completata." -ForegroundColor Green
exit 0
+36
View File
@@ -0,0 +1,36 @@
<#
pubblica.ps1 - Crea AutoBidder come UNICO file .exe self-contained per Windows x64.
Uso:
powershell -ExecutionPolicy Bypass -File .\pubblica.ps1
Risultato:
bin\publish\win-x64\AutoBidder.exe (doppio click per avviare, nessuna installazione)
#>
$ErrorActionPreference = "Stop"
$here = Split-Path -Parent $MyInvocation.MyCommand.Definition
Set-Location $here
Write-Host "==> Pubblicazione AutoBidder (self-contained, single-file, win-x64)..." -ForegroundColor Cyan
dotnet publish "AutoBidder.csproj" -c Release -r win-x64 `
--self-contained true `
-p:PublishSingleFile=true `
-p:IncludeNativeLibrariesForSelfExtract=true `
-p:EnableCompressionInSingleFile=true `
-p:PublishReadyToRun=true `
-p:PublishTrimmed=false `
-o "bin\publish\win-x64"
$exe = Join-Path $here "bin\publish\win-x64\AutoBidder.exe"
if (Test-Path $exe) {
$sizeMB = [math]::Round((Get-Item $exe).Length / 1MB, 1)
Write-Host ""
Write-Host "==> FATTO. Eseguibile pronto:" -ForegroundColor Green
Write-Host " $exe ($sizeMB MB)" -ForegroundColor Green
Write-Host " Fai doppio click per avviare. Nessun software da installare." -ForegroundColor Green
} else {
Write-Host "==> ERRORE: eseguibile non trovato." -ForegroundColor Red
exit 1
}
+284
View File
@@ -0,0 +1,284 @@
<#
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
+40
View File
@@ -0,0 +1,40 @@
# Verifica rapida: compila tutto e lancia i test.
# Da eseguire dopo ogni modifica, prima di avviare l'applicazione.
#
# powershell -ExecutionPolicy Bypass -File .\verifica.ps1
#
# Restituisce 0 se tutto è a posto, 1 altrimenti: utilizzabile in un hook.
$ErrorActionPreference = 'Continue'
Set-Location $PSScriptRoot
Write-Host "== Compilazione ==" -ForegroundColor Cyan
$build = & dotnet build AutoBidder.sln --nologo -v q 2>&1
$buildOk = $LASTEXITCODE -eq 0
$problemi = $build | Select-String -Pattern 'error|warning CS'
if ($problemi) { $problemi | ForEach-Object { Write-Host $_.Line -ForegroundColor Yellow } }
if (-not $buildOk) {
Write-Host "COMPILAZIONE FALLITA" -ForegroundColor Red
exit 1
}
Write-Host "compilazione ok, 0 errori" -ForegroundColor Green
Write-Host "`n== Test ==" -ForegroundColor Cyan
$test = & dotnet test Tests\AutoBidder.Tests.csproj --nologo -v q --no-build 2>&1
$testOk = $LASTEXITCODE -eq 0
$riga = $test | Select-String -Pattern 'Superato!|Non superato|Failed!|Passed!'
if ($riga) { $riga | ForEach-Object { Write-Host $_.Line } }
if (-not $testOk) {
# In caso di rosso servono i dettagli, non il riassunto.
$test | Select-String -Pattern 'Assert|Error Message|Stack Trace|\[FAIL\]' |
Select-Object -First 30 | ForEach-Object { Write-Host $_.Line -ForegroundColor Red }
Write-Host "`nTEST FALLITI" -ForegroundColor Red
exit 1
}
Write-Host "`nTutto a posto." -ForegroundColor Green
exit 0