fix(ci): remove --no-build from release.yml test steps #157
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: ["v*"] | |
| workflow_dispatch: | |
| inputs: | |
| tag_name: | |
| description: "Tag to create the release for (e.g. v3.7.3). Required for manual re-runs when the original tag push failed." | |
| required: false | |
| type: string | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| jobs: | |
| build-and-release: | |
| runs-on: windows-latest | |
| timeout-minutes: 90 | |
| env: | |
| # Prevent MSBuild worker nodes from persisting between steps and holding | |
| # file handles on *.cache files, which would cause MSB3492 errors. | |
| MSBUILDDISABLENODEREUSE: 1 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Cache NuGet | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', 'Directory.Packages.props', 'global.json') }} | |
| restore-keys: ${{ runner.os }}-nuget- | |
| - name: Build Release | |
| run: dotnet build RegiLattice.sln -c Release | |
| # Run test projects individually (not as solution) to guarantee sequential | |
| # execution and prevent shared-file races between Core.Tests and GUI.Tests. | |
| # See tests/.runsettings — MaxCpuCount=1 note. | |
| - name: Test (Core) | |
| run: >- | |
| dotnet test tests/RegiLattice.Core.Tests/RegiLattice.Core.Tests.csproj | |
| -c Release --no-restore | |
| --settings tests/.runsettings | |
| --blame-hang-timeout 30s | |
| --logger "console;verbosity=minimal" | |
| - name: Test (CLI) | |
| run: >- | |
| dotnet test tests/RegiLattice.CLI.Tests/RegiLattice.CLI.Tests.csproj | |
| -c Release --no-restore | |
| --settings tests/.runsettings | |
| --blame-hang-timeout 30s | |
| --logger "console;verbosity=minimal" | |
| - name: Test (GUI) | |
| run: >- | |
| dotnet test tests/RegiLattice.GUI.Tests/RegiLattice.GUI.Tests.csproj | |
| -c Release --no-restore | |
| --settings tests/.runsettings | |
| --blame-hang-timeout 30s | |
| --logger "console;verbosity=minimal" | |
| - name: Publish GUI (self-contained) | |
| run: >- | |
| dotnet publish src/RegiLattice.GUI/RegiLattice.GUI.csproj | |
| -c Release -r win-x64 --self-contained true | |
| -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true | |
| -o publish/gui | |
| - name: Publish CLI (self-contained) | |
| run: >- | |
| dotnet publish src/RegiLattice.CLI/RegiLattice.CLI.csproj | |
| -c Release -r win-x64 --self-contained true | |
| -p:PublishSingleFile=true | |
| -o publish/cli | |
| - name: Install WiX toolset | |
| continue-on-error: true | |
| shell: pwsh | |
| run: | | |
| dotnet tool install --global wix --version 6.0.2 | |
| if ($LASTEXITCODE -ne 0) { dotnet tool update --global wix --version 6.0.2 } | |
| - name: Build MSI installer | |
| continue-on-error: true | |
| run: dotnet build installer/RegiLattice.Installer.wixproj -c Release | |
| - name: Generate MSIX assets | |
| continue-on-error: true | |
| shell: pwsh | |
| run: | | |
| Add-Type -AssemblyName System.Drawing | |
| $dir = 'publish/msix-staging/Assets' | |
| New-Item -ItemType Directory -Force $dir | Out-Null | |
| $bg = [System.Drawing.Color]::FromArgb(30, 30, 46) # Catppuccin Mocha | |
| @( | |
| @{ n='Square44x44Logo.png'; w=44; h=44 }, | |
| @{ n='Square150x150Logo.png'; w=150; h=150 }, | |
| @{ n='Wide310x150Logo.png'; w=310; h=150 }, | |
| @{ n='StoreLogo.png'; w=50; h=50 }, | |
| @{ n='SplashScreen.png'; w=620; h=300 } | |
| ) | ForEach-Object { | |
| $bmp = New-Object System.Drawing.Bitmap $_.w, $_.h | |
| $g = [System.Drawing.Graphics]::FromImage($bmp) | |
| $g.Clear($bg); $g.Dispose() | |
| $bmp.Save("$dir/$($_.n)", [System.Drawing.Imaging.ImageFormat]::Png) | |
| $bmp.Dispose() | |
| } | |
| Write-Host 'Generated 5 MSIX visual assets (Catppuccin Mocha #1e1e2e)' | |
| - name: Stage MSIX content | |
| continue-on-error: true | |
| shell: pwsh | |
| run: | | |
| $tag = '${{ inputs.tag_name || github.ref_name }}' | |
| $ver = $tag.TrimStart('v') + '.0' # MSIX requires 4-part version | |
| $root = 'publish/msix-staging' | |
| Copy-Item publish/gui/RegiLattice.GUI.exe $root/ | |
| $xml = (Get-Content installer/AppxManifest.xml -Raw) ` | |
| -replace '(?<=<Identity[^>]+Version=")[^"]+', $ver | |
| [IO.File]::WriteAllText("$root/AppxManifest.xml", $xml, [Text.Encoding]::UTF8) | |
| Write-Host "MSIX content staged — version $ver" | |
| - name: Build MSIX | |
| continue-on-error: true | |
| shell: pwsh | |
| run: | | |
| $mxp = Get-ChildItem 'C:\Program Files (x86)\Windows Kits\10\bin' -Recurse ` | |
| -Filter makeappx.exe -ErrorAction SilentlyContinue | | |
| Where-Object FullName -Match x64 | | |
| Sort-Object FullName -Descending | | |
| Select-Object -First 1 -ExpandProperty FullName | |
| if (-not $mxp) { Write-Error 'makeappx.exe not found on runner'; exit 1 } | |
| $tag = '${{ inputs.tag_name || github.ref_name }}' | |
| $out = "publish/RegiLattice-$tag-win-x64.msix" | |
| & $mxp pack /d publish/msix-staging /p $out /nv | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| Write-Host "MSIX: $out ($([math]::Round((Get-Item $out).Length / 1MB, 1)) MB)" | |
| - name: Rename artifacts for release | |
| shell: pwsh | |
| run: | | |
| $tag = '${{ inputs.tag_name || github.ref_name }}' | |
| # GUI EXE: RegiLattice-vX.Y.Z-win-x64.exe | |
| Copy-Item 'publish/gui/RegiLattice.GUI.exe' "publish/RegiLattice-$tag-win-x64.exe" | |
| Write-Host "GUI EXE : RegiLattice-$tag-win-x64.exe" | |
| # CLI EXE: RegiLatticeCLI-vX.Y.Z-win-x64.exe | |
| Copy-Item 'publish/cli/RegiLattice.exe' "publish/RegiLatticeCLI-$tag-win-x64.exe" | |
| Write-Host "CLI EXE : RegiLatticeCLI-$tag-win-x64.exe" | |
| # MSI (optional — copy from WiX build output if present) | |
| # WiX SDK with InstallerPlatform=x64 outputs to installer/bin/x64/Release/ | |
| $msi = Get-ChildItem 'installer/bin' -Recurse -Filter '*.msi' -ErrorAction SilentlyContinue | Select-Object -First 1 | |
| if ($msi) { | |
| Copy-Item $msi.FullName "publish/RegiLattice-$tag-win-x64.msi" | |
| Write-Host "MSI : RegiLattice-$tag-win-x64.msi" | |
| } else { | |
| Write-Host "MSI : not built (WiX optional — skipped)" | |
| } | |
| - name: Validate required release artifacts | |
| shell: pwsh | |
| run: | | |
| $tag = '${{ inputs.tag_name || github.ref_name }}' | |
| # Mandatory: GUI and CLI EXEs must exist — fail the release if either is missing. | |
| $missing = @() | |
| if (-not (Test-Path "publish/RegiLattice-$tag-win-x64.exe")) { $missing += "RegiLattice-$tag-win-x64.exe" } | |
| if (-not (Test-Path "publish/RegiLatticeCLI-$tag-win-x64.exe")) { $missing += "RegiLatticeCLI-$tag-win-x64.exe" } | |
| if ($missing.Count -gt 0) { | |
| Write-Error "Required release artifacts missing — aborting release:`n $($missing -join "`n ")" | |
| exit 1 | |
| } | |
| $guiMb = [math]::Round((Get-Item "publish/RegiLattice-$tag-win-x64.exe").Length / 1MB, 1) | |
| $cliMb = [math]::Round((Get-Item "publish/RegiLatticeCLI-$tag-win-x64.exe").Length / 1MB, 1) | |
| Write-Host "Required artifacts present:" | |
| Write-Host " GUI exe : RegiLattice-$tag-win-x64.exe ($guiMb MB)" | |
| Write-Host " CLI exe : RegiLatticeCLI-$tag-win-x64.exe ($cliMb MB)" | |
| # Report optional artifacts (MSI / MSIX) | |
| $msi = Get-ChildItem 'publish' -Filter '*-win-x64.msi' -ErrorAction SilentlyContinue | Select-Object -First 1 | |
| $msix = Get-ChildItem 'publish' -Filter '*.msix' -ErrorAction SilentlyContinue | Select-Object -First 1 | |
| if ($msi) { Write-Host " MSI : $($msi.Name) ($([math]::Round($msi.Length/1MB,1)) MB)" } | |
| else { Write-Host " MSI : not built (WiX optional — skipped)" } | |
| if ($msix) { Write-Host " MSIX : $($msix.Name) ($([math]::Round($msix.Length/1MB,1)) MB)" } | |
| else { Write-Host " MSIX : not built (makeappx optional — skipped)" } | |
| - name: Generate checksums | |
| shell: pwsh | |
| run: | | |
| $all = @() | |
| $all += Get-ChildItem "publish" -File -Filter "*-win-x64.exe" -ErrorAction SilentlyContinue | |
| $all += Get-ChildItem "publish" -File -Filter "*-win-x64.msi" -ErrorAction SilentlyContinue | |
| $all += Get-ChildItem "publish" -File -Filter "*.msix" -ErrorAction SilentlyContinue | |
| $all += Get-ChildItem "publish" -File -Filter "*.zip" -ErrorAction SilentlyContinue | |
| $all += Get-ChildItem "publish" -File -Filter "*.nupkg" -ErrorAction SilentlyContinue | |
| $hashes = foreach ($f in $all) { Get-FileHash $f.FullName -Algorithm SHA256 } | |
| $hashes | ForEach-Object { | |
| $rel = $_.Path -replace [regex]::Escape((Get-Location).Path + '\'), '' -replace '\\', '/' | |
| "{0} {1}" -f $_.Hash.ToLowerInvariant(), $rel | |
| } | Set-Content publish/SHA256SUMS.txt | |
| - name: Build Chocolatey package | |
| continue-on-error: true | |
| shell: pwsh | |
| run: | | |
| $tag = '${{ inputs.tag_name || github.ref_name }}' | |
| $ver = $tag.TrimStart('v') | |
| # Inject real download URL + SHA-256 into chocolateyInstall.ps1 | |
| $guiSha = (Get-FileHash "publish/gui/RegiLattice.GUI.exe" -Algorithm SHA256).Hash.ToLowerInvariant() | |
| $cliSha = (Get-FileHash "publish/cli/RegiLattice.exe" -Algorithm SHA256).Hash.ToLowerInvariant() | |
| # For Chocolatey we package a zip of both EXEs | |
| $zipName = "RegiLattice-$tag-win-x64.zip" | |
| $zipPath = "publish/$zipName" | |
| Compress-Archive -Path publish/gui/RegiLattice.GUI.exe, publish/cli/RegiLattice.exe ` | |
| -DestinationPath $zipPath -Force | |
| $zipSha = (Get-FileHash $zipPath -Algorithm SHA256).Hash.ToLowerInvariant() | |
| $zipUrl = "https://github.com/RajwanYair/RegiLattice/releases/download/$tag/$zipName" | |
| # Patch the install script and nuspec | |
| $installPs1 = Get-Content "chocolatey/tools/chocolateyInstall.ps1" -Raw | |
| $installPs1 = $installPs1 -replace 'RELEASE_URL_PLACEHOLDER', $zipUrl | |
| $installPs1 = $installPs1 -replace 'RELEASE_SHA256_PLACEHOLDER', $zipSha | |
| [IO.File]::WriteAllText("chocolatey/tools/chocolateyInstall.ps1", $installPs1, [Text.Encoding]::UTF8) | |
| $nuspec = Get-Content "chocolatey/regilattice.nuspec" -Raw | |
| $nuspec = $nuspec -replace '<version>[^<]+</version>', "<version>$ver</version>" | |
| [IO.File]::WriteAllText("chocolatey/regilattice.nuspec", $nuspec, [Text.Encoding]::UTF8) | |
| choco pack chocolatey/regilattice.nuspec --output-directory publish/ | |
| Write-Host "Chocolatey package built: publish/regilattice.$ver.nupkg" | |
| - name: Push to Chocolatey Community Repository | |
| continue-on-error: true | |
| if: env.CHOCOLATEY_API_KEY != '' | |
| env: | |
| CHOCOLATEY_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }} | |
| shell: pwsh | |
| run: | | |
| $nupkg = Get-ChildItem "publish" -Filter "regilattice.*.nupkg" | Select-Object -First 1 | |
| if (-not $nupkg) { Write-Warning 'No .nupkg found — skipping push.'; exit 0 } | |
| choco push $nupkg.FullName --source https://push.chocolatey.org/ --api-key $env:CHOCOLATEY_API_KEY | |
| Write-Host "Pushed $($nupkg.Name) to Chocolatey" | |
| - name: Extract release notes from CHANGELOG | |
| id: release-notes | |
| shell: pwsh | |
| run: | | |
| $tag = '${{ inputs.tag_name || github.ref_name }}' | |
| $ver = $tag.TrimStart('v') | |
| $cl = Get-Content 'docs/CHANGELOG.md' -Raw | |
| # Extract section between ## [X.Y.Z] and the next ## [ | |
| if ($cl -match "(?s)## \[$([regex]::Escape($ver))\][^\n]*\n(.*?)(?=\n## \[|$)") { | |
| $notes = $Matches[1].Trim() | |
| } else { | |
| $notes = "Release $tag — see [CHANGELOG](https://github.com/RajwanYair/RegiLattice/blob/main/docs/CHANGELOG.md) for details." | |
| } | |
| # Write to file for gh release --notes-file | |
| [IO.File]::WriteAllText('publish/RELEASE_NOTES.md', $notes, [Text.Encoding]::UTF8) | |
| Write-Host "Release notes extracted ($($notes.Length) chars)" | |
| - name: Create GitHub Release | |
| # Uses the pre-installed gh CLI (no Node.js action — avoids node20 deprecation warnings). | |
| # On re-runs (workflow_dispatch when release already exists), uploads artifacts only. | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| $tag = '${{ inputs.tag_name || github.ref_name }}' | |
| $files = [System.Collections.Generic.List[string]]::new() | |
| # Required artifacts (versioned names) | |
| if (Test-Path "publish/RegiLattice-$tag-win-x64.exe") { $files.Add("publish/RegiLattice-$tag-win-x64.exe") } | |
| if (Test-Path "publish/RegiLatticeCLI-$tag-win-x64.exe") { $files.Add("publish/RegiLatticeCLI-$tag-win-x64.exe") } | |
| if (Test-Path 'publish/SHA256SUMS.txt') { $files.Add('publish/SHA256SUMS.txt') } | |
| # Optional artifacts (all versioned — continue-on-error steps may not produce them) | |
| Get-ChildItem 'publish' -Filter '*.zip' -ErrorAction SilentlyContinue | ForEach-Object { $files.Add($_.FullName) } | |
| Get-ChildItem 'publish' -Filter '*.nupkg' -ErrorAction SilentlyContinue | ForEach-Object { $files.Add($_.FullName) } | |
| Get-ChildItem 'publish' -Filter '*.msix' -ErrorAction SilentlyContinue | ForEach-Object { $files.Add($_.FullName) } | |
| Get-ChildItem 'publish' -Filter '*-win-x64.msi' -ErrorAction SilentlyContinue | ForEach-Object { $files.Add($_.FullName) } | |
| Write-Host "Release $tag — $($files.Count) artifact(s):" | |
| $files | ForEach-Object { Write-Host " $_" } | |
| # Create release; if it already exists (workflow_dispatch re-run), upload files only | |
| $null = gh release view $tag --json tagName 2>&1 | |
| if ($LASTEXITCODE -eq 0) { | |
| Write-Host "Release $tag already exists — uploading artifacts only" | |
| if ($files.Count -gt 0) { gh release upload $tag @files --clobber } | |
| } else { | |
| gh release create $tag @files --notes-file publish/RELEASE_NOTES.md --title $tag | |
| } | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| Write-Host "Release $tag published successfully" | |
| - name: Verify release published cleanly | |
| # Confirms the release exists on GitHub with the mandatory artifacts. | |
| # Fails the workflow (and triggers notify-failure.yml → GH Issue) if | |
| # the GUI or CLI EXE is missing from the release asset list. | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| $tag = '${{ inputs.tag_name || github.ref_name }}' | |
| # Retry up to 3 times — GitHub CDN may lag a few seconds after upload | |
| $attempts = 0 | |
| do { | |
| Start-Sleep -Seconds 5 | |
| $assets = gh release view $tag --json assets --jq '.assets[].name' 2>&1 | |
| $attempts++ | |
| } while ($LASTEXITCODE -ne 0 -and $attempts -lt 3) | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "Release $tag not found on GitHub after $attempts attempt(s)" | |
| exit 1 | |
| } | |
| $assetList = $assets -split '\n' | Where-Object { $_ -ne '' } | |
| Write-Host "Release $tag assets ($($assetList.Count)):" | |
| $assetList | ForEach-Object { Write-Host " $_" } | |
| $missing = @() | |
| if ($assetList -notcontains "RegiLattice-$tag-win-x64.exe") { $missing += "RegiLattice-$tag-win-x64.exe" } | |
| if ($assetList -notcontains "RegiLatticeCLI-$tag-win-x64.exe") { $missing += "RegiLatticeCLI-$tag-win-x64.exe" } | |
| if ($missing.Count -gt 0) { | |
| Write-Error "Release $tag is MISSING required assets: $($missing -join ', ')" | |
| exit 1 | |
| } | |
| Write-Host "\u2705 Release $tag verified — all required assets present" |