6 Commits
Author SHA1 Message Date
Alby96 9a632b8d62 Aggiungi un elenco di cose da fare nel file Modifiche.txt 2026-08-04 22:51:35 +02:00
Alby96andClaude Opus 5 cb0e838964 Rifiuta il rilascio se il tag non e' arrivato sul remoto
Il push dei tag non porta con se' i commit: a ramo indietro il tag punta a un
oggetto che il remoto non conosce e viene rifiutato. La creazione della release
pero' non falliva — Gitea, non trovando il tag, lo crea da se' sulla testa del
ramo predefinito. Sarebbe stata pubblicata una release che dichiara un commit e
allega il codice di un altro, senza che niente lo segnalasse.

Adesso il tag viene riletto dal remoto dopo il push e il rilascio si ferma se
non c'e', dicendo di spingere il ramo. I commit restano da spingere a mano:
quando farlo lo decide chi lavora, non la catena di rilascio.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:45:48 +02:00
Alby96andClaude Opus 5 8954f9aaba Correggi i commenti che indicavano ancora il .csproj come fonte della versione
AppInfo e il .csproj dicevano entrambi di alzare <Version> per pubblicare. Da
quando la versione viene dal tag non e' piu' vero, ed e' il tipo di commento che
fa perdere tempo fra sei mesi: adesso dicono a cosa serve davvero quel numero
(le compilazioni di sviluppo) e dove sta la fonte dei rilasci.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:41:55 +02:00
Alby96andClaude Opus 5 94843311b4 Il tag git diventa la fonte unica della versione
Il .csproj non viene piu' riscritto a ogni rilascio: i quattro numeri di
versione arrivano a dotnet publish come proprieta' da riga di comando, ricavati
dal tag su HEAD. Tag, eseguibile, installatore e release portano lo stesso
numero per costruzione invece che per disciplina, e un rilascio non lascia piu'
una modifica al progetto da committare dopo il fatto.

Se HEAD non ha un tag la catena lo crea da se' — numero chiesto o minor
successiva alla piu' alta esistente — ma lo fa in fondo, a installatore
prodotto: un giro fallito non lascia dietro il tag di una versione mai
costruita. Rifiuta di taggare un albero con modifiche non committate, perche'
sarebbe un tag che non indica niente di ricostruibile.

La configurazione di Gitea viene letta prima di compilare: senza token ci si
ferma in due secondi invece che dopo tre minuti di build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:38:45 +02:00
Alby96andClaude Opus 5 52e5f68da0 Completa .gitignore con le regole del vecchio repository
.idea/, Thumbs.db e Desktop.ini erano coperte nella cartella BidooBidder ma
non qui. Aggiunte prima di cancellarla, cosi' non si perde nulla.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:17:28 +02:00
Alby96andClaude Opus 5 99b3030180 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>
2026-08-04 22:15:53 +02:00
13 changed files with 1323 additions and 13 deletions
+18
View File
@@ -414,3 +414,21 @@ FodyWeavers.xsd
# Built Visual Studio Code Extensions
*.vsix
# ---> AutoBidder / Mimante
# Configurazione di rilascio: contiene un token di Gitea
gitea.json
# Riepilogo della rigiocata sui dossier (target Backtest)
*backtest-report.txt
# Pacchetti prodotti da build/Release.proj
bin/installer/
# Rider / JetBrains
.idea/
# Windows
Thumbs.db
Desktop.ini
+3 -3
View File
@@ -68,7 +68,7 @@
},
{
"label": "crea installatore",
"detail": "Chiede la versione, verifica, pubblica ed esegue Inno Setup: bin/installer/AutoBidder-<versione>-setup.exe. Non tocca Gitea.",
"detail": "Verifica, pubblica ed esegue Inno Setup: bin/installer/AutoBidder-<versione>-setup.exe. Crea il tag a pacchetto pronto. Non tocca Gitea.",
"type": "process",
"command": "dotnet",
"args": [
@@ -101,7 +101,7 @@
},
{
"label": "rilascia su Gitea",
"detail": "Chiede versione e note, poi: verifica, pubblica, installatore, tag e release su Gitea con i file allegati. Richiede build/gitea.json.",
"detail": "Verifica, pubblica, installatore, tag e release su Gitea con i file allegati. La versione viene dal tag su HEAD. Richiede build/gitea.json.",
"type": "process",
"command": "dotnet",
"args": [
@@ -121,7 +121,7 @@
{
"id": "versione",
"type": "promptString",
"description": "Versione (vuoto = incrementa la minor, es. 4.11.0 → 4.12.0)",
"description": "Versione — lascia vuoto se hai già taggato (git tag v4.14.0), o per la minor successiva",
"default": ""
},
{
+7 -6
View File
@@ -15,12 +15,13 @@
<!-- App desktop leggera per Bidoo. Nessun software aggiuntivo:
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>
<!-- Versione delle sole compilazioni di sviluppo. NON va alzata per rilasciare:
nei pacchetti pubblicati questi quattro numeri vengono dal tag git, passati
da build/Release.proj a dotnet publish. Vedi Utilities/AppInfo. -->
<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>
+2
View File
@@ -0,0 +1,2 @@
Cose da fare:
- Nulla per ora
+8 -4
View File
@@ -5,10 +5,14 @@ namespace AutoBidder.Utilities
/// <summary>
/// Informazioni sull'applicazione, in un solo posto.
///
/// <para>La versione si legge dall'assembly (dalla proprietà <c>Version</c> del
/// <c>.csproj</c>): così il titolo della finestra e il log d'avvio restano allineati
/// da soli, e per pubblicare una nuova versione basta cambiare un unico numero nel
/// progetto invece di rincorrerlo in più stringhe sparse.</para>
/// <para>La versione si legge dall'assembly, così il titolo della finestra e il log
/// d'avvio restano allineati da soli invece di rincorrere il numero in più stringhe
/// sparse.</para>
///
/// <para>Nei pacchetti pubblicati quel numero viene dal tag git, scritto
/// nell'assembly da <c>build/Release.proj</c> al momento della pubblicazione. Il
/// <c>&lt;Version&gt;</c> del <c>.csproj</c> vale solo per le compilazioni di
/// sviluppo: alzarlo non pubblica niente, e non serve prima di un rilascio.</para>
/// </summary>
public static class AppInfo
{
+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`
+622
View File
@@ -0,0 +1,622 @@
<!--
═══════════════════════════════════════════════════════════════════════════
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.
── Da dove viene la versione ────────────────────────────────────────────
Dal tag git, e da nient'altro. Il .csproj non viene mai riscritto: il numero
arriva a `dotnet publish` come proprietà da riga di comando, quindi tag,
eseguibile, installatore e release portano lo stesso numero per costruzione,
non per disciplina.
Il modo previsto è taggare e poi rilasciare:
git tag v4.14.0
dotnet msbuild build/Release.proj -t:Rilascia
Se HEAD non ha un tag di versione la catena lo crea da sé — con il numero
passato in `-p:Versione=`, oppure la minor successiva all'ultimo tag — e lo
fa in fondo, quando l'installatore esiste davvero. Un giro andato male non
lascia dietro un tag per una versione che non è mai stata costruita.
Il numero nel .csproj resta quello delle compilazioni di sviluppo, e non
c'entra più con i rilasci.
-->
<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>
<!-- Serve solo quando HEAD non è ancora taggato: è il numero del tag da
creare. Vuoto = la minor successiva all'ultimo tag. Vedi VersioneDaTag. -->
<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>
<!-- Un rilascio da albero sporco produce un tag che non corrisponde a
nessuno stato ricostruibile: è esattamente la garanzia che il tag come
fonte unica dovrebbe dare. Si può forzare, ma va detto. -->
<ConsentiModifiche Condition="'$(ConsentiModifiche)' == ''">false</ConsentiModifiche>
</PropertyGroup>
<!-- ═════════════════════ Attività in linea ═════════════════════ -->
<!--
Decide quale versione si sta costruendo, senza toccare niente.
Il tag su HEAD, se c'è, comanda: è la fonte. Se non c'è se ne propone uno —
il numero chiesto, o la minor successiva al tag più alto esistente — che
verrà creato solo a pacchetto pronto, da CreaTag.
-->
<UsingTask TaskName="VersioneDaTag" TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<TagsHead ParameterType="System.String" />
<TagsTutti ParameterType="System.String" />
<Richiesta ParameterType="System.String" />
<Csproj ParameterType="System.String" Required="true" />
<Versione ParameterType="System.String" Output="true" />
<Tag ParameterType="System.String" Output="true" />
<DaCreare ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Collections.Generic" />
<Using Namespace="System.Text.RegularExpressions" />
<Code Type="Fragment" Language="cs">
<![CDATA[
// ConsoleToMSBuild unisce le righe con il punto e virgola; git le separa
// con a-capo. Si accettano entrambi e non si fanno domande.
Func<string, string[]> spezza = s =>
(s ?? "").Split(new[] { ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
var forma = new Regex(@"^v(\d+)\.(\d+)\.(\d+)$");
var suHead = new List<string>();
foreach (var t in spezza(TagsHead))
if (forma.IsMatch(t.Trim())) suHead.Add(t.Trim());
if (suHead.Count > 1)
{
Log.LogError(
"HEAD ha piu' di un tag di versione (" + string.Join(", ", suHead) + ").\n" +
"Non si puo' sapere quale sia il rilascio: tienine uno solo con git tag -d <tag>");
return false;
}
var chiesta = (Richiesta ?? "").Trim();
if (chiesta.Length > 0 && !Regex.IsMatch(chiesta, @"^\d+\.\d+\.\d+$"))
{
// Un refuso qui produrrebbe un tag e un pacchetto sbagliati.
Log.LogError("Versione '" + chiesta + "' non valida: serve la forma X.Y.Z");
return false;
}
if (suHead.Count == 1)
{
var dalTag = suHead[0].Substring(1);
if (chiesta.Length > 0 && chiesta != dalTag)
{
Log.LogError(
"HEAD e' gia' taggato " + suHead[0] + ", ma e' stata chiesta la versione " + chiesta + ".\n" +
"Il tag e' la fonte: o rilasci " + dalTag + " lasciando vuota la versione,\n" +
"oppure togli il tag con git tag -d " + suHead[0] + " e rilancia.");
return false;
}
Versione = dalTag;
Tag = suHead[0];
DaCreare = "false";
Log.LogMessage(MessageImportance.High, " versione " + Versione + " — dal tag " + Tag + " su HEAD");
return true;
}
if (chiesta.Length > 0)
{
Versione = chiesta;
}
else
{
// La minor successiva al tag piu' alto: cosi' il numero proposto e'
// sempre libero, anche se il tag piu' alto sta su un altro ramo.
int maggiore = -1, minore = -1;
foreach (var t in spezza(TagsTutti))
{
var m = forma.Match(t.Trim());
if (!m.Success) continue;
int ma = int.Parse(m.Groups[1].Value), mi = int.Parse(m.Groups[2].Value);
if (ma > maggiore || (ma == maggiore && mi > minore)) { maggiore = ma; minore = mi; }
}
if (maggiore < 0)
{
// Primo rilascio: non c'e' nessun tag da cui ripartire, e l'unico
// numero che esiste e' quello delle compilazioni di sviluppo.
var testo = File.Exists(Csproj) ? File.ReadAllText(Csproj) : "";
var m = Regex.Match(testo, @"<Version>(\d+)\.(\d+)\.\d+</Version>");
if (!m.Success)
{
Log.LogError(
"Nessun tag di versione nel repository e <Version> illeggibile in " + Csproj + ".\n" +
"Indica la versione con -p:Versione=X.Y.Z");
return false;
}
maggiore = int.Parse(m.Groups[1].Value);
minore = int.Parse(m.Groups[2].Value);
}
Versione = maggiore + "." + (minore + 1) + ".0";
}
Tag = "v" + Versione;
DaCreare = "true";
Log.LogMessage(MessageImportance.High, " versione " + Versione + " — il tag " + Tag + " sara' creato a pacchetto pronto");
]]>
</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" +
"Non e' stato costruito niente: si controlla prima di compilare, non dopo.\n" +
"Per il solo installatore, senza Gitea, usa il target Pacchetto.");
// 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" Condition="'$(SaltaVerifica)' != 'true'">
<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 ═════════════════════ -->
<!--
I quattro numeri di versione arrivano da qui, non dal .csproj: le proprietà
da riga di comando sono globali e vincono su quelle scritte nel progetto.
Vanno passati tutti e quattro — il .csproj li assegna esplicitamente, quindi
il solo -p:Version non ne deriverebbe nessuno. Utilities/AppInfo li legge a
runtime, e vederne divergere uno significa un titolo finestra che mente.
-->
<Target Name="Pubblica" DependsOnTargets="DeterminaVersione">
<Message Importance="High" Text="== Pubblicazione dell'eseguibile ($(V)) ==" />
<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 -p:Version=$(V) -p:AssemblyVersion=$(V).0 -p:FileVersion=$(V).0 -p:InformationalVersion=$(V) -o &quot;$(CartellaPubblicazione)&quot;" />
<Error Condition="!Exists('$(CartellaPubblicazione)\AutoBidder.exe')"
Text="Pubblicazione fallita: AutoBidder.exe non trovato." />
</Target>
<!-- ═════════════════════ Pacchetto ═════════════════════ -->
<!--
L'ordine conta: si verifica, si costruisce, e il tag si crea per ultimo.
Il contrario sembra piu' naturale — decidi il numero, poi costruisci — ma
lascia dietro un tag quando qualcosa va storto, 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. Taggando in fondo,
ogni tag corrisponde a un installatore che esiste davvero.
-->
<Target Name="Pacchetto" DependsOnTargets="Verifica;DeterminaVersione;Pubblica">
<Message Importance="High" Text="== Creazione dell'installatore ==" />
<TrovaInnoSetup>
<Output TaskParameter="Percorso" PropertyName="Iscc" />
</TrovaInnoSetup>
<MakeDir Directories="$(CartellaPacchetti)" />
<Exec WorkingDirectory="$(MSBuildThisFileDirectory)"
Command="&quot;$(Iscc)&quot; /Qp &quot;/DAppVersion=$(V)&quot; &quot;/DSourceExe=$(CartellaPubblicazione)\AutoBidder.exe&quot; &quot;/DOutputDir=$(CartellaPacchetti)&quot; &quot;$(Iss)&quot;" />
<PropertyGroup>
<Setup>$(CartellaPacchetti)\AutoBidder-$(V)-setup.exe</Setup>
</PropertyGroup>
<Error Condition="!Exists('$(Setup)')" Text="Installatore non trovato: $(Setup)" />
<!-- Adesso: il pacchetto c'e', il tag puo' esistere. -->
<CallTarget Targets="CreaTag" />
<Message Importance="High" Text=" " />
<Message Importance="High" Text="Pacchetto pronto:" />
<Message Importance="High" Text=" $(Setup)" />
</Target>
<!-- ═════════════════════ Versione e tag ═════════════════════ -->
<Target Name="DeterminaVersione">
<!-- Nessuno dei due comandi fallisce mai: senza tag l'uscita e' vuota, e
basta. `git describe` invece esce in errore, e qui non serve. -->
<Exec Command="git tag --points-at HEAD" WorkingDirectory="$(Radice)"
ConsoleToMSBuild="true" StandardOutputImportance="low">
<Output TaskParameter="ConsoleOutput" PropertyName="TagSuHead" />
</Exec>
<Exec Command="git tag --list v*" WorkingDirectory="$(Radice)"
ConsoleToMSBuild="true" StandardOutputImportance="low">
<Output TaskParameter="ConsoleOutput" PropertyName="TagEsistenti" />
</Exec>
<VersioneDaTag TagsHead="$(TagSuHead)" TagsTutti="$(TagEsistenti)"
Richiesta="$(Versione)" Csproj="$(Csproj)">
<Output TaskParameter="Versione" PropertyName="V" />
<Output TaskParameter="Tag" PropertyName="Tag" />
<Output TaskParameter="DaCreare" PropertyName="TagDaCreare" />
</VersioneDaTag>
</Target>
<Target Name="CreaTag" Condition="'$(TagDaCreare)' == 'true'">
<!-- Solo le modifiche ai file gia' tracciati: un appunto non versionato
accanto al progetto non cambia cosa viene compilato. -->
<Exec Command="git status --porcelain --untracked-files=no" WorkingDirectory="$(Radice)"
ConsoleToMSBuild="true" StandardOutputImportance="low">
<Output TaskParameter="ConsoleOutput" PropertyName="AlberoSporco" />
</Exec>
<Error Condition="'$(AlberoSporco)' != '' AND '$(ConsentiModifiche)' != 'true'"
Text="Ci sono modifiche non committate: il tag $(Tag) indicherebbe uno stato che non e' ricostruibile.%0ACommitta prima di rilasciare, oppure rilancia con -p:ConsentiModifiche=true se sai cosa stai facendo.%0A%0A$(AlberoSporco)" />
<Exec Command="git tag -a $(Tag) -m &quot;AutoBidder $(V)&quot;" WorkingDirectory="$(Radice)"
StandardOutputImportance="low" StandardErrorImportance="low" />
<Message Importance="High" Text=" tag $(Tag) creato" />
</Target>
<!-- ═════════════════════ Rilascio ═════════════════════ -->
<!-- Prima di tutto il resto: un token mancante non deve costare due minuti di
compilazione per poi fermarsi all'ultimo passo. -->
<Target Name="ConfigGitea">
<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>
</Target>
<Target Name="Rilascia" DependsOnTargets="ConfigGitea;Pacchetto">
<Message Importance="High" Text="== Pubblicazione su Gitea ==" />
<PropertyGroup>
<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 esiste gia' in locale: l'ha creato Pacchetto, o c'era prima. Qui va
spinto, e va verificato che ci sia arrivato.
Non e' una formalita'. Il push qui sotto spinge i tag, non i commit: se il
ramo e' indietro, il tag punta a un oggetto che il remoto non conosce e il
push viene rifiutato. A quel punto la creazione della release non
fallisce — Gitea, non trovando il tag, lo crea da se' sulla testa del ramo
predefinito. Verrebbe pubblicata una release che dichiara di essere il
commit X mentre il codice allegato e' il commit Y, e nessuno se ne
accorgerebbe. Meglio fermarsi e dire cosa manca.
I commit non si spingono da qui: quando spingere il ramo lo decide chi
lavora, non la catena di rilascio.
-->
<Exec Command="git push --tags" WorkingDirectory="$(Radice)" ContinueOnError="true"
StandardOutputImportance="low" StandardErrorImportance="low" />
<Exec Command="git ls-remote --tags origin refs/tags/$(Tag)" WorkingDirectory="$(Radice)"
ConsoleToMSBuild="true" ContinueOnError="true" StandardOutputImportance="low">
<Output TaskParameter="ConsoleOutput" PropertyName="TagSulRemoto" />
</Exec>
<Error Condition="'$(TagSulRemoto)' == ''"
Text="Il tag $(Tag) non e' arrivato sul remoto, quasi sempre perche' il ramo e' indietro.%0AGitea creerebbe la release sulla testa del ramo predefinito: il codice allegato non corrisponderebbe al commit dichiarato.%0A%0ASpingi il ramo e rilancia: git push" />
<Message Importance="High" Text=" tag $(Tag) sul remoto" />
<!-- 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