Enhance branding and release workflows

- Updated README.md to include a reference to branding and releases documentation.
- Modified brand_config.py to support multi-brand packaging, including functions for collecting local release data and merging release manifests.
- Adjusted build_macos.sh to set a default brand if none is specified and updated DMG naming conventions.
- Enhanced create_release.ps1 and create_release.sh scripts to support dry-run functionality and improved artifact handling.
- Added a new template for brand configuration in build/brands/template.jsonc.
- Created comprehensive branding and releases documentation in docs/BRANDING_AND_RELEASES.md.
- Added unit tests for new branding functionalities in test_brand_config.py.
This commit is contained in:
claudi 2026-03-12 08:38:40 +01:00
parent fd69996c53
commit 67bfe4a600
8 changed files with 923 additions and 82 deletions

View file

@ -3,7 +3,7 @@ param(
[string]$Version,
[Parameter(Mandatory = $false)]
[string[]]$Brands = @("agravity"),
[string[]]$Brands,
[Parameter(Mandatory = $false)]
[string]$ForgejoUser,
@ -12,6 +12,7 @@ param(
[string]$ForgejoPW,
[switch]$ClearCredentials,
[switch]$DryRun,
[string]$ForgejoUrl = "https://git.him-tools.de",
[string]$Repo = "HIM-public/webdrop-bridge"
@ -26,11 +27,32 @@ if (-not (Test-Path $pythonExe)) {
$brandHelper = Join-Path $projectRoot "build\scripts\brand_config.py"
$manifestOutput = Join-Path $projectRoot "build\dist\release-manifest.json"
$localManifestPath = Join-Path $projectRoot "build\dist\release-manifest.local.json"
$existingManifestPath = Join-Path $projectRoot "build\dist\release-manifest.existing.json"
function Get-CurrentVersion {
return (& $pythonExe -c "from pathlib import Path; import sys; sys.path.insert(0, str(Path(r'$projectRoot/build/scripts').resolve())); from version_utils import get_current_version; print(get_current_version())").Trim()
}
function Get-LocalReleaseData {
$arguments = @($brandHelper, "local-release-data", "--platform", "windows", "--version", $Version)
if ($Brands) {
$arguments += "--brands"
$arguments += $Brands
}
return (& $pythonExe @arguments | ConvertFrom-Json)
}
function Get-AssetMap {
param([object[]]$Assets)
$map = @{}
foreach ($asset in ($Assets | Where-Object { $_ })) {
$map[$asset.name] = $asset
}
return $map
}
if ($ClearCredentials) {
Remove-Item env:FORGEJO_USER -ErrorAction SilentlyContinue
Remove-Item env:FORGEJO_PASS -ErrorAction SilentlyContinue
@ -38,6 +60,44 @@ if ($ClearCredentials) {
exit 0
}
if (-not $Version) {
$Version = Get-CurrentVersion
}
$localData = Get-LocalReleaseData
$artifactPaths = New-Object System.Collections.Generic.List[string]
foreach ($artifact in $localData.artifacts) {
$artifactPaths.Add([string]$artifact)
if ((Test-Path $artifact) -and ((Get-Item $artifact).Extension -eq ".msi")) {
$msiSize = (Get-Item $artifact).Length / 1MB
Write-Host "Windows artifact: $([System.IO.Path]::GetFileName($artifact)) ($([math]::Round($msiSize, 2)) MB)"
}
}
if ($artifactPaths.Count -eq 0) {
Write-Host "ERROR: No local Windows artifacts found" -ForegroundColor Red
exit 1
}
$localData.manifest | ConvertTo-Json -Depth 10 | Set-Content -Path $localManifestPath -Encoding utf8
if ($DryRun) {
Copy-Item $localManifestPath $manifestOutput -Force
$brandsText = if ($localData.brands.Count -gt 0) { $localData.brands -join ", " } else { "<none>" }
Write-Host "[DRY RUN] No network requests or uploads will be performed." -ForegroundColor Yellow
Write-Host "[DRY RUN] Release tag: v$Version"
Write-Host "[DRY RUN] Release URL: $ForgejoUrl/$Repo/releases/tag/v$Version"
Write-Host "[DRY RUN] Discovered brands: $brandsText"
Write-Host "[DRY RUN] Artifacts that would be uploaded:"
foreach ($artifact in $artifactPaths) {
Write-Host " - $artifact"
}
Write-Host "[DRY RUN] Local manifest preview: $manifestOutput"
exit 0
}
if (-not $ForgejoUser) {
$ForgejoUser = $env:FORGEJO_USER
}
@ -58,36 +118,6 @@ if (-not $ForgejoUser -or -not $ForgejoPW) {
$env:FORGEJO_PASS = $ForgejoPW
}
if (-not $Version) {
$Version = Get-CurrentVersion
}
$artifactPaths = New-Object System.Collections.Generic.List[string]
foreach ($brand in $Brands) {
$brandJson = & $pythonExe $brandHelper show --brand $brand | ConvertFrom-Json
$msiPath = Join-Path $projectRoot "build\dist\windows\$($brandJson.brand_id)\$($brandJson.asset_prefix)-$Version-win-x64.msi"
$checksumPath = "$msiPath.sha256"
if (Test-Path $msiPath) {
$artifactPaths.Add($msiPath)
if (Test-Path $checksumPath) {
$artifactPaths.Add($checksumPath)
}
$msiSize = (Get-Item $msiPath).Length / 1MB
Write-Host "Windows artifact: $([System.IO.Path]::GetFileName($msiPath)) ($([math]::Round($msiSize, 2)) MB)"
}
}
& $pythonExe $brandHelper release-manifest --version $Version --output $manifestOutput --brands $Brands | Out-Null
if (Test-Path $manifestOutput) {
$artifactPaths.Add($manifestOutput)
}
if ($artifactPaths.Count -eq 0) {
Write-Host "ERROR: No Windows artifacts found for the requested brands" -ForegroundColor Red
exit 1
}
$auth = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("${ForgejoUser}:${ForgejoPW}"))
$headers = @{
"Authorization" = "Basic $auth"
@ -105,28 +135,46 @@ $releaseData = @{
} | ConvertTo-Json
try {
$lookupResponse = Invoke-WebRequest -Uri $releaseLookupUrl -Method GET -Headers $headers -TimeoutSec 30 -UseBasicParsing -ErrorAction Stop
$releaseInfo = $lookupResponse.Content | ConvertFrom-Json
$releaseInfo = Invoke-RestMethod -Uri $releaseLookupUrl -Method GET -Headers $headers -TimeoutSec 30 -ErrorAction Stop
$releaseId = $releaseInfo.id
Write-Host "[OK] Using existing release (ID: $releaseId)" -ForegroundColor Green
}
catch {
$response = Invoke-WebRequest -Uri $releaseUrl -Method POST -Headers $headers -Body $releaseData -TimeoutSec 30 -UseBasicParsing -ErrorAction Stop
$releaseInfo = $response.Content | ConvertFrom-Json
$releaseInfo = Invoke-RestMethod -Uri $releaseUrl -Method POST -Headers $headers -Body $releaseData -TimeoutSec 30 -ErrorAction Stop
$releaseId = $releaseInfo.id
Write-Host "[OK] Release created (ID: $releaseId)" -ForegroundColor Green
}
$assetMap = Get-AssetMap -Assets $releaseInfo.assets
if ($assetMap.ContainsKey("release-manifest.json")) {
Invoke-WebRequest -Uri $assetMap["release-manifest.json"].browser_download_url -Method GET -Headers $headers -TimeoutSec 30 -OutFile $existingManifestPath | Out-Null
& $pythonExe $brandHelper merge-manifests --base $existingManifestPath --overlay $localManifestPath --output $manifestOutput | Out-Null
}
else {
Copy-Item $localManifestPath $manifestOutput -Force
}
$artifactPaths.Add($manifestOutput)
$assetMap = Get-AssetMap -Assets $releaseInfo.assets
$curlAuth = "$ForgejoUser`:$ForgejoPW"
$uploadUrl = "$ForgejoUrl/api/v1/repos/$Repo/releases/$releaseId/assets"
foreach ($artifact in $artifactPaths) {
$assetName = [System.IO.Path]::GetFileName($artifact)
if ($assetMap.ContainsKey($assetName)) {
$existingAsset = $assetMap[$assetName]
Invoke-RestMethod -Uri "$ForgejoUrl/api/v1/repos/$Repo/releases/$releaseId/assets/$($existingAsset.id)" -Method DELETE -Headers $headers -TimeoutSec 30 | Out-Null
Write-Host "[OK] Replaced existing asset $assetName" -ForegroundColor Yellow
}
$response = curl.exe -s -X POST -u $curlAuth -F "attachment=@$artifact" $uploadUrl
if ($response -like "*error*" -or $response -like "*404*") {
Write-Host "WARNING: Could not upload $artifact : $response" -ForegroundColor Yellow
}
else {
Write-Host "[OK] Uploaded $([System.IO.Path]::GetFileName($artifact))" -ForegroundColor Green
Write-Host "[OK] Uploaded $assetName" -ForegroundColor Green
}
}