diff --git a/.gitignore b/.gitignore
index 7e2e97c..239e675 100644
--- a/.gitignore
+++ b/.gitignore
@@ -414,3 +414,14 @@ 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/
diff --git a/Mimante/AutoBidder.csproj b/Mimante/AutoBidder.csproj
index d12a269..b5f8e31 100644
--- a/Mimante/AutoBidder.csproj
+++ b/Mimante/AutoBidder.csproj
@@ -17,10 +17,10 @@
WebView2 runtime è preinstallato su Windows 11. -->
- 4.12.0
- 4.12.0.0
- 4.12.0.0
- 4.12.0
+ 4.13.0
+ 4.13.0.0
+ 4.13.0.0
+ 4.13.0
win-x64
diff --git a/Mimante/build/AutoBidder.iss b/Mimante/build/AutoBidder.iss
new file mode 100644
index 0000000..e780581
--- /dev/null
+++ b/Mimante/build/AutoBidder.iss
@@ -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;
diff --git a/Mimante/build/README.md b/Mimante/build/README.md
new file mode 100644
index 0000000..e1f34fc
--- /dev/null
+++ b/Mimante/build/README.md
@@ -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`
diff --git a/Mimante/build/Release.proj b/Mimante/build/Release.proj
new file mode 100644
index 0000000..2c8fcec
--- /dev/null
+++ b/Mimante/build/Release.proj
@@ -0,0 +1,513 @@
+
+
+
+
+ $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..'))
+ $(Radice)\AutoBidder.csproj
+ $(Radice)\Tests\AutoBidder.Tests.csproj
+ $(MSBuildThisFileDirectory)AutoBidder.iss
+
+ $(Radice)\bin\publish\win-x64
+ $(Radice)\bin\installer
+
+
+ $([System.IO.Path]::GetTempPath())AutoBidder.Verifica
+
+
+
+
+
+
+
+
+ MSBUILD_EXE_PATH=;MSBuildLoadMicrosoftTargetsReadOnly=
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ([^<]+)");
+ if (!m.Success) { Log.LogError(" 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);
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [^<]+", "" + Versione + "");
+ testo = Regex.Replace(testo, @"[^<]+", "" + Versione + ".0");
+ testo = Regex.Replace(testo, @"[^<]+", "" + Versione + ".0");
+ testo = Regex.Replace(testo, @"[^<]+", "" + Versione + "");
+
+ File.WriteAllText(Csproj, testo);
+ Log.LogMessage(MessageImportance.High, " .csproj aggiornato a " + Versione);
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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;
+ }
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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}");
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 200 ? testo.Substring(0, 200) : testo);
+ }
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(LOCALAPPDATA)\AutoBidder\Dati\Registri\Aste
+ 0
+ $(Radice)\bin\backtest-report.txt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(CartellaPacchetti)\AutoBidder-$(VersioneEffettiva)-setup.exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v$(V)
+ $(GUrl)/api/v1/repos/$(GOwner)/$(GRepo)
+ $([System.IO.Path]::GetTempPath())AutoBidder.Rilascio
+ $(Tmp)\curl.cfg
+ $(Tmp)\release.json
+ $(Tmp)\risposta.json
+ $(CartellaPacchetti)\AutoBidder-$(V)-setup.exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Mimante/build/gitea.example.json b/Mimante/build/gitea.example.json
new file mode 100644
index 0000000..a6427e2
--- /dev/null
+++ b/Mimante/build/gitea.example.json
@@ -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"
+}
diff --git a/Mimante/build/sostituiti/backtest.ps1 b/Mimante/build/sostituiti/backtest.ps1
new file mode 100644
index 0000000..c8f3c8c
--- /dev/null
+++ b/Mimante/build/sostituiti/backtest.ps1
@@ -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
diff --git a/Mimante/build/sostituiti/pubblica.ps1 b/Mimante/build/sostituiti/pubblica.ps1
new file mode 100644
index 0000000..cb755b2
--- /dev/null
+++ b/Mimante/build/sostituiti/pubblica.ps1
@@ -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
+}
diff --git a/Mimante/build/sostituiti/rilascia.ps1 b/Mimante/build/sostituiti/rilascia.ps1
new file mode 100644
index 0000000..1096e4e
--- /dev/null
+++ b/Mimante/build/sostituiti/rilascia.ps1
@@ -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 ()."
+ 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 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
diff --git a/Mimante/build/sostituiti/verifica.ps1 b/Mimante/build/sostituiti/verifica.ps1
new file mode 100644
index 0000000..541e656
--- /dev/null
+++ b/Mimante/build/sostituiti/verifica.ps1
@@ -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