# Copy of D:\TTRPG-Release\publish.ps1 - keep in sync when updating the release folder tool. param( [switch]$CheckOnly ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $ReleaseDir = $PSScriptRoot $ConfigPath = Join-Path $ReleaseDir 'publish-config.json' function Expand-ConfigPath([string]$value) { if ($value -match '%([^%]+)%') { $envName = $Matches[1] $envVal = [Environment]::GetEnvironmentVariable($envName) if ($null -ne $envVal) { return $value.Replace("%$envName%", $envVal) } } return $value } function Write-Title([string]$text) { Write-Host '' Write-Host "=== $text ===" -ForegroundColor Cyan } function Write-Ok([string]$text) { Write-Host " [OK] $text" -ForegroundColor Green } function Write-Fail([string]$text) { Write-Host " [!!] $text" -ForegroundColor Red } function Write-Warn([string]$text) { Write-Host " [--] $text" -ForegroundColor Yellow } function Get-YmlReferencedFiles([string]$ymlPath) { $names = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $content = Get-Content -LiteralPath $ymlPath -Raw -Encoding UTF8 foreach ($m in [regex]::Matches($content, '(?m)^(?:\s*-\s*)?(?:url|path):\s*(\S+)\s*$')) { [void]$names.Add($m.Groups[1].Value.Trim()) } return @($names) } function Resolve-ReleaseFile([string]$name) { $direct = Join-Path $ReleaseDir $name if (Test-Path -LiteralPath $direct) { return Get-Item -LiteralPath $direct } if ($name -match 'x64' -and $name -notmatch 'x86_64') { $alt = $name -replace 'x64', 'x86_64' $altPath = Join-Path $ReleaseDir $alt if (Test-Path -LiteralPath $altPath) { return Get-Item -LiteralPath $altPath } } if ($name -match 'x86_64') { $alt = $name -replace 'x86_64', 'x64' $altPath = Join-Path $ReleaseDir $alt if (Test-Path -LiteralPath $altPath) { return Get-Item -LiteralPath $altPath } } return $null } function Add-FileToUploadSet { param( [System.Collections.Generic.HashSet[string]]$set, [System.IO.FileInfo]$file ) [void]$set.Add($file.FullName) } Write-Title 'TTRPG Release Publisher' Write-Host "Release folder: $ReleaseDir" if (-not (Test-Path -LiteralPath $ConfigPath)) { throw "Missing publish-config.json: $ConfigPath" } $config = Get-Content -LiteralPath $ConfigPath -Raw -Encoding UTF8 | ConvertFrom-Json $sshKey = Expand-ConfigPath $config.sshKey $sshTarget = [string]$config.sshTarget $remoteDir = [string]$config.remoteDir $feedUrl = [string]$config.feedUrl if (-not (Test-Path -LiteralPath $sshKey)) { throw "SSH key not found: $sshKey" } $errors = [System.Collections.Generic.List[string]]::new() $warnings = [System.Collections.Generic.List[string]]::new() $uploadFiles = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) Write-Title 'Windows (required)' $winYml = Join-Path $ReleaseDir 'latest.yml' if (-not (Test-Path -LiteralPath $winYml)) { $errors.Add('Missing latest.yml') } else { Write-Ok 'latest.yml' [void]$uploadFiles.Add($winYml) foreach ($name in (Get-YmlReferencedFiles $winYml)) { $file = Resolve-ReleaseFile $name if ($null -eq $file) { $errors.Add("Windows: missing file $name (from latest.yml)") } else { Write-Ok $file.Name Add-FileToUploadSet $uploadFiles $file } } $blockmap = Resolve-ReleaseFile 'TTRPGPlayer-Setup.exe.blockmap' if ($null -eq $blockmap) { $warnings.Add('Missing TTRPGPlayer-Setup.exe.blockmap (recommended)') } else { Write-Ok $blockmap.Name Add-FileToUploadSet $uploadFiles $blockmap } } Write-Title 'Linux (if latest-linux*.yml present)' $linuxYmls = Get-ChildItem -LiteralPath $ReleaseDir -Filter 'latest-linux*.yml' -File -ErrorAction SilentlyContinue if ($linuxYmls.Count -eq 0) { Write-Warn 'No latest-linux*.yml - skipping Linux' } else { foreach ($yml in $linuxYmls) { Write-Ok $yml.Name [void]$uploadFiles.Add($yml.FullName) foreach ($name in (Get-YmlReferencedFiles $yml.FullName)) { $file = Resolve-ReleaseFile $name if ($null -eq $file) { $errors.Add("Linux ($($yml.Name)): missing file $name") } else { if ($file.Name -ne $name) { Write-Warn "$($yml.Name): yml expects $name, disk has $($file.Name) - will upload $($file.Name)" } else { Write-Ok "$($yml.Name) -> $name" } Add-FileToUploadSet $uploadFiles $file } } } } Write-Title 'macOS (if latest-mac.yml present)' $macYml = Join-Path $ReleaseDir 'latest-mac.yml' if (-not (Test-Path -LiteralPath $macYml)) { Write-Warn 'No latest-mac.yml - skipping macOS' } else { Write-Ok 'latest-mac.yml' [void]$uploadFiles.Add($macYml) foreach ($name in (Get-YmlReferencedFiles $macYml)) { $file = Resolve-ReleaseFile $name if ($null -eq $file) { $errors.Add("macOS: missing file $name (from latest-mac.yml)") } else { Write-Ok $name Add-FileToUploadSet $uploadFiles $file } } } Write-Title 'Summary' foreach ($w in $warnings) { Write-Warn $w } if ($errors.Count -gt 0) { foreach ($e in $errors) { Write-Fail $e } Write-Host '' Write-Host 'Upload cancelled. Fix the release folder and run again.' -ForegroundColor Red exit 1 } Write-Host '' Write-Host "Files to upload: $($uploadFiles.Count)" -ForegroundColor Green foreach ($path in ($uploadFiles | Sort-Object)) { Write-Host " - $([System.IO.Path]::GetFileName($path))" } if ($CheckOnly) { Write-Host '' Write-Host 'CheckOnly: upload skipped.' -ForegroundColor Yellow exit 0 } Write-Title 'Upload' Write-Host "Target: ${sshTarget}:${remoteDir}" Write-Host "Feed: $feedUrl" foreach ($path in ($uploadFiles | Sort-Object)) { $name = [System.IO.Path]::GetFileName($path) Write-Host " -> $name" & scp -i $sshKey -q $path "${sshTarget}:${remoteDir}/" if ($LASTEXITCODE -ne 0) { throw "scp failed for $name (exit $LASTEXITCODE)" } } Write-Host '' Write-Host 'Setting www-data ownership on server...' & ssh -i $sshKey $sshTarget "chown -R www-data:www-data '$remoteDir'" if ($LASTEXITCODE -ne 0) { throw "ssh chown failed (exit $LASTEXITCODE)" } Write-Title 'Done' Write-Host 'Verify:' Write-Host " ${feedUrl}latest.yml" exit 0