290 lines
8.6 KiB
PowerShell
290 lines
8.6 KiB
PowerShell
# Copy of D:\TTRPG-Release\publish.ps1 - keep in sync when updating the release folder tool.
|
|
|
|
param(
|
|
[switch]$CheckOnly,
|
|
[switch]$SkipMac
|
|
)
|
|
|
|
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-YmlVersion([string]$ymlPath) {
|
|
$content = Get-Content -LiteralPath $ymlPath -Raw -Encoding UTF8
|
|
$m = [regex]::Match($content, '(?m)^version:\s*(\S+)\s*$')
|
|
if ($m.Success) {
|
|
return $m.Groups[1].Value.Trim()
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Get-YmlPrimaryPath([string]$ymlPath) {
|
|
$content = Get-Content -LiteralPath $ymlPath -Raw -Encoding UTF8
|
|
$m = [regex]::Match($content, '(?m)^path:\s*(\S+)\s*$')
|
|
if ($m.Success) {
|
|
return $m.Groups[1].Value.Trim()
|
|
}
|
|
return $null
|
|
}
|
|
|
|
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 Get-YmlOptionalUrls([string]$ymlPath) {
|
|
$primary = Get-YmlPrimaryPath $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:\s*(\S+)\s*$')) {
|
|
$name = $m.Groups[1].Value.Trim()
|
|
if ($primary -and $name.Equals($primary, [StringComparison]::OrdinalIgnoreCase)) {
|
|
continue
|
|
}
|
|
[void]$names.Add($name)
|
|
}
|
|
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 ($SkipMac) {
|
|
Write-Warn 'macOS skipped (-SkipMac)'
|
|
} elseif (-not (Test-Path -LiteralPath $macYml)) {
|
|
Write-Warn 'No latest-mac.yml - skipping macOS'
|
|
} else {
|
|
Write-Ok 'latest-mac.yml'
|
|
[void]$uploadFiles.Add($macYml)
|
|
|
|
$macVersion = Get-YmlVersion $macYml
|
|
$winYml = Join-Path $ReleaseDir 'latest.yml'
|
|
$winVersion = $null
|
|
if (Test-Path -LiteralPath $winYml) {
|
|
$winVersion = Get-YmlVersion $winYml
|
|
}
|
|
if ($macVersion -and $winVersion -and $macVersion -ne $winVersion) {
|
|
$errors.Add(
|
|
"macOS: latest-mac.yml is v$macVersion but latest.yml is v$winVersion (stale Mac feed). " +
|
|
'On Mac run: npm run pack:mac, then copy latest-mac.yml + TTRPGPlayer-*.zip (+ optional *.dmg). ' +
|
|
'Or publish Win/Linux only: publish.cmd -SkipMac'
|
|
)
|
|
}
|
|
|
|
$primaryName = Get-YmlPrimaryPath $macYml
|
|
if (-not $primaryName) {
|
|
$errors.Add('macOS: latest-mac.yml has no path: entry')
|
|
} else {
|
|
$primaryFile = Resolve-ReleaseFile $primaryName
|
|
if ($null -eq $primaryFile) {
|
|
$errors.Add(
|
|
"macOS: missing primary update file $primaryName (path in latest-mac.yml). " +
|
|
'electron-updater on macOS needs the .zip from pack:mac, not only .dmg. Rebuild on Mac or use -SkipMac.'
|
|
)
|
|
} else {
|
|
Write-Ok "primary update: $($primaryFile.Name)"
|
|
Add-FileToUploadSet $uploadFiles $primaryFile
|
|
}
|
|
}
|
|
|
|
foreach ($name in (Get-YmlOptionalUrls $macYml)) {
|
|
$file = Resolve-ReleaseFile $name
|
|
if ($null -eq $file) {
|
|
$warnings.Add("macOS: optional file missing (not uploaded): $name")
|
|
} else {
|
|
Write-Ok "optional: $($file.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"
|
|
if (Test-Path -LiteralPath (Join-Path $ReleaseDir 'latest-linux.yml')) {
|
|
Write-Host " ${feedUrl}latest-linux.yml"
|
|
}
|
|
if (Test-Path -LiteralPath (Join-Path $ReleaseDir 'latest-mac.yml')) {
|
|
Write-Host " ${feedUrl}latest-mac.yml"
|
|
}
|
|
|
|
exit 0
|