diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 00000000..5c203078 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,140 @@ +# Gitea Actions CI gate for the self-hosted runners. +# +# Deliberately does NOT use actions/setup-dotnet: data.forgejo.org (the mirror +# Gitea resolves actions from) does not host that action at all, and the +# self-hosted runners carry the pinned SDK band from global.json already. +# actions/checkout IS mirrored, so it is used normally. +# +# The suite runs through tools/run-release-gate.ps1 rather than a bare +# `dotnet test`: that script owns the xUnit trait-lane filter which excludes +# the InstalledDat / Live / Manual / OS-specific lanes. A bare `dotnet test` +# fails ~36 tests by design because those lanes assert their own preconditions. +name: CI +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + windows-gate: + runs-on: windows-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v6 + + - name: Verify the pinned SDK band resolves + shell: pwsh + run: | + dotnet --version + dotnet --list-sdks + + - name: Complete Release gate + shell: pwsh + run: ./tools/run-release-gate.ps1 + + - name: Upload gate evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: release-gate-windows + path: artifacts/release-gate/ + if-no-files-found: warn + retention-days: 14 + + linux-portable: + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v6 + + - name: Portable closure (Linux lanes run here, not on Windows) + run: | + set -e + dotnet --version + for p in \ + tests/AcDream.Platform.Tests \ + tests/AcDream.Core.Tests \ + tests/AcDream.Core.Net.Tests \ + tests/AcDream.Content.Tests \ + tests/AcDream.Runtime.Tests \ + tests/AcDream.Headless.Tests \ + tests/AcDream.Launcher.Core.Tests \ + tests/AcDream.UI.Abstractions.Tests ; do + echo "::group::$p" + dotnet test "$p" -c Release --nologo \ + --filter 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Windows&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure' + echo "::endgroup::" + done + + release: + # Same workflow rather than a workflow_run trigger: workflow_run is a + # GitHub feature whose Forgejo support is unreliable, while `needs` is + # guaranteed. A red gate therefore cannot publish. + needs: [windows-gate, linux-portable] + runs-on: windows-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v6 + + - name: Compute release version + id: ver + shell: pwsh + run: | + $v = '0.1.0-build.{0}' -f ([DateTime]::UtcNow.ToString('yyyyMMddHHmm')) + "version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + Write-Host "release version: $v" + + - name: Build payloads with release-attachment URLs + shell: pwsh + env: + TAG: ${{ steps.ver.outputs.version }} + run: | + ./tools/publish-bin.ps1 -Version $env:TAG -BaseUrl "${{ github.server_url }}/${{ github.repository }}/releases/download/$env:TAG" + + - name: Create the release and upload payloads + shell: pwsh + env: + TAG: ${{ steps.ver.outputs.version }} + TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + $ErrorActionPreference = 'Stop' + $api = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}" + $headers = @{ Authorization = "token $env:TOKEN" } + $body = @{ + tag_name = $env:TAG + name = "acdream alpha $env:TAG" + body = "Automated alpha build from ${{ github.sha }}." + draft = $false + prerelease = $true + target_commitish = 'main' + } | ConvertTo-Json + $release = Invoke-RestMethod -Method Post -Uri "$api/releases" -Headers $headers -ContentType 'application/json' -Body $body + Write-Host "created release id=$($release.id)" + foreach ($f in Get-ChildItem bin -File) { + Write-Host ("uploading {0} ({1:N1} MB)" -f $f.Name, ($f.Length/1MB)) + Invoke-RestMethod -Method Post -Headers $headers -Uri "$api/releases/$($release.id)/assets?name=$($f.Name)" -Form @{ attachment = Get-Item $f.FullName } | Out-Null + } + + - name: Point the launcher manifest at the new release + shell: pwsh + env: + TAG: ${{ steps.ver.outputs.version }} + TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + $ErrorActionPreference = 'Stop' + # dist carries ONLY manifest.json (~500 bytes), force-replaced each + # publish, never pushed to GitHub. Payloads stay in release + # attachments, outside git history entirely. + $url = "${{ github.server_url }}/${{ github.repository }}.git" -replace '^https://', "https://x:$env:TOKEN@" + git config --global user.email 'ci@acdream.local' + git config --global user.name 'acdream CI' + New-Item -ItemType Directory -Force dist-branch/bin | Out-Null + Copy-Item bin/manifest.json dist-branch/bin/manifest.json + Push-Location dist-branch + git init -q + git checkout -q -b dist + git add -f bin/manifest.json + git commit -q -m "release: manifest for $env:TAG" + git push -q --force $url dist:dist + Pop-Location + Write-Host "manifest published for $env:TAG" diff --git a/tests/AcDream.Launcher.Tests/AcDream.Launcher.Tests.csproj b/tests/AcDream.Launcher.Tests/AcDream.Launcher.Tests.csproj index 23aedde8..78b2ec1b 100644 --- a/tests/AcDream.Launcher.Tests/AcDream.Launcher.Tests.csproj +++ b/tests/AcDream.Launcher.Tests/AcDream.Launcher.Tests.csproj @@ -23,4 +23,14 @@ + + + + + diff --git a/tests/AcDream.Launcher.Tests/LauncherProjectBoundaryTests.cs b/tests/AcDream.Launcher.Tests/LauncherProjectBoundaryTests.cs index 111145e4..73713571 100644 --- a/tests/AcDream.Launcher.Tests/LauncherProjectBoundaryTests.cs +++ b/tests/AcDream.Launcher.Tests/LauncherProjectBoundaryTests.cs @@ -140,10 +140,13 @@ public sealed class LauncherProjectBoundaryTests "workflows", "headless-portability.yml")); - Assert.Contains("src/AcDream.Launcher/**", workflow, StringComparison.Ordinal); - Assert.Contains("tests/AcDream.Launcher.Tests/**", workflow, StringComparison.Ordinal); - Assert.Contains("src/AcDream.Bake/**", workflow, StringComparison.Ordinal); - Assert.Contains("tests/AcDream.Bake.Tests/**", workflow, StringComparison.Ordinal); + // The four `**` path filters this used to assert belonged to the + // push/pull_request triggers that `8be14d39 ci: make GitHub workflows + // manual only` deliberately removed; the workflow is workflow_dispatch + // now. What this test is actually for — that the portability workflow + // BUILDS, TESTS, PUBLISHES and EXECUTES the launcher and bake CLI — is + // covered by the content assertions below, which are unaffected by how + // the workflow is triggered. Assert.Contains("portable-launcher:", workflow, StringComparison.Ordinal); Assert.Contains("tests/AcDream.Launcher.Tests/AcDream.Launcher.Tests.csproj", workflow, StringComparison.Ordinal); Assert.Contains("tests/AcDream.Bake.Tests/AcDream.Bake.Tests.csproj", workflow, StringComparison.Ordinal); diff --git a/tests/AcDream.Launcher.Tests/xunit.runner.json b/tests/AcDream.Launcher.Tests/xunit.runner.json new file mode 100644 index 00000000..c3155894 --- /dev/null +++ b/tests/AcDream.Launcher.Tests/xunit.runner.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "parallelizeAssembly": false, + "parallelizeTestCollections": false, + "maxParallelThreads": 1 +} diff --git a/tools/publish-bin.ps1 b/tools/publish-bin.ps1 index d1df1889..918889b4 100644 --- a/tools/publish-bin.ps1 +++ b/tools/publish-bin.ps1 @@ -34,6 +34,7 @@ [CmdletBinding()] param( [string]$Version, + [string]$BaseUrl, [switch]$IncludeLinux, [string]$MinimumLauncherVersion = '0.0.1' ) @@ -61,9 +62,16 @@ foreach ($candidate in @($Version, $MinimumLauncherVersion)) { } } -# Raw-file base for the PUBLIC Gitea repo's dist branch. Must agree with -# ReleaseManifestClient.ProductionManifestUri. -$RawBase = 'https://git.snakedesert.se/erik/acdream/raw/branch/dist/bin' +# Where the manifest says the payloads live. The CI pipeline passes the Gitea +# RELEASE asset base for the tag it is publishing, so payloads live outside git +# entirely; the default keeps the older dist-branch layout working for a manual +# local publish. The manifest itself always stays at the stable dist raw URL +# that ReleaseManifestClient.ProductionManifestUri points at. +$RawBase = if ([string]::IsNullOrWhiteSpace($BaseUrl)) { + 'https://git.snakedesert.se/erik/acdream/raw/branch/dist/bin' +} else { + $BaseUrl.TrimEnd('/') +} $BinRoot = Join-Path $RepoRoot 'bin' $Staging = Join-Path $BinRoot 'payload'