acdream/tools/publish-bin.ps1
Erik ff01423f3f
Some checks failed
CI / linux-portable (push) Failing after 3m22s
CI / windows-gate (push) Successful in 5m27s
CI / release (push) Has been skipped
ci: put the launcher's update pointer in a release, and delete the dist branch
The dist branch existed to carry ~120 MB payloads that could not go on main.
Once payloads became release attachments it held one 500-byte manifest.json,
so it was a whole branch for a reason that no longer applied.

The pointer is now a release asset too: each publish recreates a one-asset
 release naming the versioned build. Forgejo has no
/releases/latest/download/ route (404), so a pointer is still required — but
keeping it in a release means nothing about distribution lives in git: no
payload branch, no bot commits on main, and no push that could retrigger the
pipeline (which is why writing the manifest to main was not the answer either).

Recreating the tag deletes the old release AND its tag; the tag outlives its
release and would otherwise block recreation.

Versioned releases are retained, so older builds stay downloadable.
tools/publish-dist.ps1 is removed — publishing is CI's job now.
2026-08-19 14:40:41 +02:00

251 lines
10 KiB
PowerShell

<#
.SYNOPSIS
Publishes the alpha distribution feed into the repo's /bin folder.
.DESCRIPTION
Builds self-contained client and launcher payloads, zips them, and writes
bin/manifest.json pointing at this repository's PUBLIC Gitea raw URLs.
Commit and push /bin afterwards and the launcher's "Check for updates"
finds the new build — no release server, no tokens, no CDN.
Payload contents (what the launcher expects to find at each zip root):
client-<rid>.zip AcDream.App[.exe] + acdream-headless[.exe]
launcher-<rid>.zip acdream-launcher[.exe] + acdream-bake[.exe]
(the launcher csproj co-deploys the bake CLI)
The launcher's own published version is stamped to the manifest version so
a freshly published launcher does NOT report itself as out of date.
.PARAMETER Version
SemVer 2.0 release version. Defaults to a monotonic UTC build stamp,
e.g. 0.1.0-build.202608181552. Must sort ABOVE the previous published
version or the launcher will not offer it as an update.
.PARAMETER IncludeLinux
Also publish linux-x64 payloads. Off by default: Linux graphical is parked
at Slice L1, and each RID roughly doubles build time and feed size.
.EXAMPLE
pwsh -NoProfile -File tools/publish-bin.ps1
git add bin
git commit -m "release: alpha build"
git push origin main
#>
[CmdletBinding()]
param(
[string]$Version,
[string]$BaseUrl,
[switch]$IncludeLinux,
[string]$MinimumLauncherVersion = '0.0.1'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
if ($PSVersionTable.PSVersion.Major -lt 7) {
throw 'publish-bin requires PowerShell 7 or newer.'
}
$RepoRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
if (-not (Test-Path (Join-Path $RepoRoot 'AcDream.slnx'))) {
throw "Could not locate AcDream.slnx above '$PSScriptRoot'."
}
if ([string]::IsNullOrWhiteSpace($Version)) {
$Version = '0.1.0-build.{0}' -f ([DateTime]::UtcNow.ToString('yyyyMMddHHmm'))
}
$semver = '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$'
foreach ($candidate in @($Version, $MinimumLauncherVersion)) {
if ($candidate -notmatch $semver) {
throw "Version '$candidate' is not SemVer 2.0 (build metadata '+' is not allowed here)."
}
}
# Where the manifest says the payloads live. Publishing is CI's job: the release
# workflow passes the Gitea RELEASE asset base for the tag it is creating. This
# default only makes a local build self-describing — it names a release tag that
# will not exist until CI publishes one.
$RawBase = if ([string]::IsNullOrWhiteSpace($BaseUrl)) {
"https://git.snakedesert.se/erik/acdream/releases/download/$Version"
} else {
$BaseUrl.TrimEnd('/')
}
$BinRoot = Join-Path $RepoRoot 'bin'
$Staging = Join-Path $BinRoot 'payload'
$Rids = @('win-x64')
if ($IncludeLinux) { $Rids += 'linux-x64' }
Write-Host "acdream alpha feed" -ForegroundColor Cyan
Write-Host " version : $Version"
Write-Host " rids : $($Rids -join ', ')"
Write-Host " output : $BinRoot"
Write-Host ''
if (Test-Path $Staging) { Remove-Item -LiteralPath $Staging -Recurse -Force }
$null = New-Item -ItemType Directory -Path $Staging -Force
function Invoke-Publish {
param(
[Parameter(Mandatory)][string]$Project,
[Parameter(Mandatory)][string]$Rid,
[Parameter(Mandatory)][string]$OutputDirectory,
[switch]$SingleFile
)
$arguments = @(
'publish', (Join-Path $RepoRoot $Project),
'-c', 'Release',
'-r', $Rid,
'--self-contained', 'true',
# InformationalVersion ONLY — never -p:Version. The launcher reads
# AssemblyInformationalVersion (App.GetLauncherVersion), while Version
# also rewrites project-reference versions inside the committed
# packages.<rid>.lock.json files, so stamping it churned every lock
# file with a throwaway build stamp (observed 2026-08-18).
"-p:InformationalVersion=$Version",
# No SourceLink '+<sha>' suffix: LauncherVersion parses this as SemVer.
'-p:IncludeSourceRevisionInInformationalVersion=false',
'-o', $OutputDirectory,
'--nologo'
)
if ($SingleFile) { $arguments += '-p:PublishSingleFile=true' }
& dotnet @arguments | Out-Null
if ($LASTEXITCODE) { throw "publish failed: $Project ($Rid)" }
}
function Remove-DebugSymbols {
param([Parameter(Mandatory)][string]$Directory)
# Players never load these, and the native ones are enormous: a stock
# Avalonia publish ships libSkiaSharp.pdb (80 MB) and
# libHarfBuzzSharp.pdb (20 MB), which alone were 100 MB of a 278 MB
# launcher payload (measured 2026-08-18). MSBuild's DebugType switches
# only govern OUR managed symbols, not the native .pdb files that arrive
# as package runtime assets, so drop them from the payload directly.
$symbols = @(Get-ChildItem -LiteralPath $Directory -Recurse -File -Filter *.pdb)
if ($symbols.Count -eq 0) { return }
$freed = ($symbols | Measure-Object -Property Length -Sum).Sum
$symbols | Remove-Item -Force
' stripped {0} debug symbol file(s), {1:N1} MB' -f $symbols.Count, ($freed / 1MB) |
Write-Host -ForegroundColor DarkGray
}
function New-PayloadZip {
param(
[Parameter(Mandatory)][string]$SourceDirectory,
[Parameter(Mandatory)][string]$ZipPath,
[Parameter(Mandatory)][string[]]$RequiredFiles
)
Remove-DebugSymbols $SourceDirectory
foreach ($required in $RequiredFiles) {
if (-not (Test-Path -LiteralPath (Join-Path $SourceDirectory $required))) {
throw "Payload '$SourceDirectory' is missing required file '$required'."
}
}
if (Test-Path -LiteralPath $ZipPath) { Remove-Item -LiteralPath $ZipPath -Force }
# includeBaseDirectory:$false -> entries sit at the zip ROOT, which is where
# LauncherExecutableSet resolves the hosts after extraction.
[IO.Compression.ZipFile]::CreateFromDirectory(
$SourceDirectory,
$ZipPath,
[IO.Compression.CompressionLevel]::Optimal,
$false)
}
function Get-Artifact {
param(
[Parameter(Mandatory)][string]$ZipPath,
[Parameter(Mandatory)][string]$Url
)
$item = Get-Item -LiteralPath $ZipPath
return [ordered]@{
url = $Url
sha256 = (Get-FileHash -LiteralPath $ZipPath -Algorithm SHA256).Hash.ToLowerInvariant()
size = $item.Length
}
}
Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue
$clients = [ordered]@{}
$launchers = [ordered]@{}
foreach ($rid in $Rids) {
$suffix = if ($rid -like 'win-*') { '.exe' } else { '' }
$clientDirectory = Join-Path $Staging "client-$rid"
$launcherDirectory = Join-Path $Staging "launcher-$rid"
Write-Host "[$rid] publishing client (App + Headless)..." -ForegroundColor Yellow
Invoke-Publish 'src/AcDream.App/AcDream.App.csproj' $rid $clientDirectory
Invoke-Publish 'src/AcDream.Headless/AcDream.Headless.csproj' $rid $clientDirectory
# PublishSingleFile/SelfContained come from the launcher csproj itself, and
# its PublishCoDeployedBakeTool target adds acdream-bake as a SECOND
# self-contained single file in the same directory. Two independent .NET
# runtimes is deliberate (see that csproj's comments: a framework-dependent
# bake would scatter AcDream.Content assemblies into the launcher's output),
# so this payload is ~103 MB and CANNOT be pushed to GitHub, whose hard
# per-file limit is 100 MB. The alpha feed is Gitea-only by design.
Write-Host "[$rid] publishing launcher (+ co-deployed bake)..." -ForegroundColor Yellow
Invoke-Publish 'src/AcDream.Launcher/AcDream.Launcher.csproj' $rid $launcherDirectory
$clientZip = Join-Path $BinRoot "client-$rid.zip"
$launcherZip = Join-Path $BinRoot "launcher-$rid.zip"
Write-Host "[$rid] packing..." -ForegroundColor Yellow
New-PayloadZip $clientDirectory $clientZip @("AcDream.App$suffix", "acdream-headless$suffix")
New-PayloadZip $launcherDirectory $launcherZip @("acdream-launcher$suffix", "acdream-bake$suffix")
$clients[$rid] = Get-Artifact $clientZip "$RawBase/client-$rid.zip"
$launchers[$rid] = Get-Artifact $launcherZip "$RawBase/launcher-$rid.zip"
}
$manifest = [ordered]@{
schemaVersion = 1
version = $Version
minimumLauncherVersion = $MinimumLauncherVersion
clients = $clients
launchers = $launchers
}
$manifestPath = Join-Path $BinRoot 'manifest.json'
$json = $manifest | ConvertTo-Json -Depth 6
[IO.File]::WriteAllText($manifestPath, $json + "`n", [Text.UTF8Encoding]::new($false))
Remove-Item -LiteralPath $Staging -Recurse -Force
Write-Host ''
Write-Host 'Feed written:' -ForegroundColor Green
$total = 0L
foreach ($file in (Get-ChildItem -LiteralPath $BinRoot -File | Sort-Object Name)) {
$total += $file.Length
' {0,-26} {1,10:N1} MB' -f $file.Name, ($file.Length / 1MB) | Write-Host
}
' {0,-26} {1,10:N1} MB' -f 'TOTAL', ($total / 1MB) | Write-Host
# A RID-specific restore rewrites packages.<rid>.lock.json for the projects it
# touches, so a feed build can leave the working tree dirty even though nothing
# about the source changed. Report it rather than silently reverting: the files
# are the developer's, and a real dependency change must not be swallowed here.
$dirtyLocks = @(
@(& git -C $RepoRoot status --porcelain -- '*packages.*.lock.json' 2>$null) |
Where-Object { $_ }
)
if ($dirtyLocks.Count -gt 0) {
Write-Host ''
Write-Host 'Note: the RID restore modified these lock files:' -ForegroundColor Yellow
$dirtyLocks | ForEach-Object { " $($_.Trim())" | Write-Host }
Write-Host ' If you did not change dependencies, discard them:' -ForegroundColor DarkGray
Write-Host " git checkout -- '*packages.*.lock.json'" -ForegroundColor DarkGray
}
Write-Host ''
Write-Host 'Releases are published by CI, not from here:' -ForegroundColor Cyan
Write-Host ' push to main -> .gitea/workflows/ci.yml -> Gitea Release' -ForegroundColor DarkGray
Write-Host ' This build is for local inspection; bin/ stays gitignored.' -ForegroundColor DarkGray