Skip to content

fix(ci): remove --no-build from release.yml test steps #157

fix(ci): remove --no-build from release.yml test steps

fix(ci): remove --no-build from release.yml test steps #157

Workflow file for this run

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"