6565 dotnet restore Lite/PerformanceMonitorLite.csproj --locked-mode
6666 dotnet restore Installer/PerformanceMonitorInstaller.csproj --locked-mode
6767 dotnet restore Lite.Tests/Lite.Tests.csproj --locked-mode
68+ dotnet restore Darling/PerformanceMonitor.Darling.Viewer/PerformanceMonitor.Darling.Viewer.csproj --locked-mode
6869
6970 - name : Run tests
7071 run : dotnet test Lite.Tests/Lite.Tests.csproj -c Release --verbosity normal
7879 - name : Publish CLI Installer
7980 run : dotnet publish Installer/PerformanceMonitorInstaller.csproj -c Release
8081
82+ - name : Publish Darling Service
83+ run : dotnet publish Darling/PerformanceMonitor.Darling.Service/PerformanceMonitor.Darling.Service.csproj -c Release -o publish/DarlingService
84+
85+ - name : Publish Darling Viewer
86+ run : dotnet publish Darling/PerformanceMonitor.Darling.Viewer/PerformanceMonitor.Darling.Viewer.csproj -c Release -o publish/DarlingViewer
87+
88+ # Self-contained viewer publish that feeds the remote-seat Velopack Setup.exe (#1555), the same
89+ # publish shape build.yml uses for the Dashboard/Lite Velopack packs. This is IN ADDITION to the
90+ # framework-dependent "Publish Darling Viewer" above, which still feeds the co-located viewer\
91+ # folder inside PerformanceMonitorDarling-*.zip — that zip is unchanged.
92+ - name : Publish Darling Viewer (self-contained for Velopack)
93+ run : dotnet publish Darling/PerformanceMonitor.Darling.Viewer/PerformanceMonitor.Darling.Viewer.csproj -c Release -r win-x64 --self-contained -o publish/DarlingViewer-velopack
94+
95+ # Same cache key as build.yml's release path: the fetch script's content hash (the SHA256
96+ # pins live inside it). The ~340MB EDB/TimescaleDB fetch runs at most once per pin-set per
97+ # branch; nightly and release runs share the assembled zip whenever the cache is visible.
98+ - name : Cache Darling pg-runtime.zip
99+ id : cache-pg-runtime
100+ uses : actions/cache@v4
101+ with :
102+ path : Darling/artifacts/pg-runtime.zip
103+ key : pg-runtime-${{ runner.os }}-${{ hashFiles('Darling/tools/fetch-pg-runtime.ps1') }}
104+
105+ - name : Build Darling pg-runtime.zip
106+ if : steps.cache-pg-runtime.outputs.cache-hit != 'true'
107+ shell : pwsh
108+ run : ./Darling/tools/fetch-pg-runtime.ps1
109+
81110 - name : Package artifacts
82111 shell : pwsh
83112 run : |
@@ -102,10 +131,39 @@ jobs:
102131
103132 Compress-Archive -Path 'publish/Installer/*' -DestinationPath "releases/PerformanceMonitorInstaller-$version.zip" -Force
104133
134+ # Same layout as the release zip (build.yml "Package Darling (signed)"): service at the
135+ # archive root with darling.sample.json alongside, viewer under viewer\, pg-runtime.zip
136+ # beside the service exe where DarlingManagedPostgres extracts it on first run. Nightly
137+ # zips are unsigned across the board, so this stages from publish/ instead of signed/.
138+ $darlingDir = 'publish/Darling'
139+ New-Item -ItemType Directory -Force -Path "$darlingDir/viewer" | Out-Null
140+ Copy-Item 'publish/DarlingService/*' $darlingDir -Recurse
141+ Copy-Item 'publish/DarlingViewer/*' "$darlingDir/viewer" -Recurse
142+ Copy-Item 'Darling/artifacts/pg-runtime.zip' $darlingDir
143+
144+ Compress-Archive -Path 'publish/Darling/*' -DestinationPath "releases/PerformanceMonitorDarling-$version.zip" -Force
145+
146+ # Darling viewer remote-seat installer (#1555). Nightly ships it UNSIGNED like every other nightly
147+ # artifact. Mirrors build.yml's release vpk pack (same pack id / exe / channel) but packs from the
148+ # raw self-contained publish (no SignPath), and deliberately does NOT touch the Velopack update
149+ # feed: the nightly GitHub release is deleted + recreated each night, so there is no persistent
150+ # delta chain to `vpk download`/`vpk upload` from — we ship a standalone full Setup.exe as a plain
151+ # release asset. Copied to a deterministic name so the checksum + upload steps below pick it up.
152+ # Purely additive: the co-located viewer inside PerformanceMonitorDarling-*.zip is untouched.
153+ - name : Create Darling Viewer Setup.exe (Velopack, unsigned)
154+ shell : pwsh
155+ run : |
156+ $version = "${{ steps.version.outputs.VERSION }}"
157+ dotnet tool install -g vpk --version 1.2.0
158+ New-Item -ItemType Directory -Force -Path releases/velopack-darlingviewer
159+ vpk pack -u PerformanceMonitorDarlingViewer -v $version -p publish/DarlingViewer-velopack -e PerformanceMonitor.Darling.Viewer.exe -o releases/velopack-darlingviewer --channel darlingviewer
160+ $setup = Get-ChildItem releases/velopack-darlingviewer/*Setup.exe | Select-Object -First 1
161+ Copy-Item $setup.FullName "releases/PerformanceMonitorDarlingViewer-$version-Setup.exe"
162+
105163 - name : Generate checksums
106164 shell : pwsh
107165 run : |
108- $checksums = Get-ChildItem releases/*.zip | ForEach-Object {
166+ $checksums = Get-ChildItem releases/*.zip, releases/*.exe | ForEach-Object {
109167 $hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower()
110168 "$hash $($_.Name)"
111169 }
@@ -142,4 +200,123 @@ jobs:
142200 --title "Nightly Build ($version)" `
143201 --notes $body `
144202 --prerelease `
145- releases/*.zip releases/SHA256SUMS.txt
203+ releases/*.zip releases/*.exe releases/SHA256SUMS.txt
204+
205+ # Darling.Tests has live-PostgreSQL tests gated on env vars that the per-PR/push build never
206+ # sets, so regular CI runs only the ungated subset. This job stands up a throwaway PostgreSQL
207+ # from the bundled pg-runtime and runs the full Darling suite against it nightly, so the
208+ # Postgres + managed-bootstrap paths get real coverage. The one live-SQL-Server E2E stays
209+ # skipped (no SQL Server on the runner) — expected. Gated like the build job so it only runs
210+ # when dev actually changed (or on manual dispatch).
211+ darling-pg :
212+ name : Darling PostgreSQL tests
213+ needs : check
214+ if : needs.check.outputs.has_changes == 'true' || github.event_name == 'workflow_dispatch'
215+ runs-on : windows-latest
216+
217+ steps :
218+ # Scheduled runs always test dev (schedules execute from the default branch, so ref_name
219+ # would be main). A manual dispatch tests the DISPATCHED ref — the only way to validate a
220+ # branch's gated-pg test changes before merge; the artifact-publishing build job stays
221+ # pinned to dev either way, so a branch dispatch can never ship branch binaries.
222+ - uses : actions/checkout@v5
223+ with :
224+ ref : ${{ github.event_name == 'workflow_dispatch' && github.ref_name || 'dev' }}
225+
226+ - name : Setup .NET 10.0
227+ uses : actions/setup-dotnet@v5
228+ with :
229+ dotnet-version : 10.0.x
230+ cache : true
231+ cache-dependency-path : ' **/packages.lock.json'
232+
233+ # Darling bundles a PostgreSQL 18 + TimescaleDB runtime (pg-runtime.zip). The fetch script
234+ # pulls ~340MB of pinned EDB/TimescaleDB archives, so cache the assembled zip on the SAME
235+ # key build.yml's release job uses (the fetch script's own content hash) — nightly and
236+ # release share one cache entry, so a warm cache means no download here.
237+ - name : Cache Darling pg-runtime.zip
238+ id : cache-pg-runtime
239+ uses : actions/cache@v4
240+ with :
241+ path : Darling/artifacts/pg-runtime.zip
242+ key : pg-runtime-${{ runner.os }}-${{ hashFiles('Darling/tools/fetch-pg-runtime.ps1') }}
243+
244+ - name : Build Darling pg-runtime.zip (cache miss only)
245+ if : steps.cache-pg-runtime.outputs.cache-hit != 'true'
246+ shell : pwsh
247+ run : ./Darling/tools/fetch-pg-runtime.ps1
248+
249+ # One uniform path for hit and miss: we always have the zip (restored or freshly built),
250+ # so always extract it — simpler than branching on the script's -KeepWork assembled tree.
251+ # ExtractToDirectory mirrors the fetch script's own API and reads any zip it writes.
252+ - name : Extract pg-runtime
253+ shell : pwsh
254+ run : |
255+ $zip = "$env:GITHUB_WORKSPACE\Darling\artifacts\pg-runtime.zip"
256+ $dest = "$env:GITHUB_WORKSPACE\Darling\artifacts\pg-runtime"
257+ if (Test-Path $dest) { Remove-Item -Recurse -Force $dest }
258+ Add-Type -AssemblyName System.IO.Compression.FileSystem
259+ [System.IO.Compression.ZipFile]::ExtractToDirectory($zip, $dest)
260+ if (-not (Test-Path "$dest\pgsql\bin\pg_ctl.exe")) { throw "pg-runtime missing pgsql\bin\pg_ctl.exe" }
261+
262+ - name : Restore Darling.Tests
263+ run : dotnet restore Darling/Darling.Tests/Darling.Tests.csproj --locked-mode
264+
265+ - name : Build Darling.Tests
266+ run : dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Release --no-restore
267+
268+ # Stand up a throwaway cluster from the bundled runtime. initdb TRUST auth is acceptable
269+ # ONLY here: an ephemeral CI runner, loopback-only, throwaway data. The PRODUCT default is
270+ # the opposite (scram-sha-256 + a generated credential; see DarlingManagedPostgres). The
271+ # appended settings mirror DarlingManagedPostgres.BuildConfAppend (timescaledb preload —
272+ # the Timescale-gated tests detect and use it — plus the port and loopback bind). Port 5541
273+ # is fixed and distinct; the managed-bootstrap E2E starts its OWN postgres on a random free
274+ # port, so there is no collision.
275+ - name : Initialize and start throwaway PostgreSQL
276+ shell : pwsh
277+ run : |
278+ $bin = "$env:GITHUB_WORKSPACE\Darling\artifacts\pg-runtime\pgsql\bin"
279+ $dataDir = "$env:RUNNER_TEMP\darling-pgdata"
280+ $logFile = "$env:RUNNER_TEMP\darling-pg.log"
281+ # The bootstrap superuser is named "darling", not "postgres": the V8 schema-split
282+ # migration runs CREATE SCHEMA ... AUTHORIZATION darling (PgSchemaGenerator.OwnerRole),
283+ # so a cluster without that role fails every fresh-store migration. Matching managed
284+ # mode's shape (DarlingManagedPostgres also initdbs its owner as "darling").
285+ & "$bin\initdb.exe" -D $dataDir -U darling -A trust --encoding=UTF8
286+ if ($LASTEXITCODE -ne 0) { throw "initdb failed ($LASTEXITCODE)" }
287+ Add-Content -Path "$dataDir\postgresql.conf" -Value "shared_preload_libraries = 'timescaledb'"
288+ Add-Content -Path "$dataDir\postgresql.conf" -Value "port = 5541"
289+ Add-Content -Path "$dataDir\postgresql.conf" -Value "listen_addresses = '127.0.0.1'"
290+ & "$bin\pg_ctl.exe" -D $dataDir -l $logFile -w start
291+ if ($LASTEXITCODE -ne 0) { if (Test-Path $logFile) { Get-Content $logFile -Tail 50 }; throw "pg_ctl start failed ($LASTEXITCODE)" }
292+ & "$bin\createdb.exe" -h 127.0.0.1 -p 5541 -U darling darling
293+ if ($LASTEXITCODE -ne 0) { throw "createdb failed ($LASTEXITCODE)" }
294+
295+ # DARLING_TEST_PG lights up the [Collection("live-postgres")] classes; DARLING_TEST_PGRUNTIME
296+ # lights up the managed-bootstrap E2E. DARLING_TEST_SQL is intentionally unset — the one
297+ # live-SQL-Server E2E stays skipped (no SQL Server on the runner).
298+ - name : Run Darling PG tests
299+ shell : pwsh
300+ env :
301+ DARLING_TEST_PG : " Host=127.0.0.1;Port=5541;Username=darling;Database=darling"
302+ DARLING_TEST_PGRUNTIME : ${{ github.workspace }}\Darling\artifacts\pg-runtime
303+ run : dotnet test Darling/Darling.Tests/Darling.Tests.csproj -c Release --no-build --verbosity normal --logger "trx;LogFileName=darling-nightly.trx" --results-directory TestResults
304+
305+ - name : Stop PostgreSQL
306+ if : always()
307+ shell : pwsh
308+ run : |
309+ $bin = "$env:GITHUB_WORKSPACE\Darling\artifacts\pg-runtime\pgsql\bin"
310+ $dataDir = "$env:RUNNER_TEMP\darling-pgdata"
311+ if (Test-Path "$bin\pg_ctl.exe") { & "$bin\pg_ctl.exe" -D $dataDir -m fast -w stop }
312+ exit 0
313+
314+ - name : Upload PG log and test results on failure
315+ if : failure()
316+ uses : actions/upload-artifact@v6
317+ with :
318+ name : darling-pg-failure
319+ path : |
320+ ${{ runner.temp }}/darling-pg.log
321+ TestResults/
322+ if-no-files-found : ignore
0 commit comments