feat(release): Veroeffentlichungsvorlage fuer fremde Projekte
Die Anleitung benutzte pack-and-deploy, als laege es im PATH - beziehbar war es nirgends. Ein Projekt, das den UpdateService einbindet, konnte also nicht veroeffentlichen, ohne dieses Repository auszuchecken und selbst zu uebersetzen. Das Werkzeug existierte, nur kam niemand daran. - build_installer.ps1 baut pack-and-deploy fuer dieselben Laufzeitkennungen mit und fuehrt es in installer.json unter "tools". Damit steht es neben dem Agenten unter /installer/ bereit. - Neue Vorlage unter public/docs/release-template/: release.ps1, release.sh und release.config.example.json. Kopieren, Konfiguration ausfuellen, fertig - die Skripte selbst bleiben unveraendert und lassen sich bei einer neuen Fassung einfach ersetzen. - Sie orchestrieren nur: je Laufzeitkennung einmal dotnet publish, dann pack-and-deploy. Pruefsummen, Dateimanifest, latest.json und die Anmeldung bleiben im Werkzeug - ein zweiter Ort fuer dieselbe Logik waere ein zweiter Ort fuer dieselben Fehler. - Das Werkzeug wird beim ersten Lauf selbst geholt, gegen die .sha256 geprueft und unter .dc-tools/ abgelegt. Die Vorlage ist damit wirklich eine Datei. - setup.json wird ins Publish-Verzeichnis kopiert, sonst faende der Installer sie nicht. - Rueckgabewert 1 (Konfigurations- oder Versionsfehler) bricht sofort ab; die weiteren Plattformen wuerden genauso scheitern. Bei 2 laeuft es weiter und meldet am Ende, welche betroffen sind. Anleitung: public/docs/release.md, oeffentlich unter /docs/release.md - dort, wo auch das Bugtracker-Handbuch liegt. Das Entwickler-docs/ wird nicht ausgeliefert; ein erster Anlauf legte die Vorlage dort ab und war deshalb nicht abrufbar. Beim Erproben in einem leeren Projekt aufgefallen und behoben: - Die Vorlage verlangte jq. Das ist auf den wenigsten Systemen vorinstalliert; sie kommt jetzt auch mit Python aus. - Windows legt unter WindowsApps einen python3-Platzhalter ab, der gefunden wird, beim Aufruf aber nur auf den Store verweist. Die Erkennung erprobt den Interpreter deshalb, statt nur seine Existenz zu pruefen. - Der Ternary-Operator in release.ps1 gibt es erst ab PowerShell 7; die Vorlage laeuft jetzt auch mit dem mitgelieferten 5.1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
56b1d2631a
commit
e01a608c08
@@ -0,0 +1,233 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Veroeffentlicht dieses Projekt im Deploymentcenter.
|
||||
|
||||
.DESCRIPTION
|
||||
Vorlage zum Kopieren nach scripts/release.ps1 des eigenen Projekts.
|
||||
Anzupassen ist nur der Kopf von release.config.json - dieses Skript
|
||||
selbst bleibt unveraendert.
|
||||
|
||||
Der Ablauf je Zielplattform:
|
||||
dotnet publish -r <rid> -> pack-and-deploy --platform <rid>
|
||||
|
||||
pack-and-deploy uebernimmt dabei Pruefsummen, Dateimanifest, das
|
||||
Fortschreiben der latest.json und die Anmeldung beim Deploymentcenter.
|
||||
Das hier nachzubauen waere ein zweiter Ort fuer dieselben Fehler; das
|
||||
Skript orchestriert nur.
|
||||
|
||||
Fehlt das Werkzeug, wird es geholt und die Pruefsumme geprueft.
|
||||
|
||||
.EXAMPLE
|
||||
.\scripts\release.ps1 -Version 1.4.3 -Changelog "Behebt den Login-Fehler."
|
||||
|
||||
.EXAMPLE
|
||||
.\scripts\release.ps1 -Version 1.5.0 -Channel beta -WhatIf
|
||||
#>
|
||||
|
||||
[CmdletBinding(SupportsShouldProcess = $true)]
|
||||
param(
|
||||
# Ohne Angabe wird die Version aus Directory.Build.props bzw. der csproj gelesen.
|
||||
[string] $Version,
|
||||
|
||||
[ValidateSet('prod', 'beta', 'dev')]
|
||||
[string] $Channel = 'prod',
|
||||
|
||||
[string] $Changelog,
|
||||
|
||||
# Als kritisches Update kennzeichnen (Rollout priorisieren).
|
||||
[switch] $Critical,
|
||||
|
||||
[string] $ConfigFile = (Join-Path $PSScriptRoot 'release.config.json')
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# ---------------------------------------------------------------- Konfiguration
|
||||
if (-not (Test-Path $ConfigFile)) {
|
||||
throw "Konfiguration fehlt: $ConfigFile`nVorlage kopieren: release.config.example.json -> release.config.json"
|
||||
}
|
||||
|
||||
$config = Get-Content $ConfigFile -Raw | ConvertFrom-Json
|
||||
|
||||
foreach ($required in @('project', 'csproj', 'runtimes')) {
|
||||
if (-not $config.$required) {
|
||||
throw "In $ConfigFile fehlt der Eintrag '$required'."
|
||||
}
|
||||
}
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..')
|
||||
$csprojRel = $config.csproj
|
||||
$csproj = Join-Path $repoRoot $csprojRel
|
||||
|
||||
if (-not (Test-Path $csproj)) {
|
||||
throw "Projektdatei nicht gefunden: $csproj"
|
||||
}
|
||||
|
||||
$baseUrl = if ($config.baseUrl) { $config.baseUrl.TrimEnd('/') } else { 'https://dc.mhdf.de' }
|
||||
$toolDir = Join-Path $repoRoot '.dc-tools'
|
||||
|
||||
# ---------------------------------------------------------------------- Version
|
||||
function Get-ProjectVersion {
|
||||
# Directory.Build.props zuerst: Steht <Version> nur in einem von mehreren
|
||||
# Projekten, laufen die Angaben frueher oder spaeter auseinander - und
|
||||
# pack-and-deploy bricht dann zu Recht mit einem Versionskonflikt ab.
|
||||
foreach ($candidate in @(
|
||||
(Join-Path $repoRoot 'Directory.Build.props'),
|
||||
$csproj
|
||||
)) {
|
||||
if (-not (Test-Path $candidate)) { continue }
|
||||
|
||||
$match = [regex]::Match((Get-Content $candidate -Raw), '<Version>\s*([^<]+?)\s*</Version>')
|
||||
if ($match.Success) {
|
||||
return $match.Groups[1].Value.Trim()
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
if (-not $Version) {
|
||||
$Version = Get-ProjectVersion
|
||||
if (-not $Version) {
|
||||
throw "Keine <Version> in Directory.Build.props oder $csprojRel gefunden. Bitte -Version angeben."
|
||||
}
|
||||
Write-Host "Version aus dem Projekt gelesen: $Version" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
if (-not $Changelog) {
|
||||
$Changelog = "Release v$Version"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------- Werkzeug
|
||||
function Get-PackAndDeploy {
|
||||
$exe = Join-Path $toolDir 'pack-and-deploy.exe'
|
||||
if (Test-Path $exe) { return $exe }
|
||||
|
||||
Write-Host "pack-and-deploy wird geholt ..." -ForegroundColor Cyan
|
||||
New-Item -ItemType Directory -Force -Path $toolDir | Out-Null
|
||||
|
||||
$name = 'pack-and-deploy-win-x64.exe'
|
||||
$temp = Join-Path $toolDir 'download.tmp'
|
||||
|
||||
Invoke-WebRequest -Uri "$baseUrl/installer/$name" -OutFile $temp -UseBasicParsing
|
||||
|
||||
$expectedRaw = (Invoke-WebRequest -Uri "$baseUrl/installer/$name.sha256" -UseBasicParsing).Content
|
||||
$expected = if ($expectedRaw -is [byte[]]) {
|
||||
[System.Text.Encoding]::ASCII.GetString($expectedRaw)
|
||||
} else { [string]$expectedRaw }
|
||||
$expected = $expected.Trim().ToLower()
|
||||
|
||||
$actual = (Get-FileHash $temp -Algorithm SHA256).Hash.ToLower()
|
||||
|
||||
if ($actual -ne $expected) {
|
||||
Remove-Item $temp -Force
|
||||
throw "Pruefsumme von $name stimmt nicht.`n erwartet: $expected`n erhalten: $actual"
|
||||
}
|
||||
|
||||
Move-Item $temp $exe -Force
|
||||
try { Unblock-File $exe -ErrorAction SilentlyContinue } catch { }
|
||||
|
||||
Write-Host " Pruefsumme in Ordnung." -ForegroundColor DarkGray
|
||||
return $exe
|
||||
}
|
||||
|
||||
$packAndDeploy = Get-PackAndDeploy
|
||||
|
||||
# --------------------------------------------------------------------- Ablauf
|
||||
Write-Host ''
|
||||
Write-Host "Projekt : $($config.project)" -ForegroundColor White
|
||||
Write-Host "Version : $Version"
|
||||
Write-Host "Kanal : $Channel"
|
||||
Write-Host "Plattform : $($config.runtimes -join ', ')"
|
||||
Write-Host ''
|
||||
|
||||
$results = @()
|
||||
|
||||
foreach ($rid in $config.runtimes) {
|
||||
Write-Host "=== $rid ===" -ForegroundColor Cyan
|
||||
|
||||
$publishDir = Join-Path $repoRoot "artifacts/publish/$rid"
|
||||
|
||||
if ($PSCmdlet.ShouldProcess("$($config.project) $Version ($rid)", 'dotnet publish')) {
|
||||
# Sauber neu bauen: Reste einer vorherigen Laufzeitkennung wuerden
|
||||
# sonst mit ins Paket wandern.
|
||||
if (Test-Path $publishDir) { Remove-Item $publishDir -Recurse -Force }
|
||||
|
||||
$publishArgs = @(
|
||||
'publish', $csproj,
|
||||
'-c', 'Release',
|
||||
'-r', $rid,
|
||||
'-o', $publishDir,
|
||||
'--nologo'
|
||||
)
|
||||
|
||||
if ($config.selfContained) { $publishArgs += '--self-contained', 'true' }
|
||||
else { $publishArgs += '--self-contained', 'false' }
|
||||
|
||||
if ($config.publishSingleFile) { $publishArgs += '-p:PublishSingleFile=true' }
|
||||
|
||||
& dotnet @publishArgs
|
||||
if ($LASTEXITCODE -ne 0) { throw "dotnet publish fuer $rid ist fehlgeschlagen." }
|
||||
}
|
||||
|
||||
# setup.json mitliefern, damit der Installer weiss, was einzurichten ist.
|
||||
# Bewusst kein Ternary-Operator: den gibt es erst ab PowerShell 7, und
|
||||
# diese Vorlage soll auch mit dem mitgelieferten 5.1 laufen.
|
||||
$setupRel = if ($config.setupJson) { $config.setupJson } else { 'setup.json' }
|
||||
$setupJson = Join-Path $repoRoot $setupRel
|
||||
if (Test-Path $setupJson) {
|
||||
Copy-Item $setupJson (Join-Path $publishDir 'setup.json') -Force
|
||||
Write-Host " setup.json mitgenommen" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
if ($PSCmdlet.ShouldProcess("$($config.project) $Version ($rid)", 'pack-and-deploy')) {
|
||||
$packArgs = @(
|
||||
'--project', $config.project,
|
||||
'--version', $Version,
|
||||
'--channel', $Channel,
|
||||
'--platform', $rid,
|
||||
'--publish-dir', $publishDir,
|
||||
'--changelog', $Changelog
|
||||
)
|
||||
|
||||
if ($Critical) { $packArgs += '--critical' }
|
||||
if ($config.mainAssembly) { $packArgs += '--main-assembly', $config.mainAssembly }
|
||||
|
||||
& $packAndDeploy @packArgs
|
||||
$code = $LASTEXITCODE
|
||||
|
||||
$results += [pscustomobject]@{ Runtime = $rid; ExitCode = $code }
|
||||
|
||||
# 1 = Konfigurationsfehler oder Versionskonflikt: dann stimmt etwas
|
||||
# Grundsaetzliches, und die weiteren Plattformen wuerden genauso
|
||||
# scheitern.
|
||||
if ($code -eq 1) { throw "pack-and-deploy meldet einen Konfigurations- oder Versionsfehler." }
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ Ergebnis
|
||||
Write-Host '=== Ergebnis ===' -ForegroundColor White
|
||||
|
||||
foreach ($r in $results) {
|
||||
$text = switch ($r.ExitCode) {
|
||||
0 { 'vollstaendig veroeffentlicht' }
|
||||
2 { 'TEILWEISE - Upload oder Registrierung fehlgeschlagen' }
|
||||
default { "unerwarteter Rueckgabewert $($r.ExitCode)" }
|
||||
}
|
||||
$color = if ($r.ExitCode -eq 0) { 'Green' } else { 'Yellow' }
|
||||
Write-Host (" {0,-16} {1}" -f $r.Runtime, $text) -ForegroundColor $color
|
||||
}
|
||||
|
||||
$failed = @($results | Where-Object { $_.ExitCode -ne 0 })
|
||||
|
||||
if ($failed.Count -gt 0) {
|
||||
Write-Host ''
|
||||
Write-Host 'Nicht alle Plattformen sind durchgelaufen. Vor einem erneuten Versuch pruefen,' -ForegroundColor Yellow
|
||||
Write-Host 'ob die bereits hochgeladenen Dateien konsistent sind.' -ForegroundColor Yellow
|
||||
exit 2
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "Fertig. $($config.project) $Version ist im Kanal $Channel verfuegbar." -ForegroundColor Green
|
||||
Reference in New Issue
Block a user