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.
251 lines
10 KiB
PowerShell
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
|