chore(release): prepare v1.1.2 release alignment #8
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: | |
| permissions: | |
| contents: write | |
| jobs: | |
| build: | |
| runs-on: windows-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| tag: ${{ steps.version.outputs.tag }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: "8.0.x" | |
| - name: Resolve version | |
| id: version | |
| shell: pwsh | |
| run: | | |
| $refName = "${{ github.ref_name }}" | |
| if ($refName -match '^v\d+\.\d+\.\d+$') { | |
| $version = $refName.TrimStart('v') | |
| $tag = $refName | |
| } | |
| else { | |
| [xml]$project = Get-Content "ThreadPilot.csproj" | |
| $version = $project.Project.PropertyGroup.Version | |
| if ([string]::IsNullOrWhiteSpace($version)) { | |
| throw "Unable to resolve version from tag or ThreadPilot.csproj" | |
| } | |
| $tag = "v$version" | |
| } | |
| "version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| "tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| - name: Restore | |
| run: dotnet restore "ThreadPilot_1.sln" | |
| - name: Build | |
| run: dotnet build "ThreadPilot_1.sln" --configuration Release --no-restore | |
| - name: Test | |
| run: dotnet test "ThreadPilot_1.sln" --configuration Release --no-build --collect:"XPlat Code Coverage" --results-directory TestResults | |
| - name: Publish self-contained single-file | |
| run: dotnet publish "ThreadPilot.csproj" --configuration Release -p:PublishProfile=WinX64-SingleFile | |
| - name: Ensure bundled power plans in single-file output | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| New-Item -ItemType Directory -Force -Path "artifacts/release/singlefile/Powerplans" | Out-Null | |
| Copy-Item "assets/Powerplans/*" -Destination "artifacts/release/singlefile/Powerplans" -Recurse -Force | |
| - name: Publish ReadyToRun | |
| run: dotnet publish "ThreadPilot.csproj" --configuration Release -p:PublishProfile=WinX64-ReadyToRun | |
| - name: Install Inno Setup | |
| shell: pwsh | |
| run: choco install innosetup --no-progress -y | |
| - name: Build Inno Setup installer | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $version = "${{ steps.version.outputs.version }}" | |
| $sourceDir = (Resolve-Path "artifacts/release/singlefile").Path | |
| & iscc.exe "/DMyAppVersion=$version" "/DMyAppSourceDir=$sourceDir" "Installer/setup.iss" | |
| - name: Prepare signing certificate (optional) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| if ([string]::IsNullOrWhiteSpace($env:WINDOWS_SIGNING_CERT_BASE64) -or [string]::IsNullOrWhiteSpace($env:WINDOWS_SIGNING_CERT_PASSWORD)) | |
| { | |
| Write-Host "Signing secrets not provided via environment variables. Skipping certificate preparation." | |
| exit 0 | |
| } | |
| $certPath = Join-Path $env:RUNNER_TEMP "threadpilot-signing.pfx" | |
| [System.IO.File]::WriteAllBytes($certPath, [System.Convert]::FromBase64String($env:WINDOWS_SIGNING_CERT_BASE64)) | |
| "THREADPILOT_SIGN_CERT_PATH=$certPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| - name: Sign binaries and packages (optional) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| if ([string]::IsNullOrWhiteSpace($env:THREADPILOT_SIGN_CERT_PATH) -or -not (Test-Path $env:THREADPILOT_SIGN_CERT_PATH)) | |
| { | |
| Write-Host "No signing certificate prepared. Skipping signing step." | |
| exit 0 | |
| } | |
| if (-not (Get-Command signtool.exe -ErrorAction SilentlyContinue)) | |
| { | |
| throw "signtool.exe not found on runner; install Windows SDK or disable signing." | |
| } | |
| $timestampUrl = "http://timestamp.digicert.com" | |
| $password = $env:WINDOWS_SIGNING_CERT_PASSWORD | |
| $targets = @() | |
| $targets += Get-ChildItem "artifacts/release/singlefile" -Recurse -File -Include *.exe -ErrorAction SilentlyContinue | |
| $targets += Get-ChildItem "artifacts/release/readytorun" -Recurse -File -Include *.exe -ErrorAction SilentlyContinue | |
| $targets += Get-ChildItem "artifacts/release/installer" -Recurse -File -Include *.exe -ErrorAction SilentlyContinue | |
| foreach ($target in $targets) | |
| { | |
| signtool.exe sign /fd SHA256 /tr $timestampUrl /td SHA256 /f "$env:THREADPILOT_SIGN_CERT_PATH" /p $password "$($target.FullName)" | |
| } | |
| - name: Create release packages | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $version = "${{ steps.version.outputs.tag }}" | |
| New-Item -ItemType Directory -Force -Path "artifacts/release/packages" | Out-Null | |
| New-Item -ItemType Directory -Force -Path "artifacts/release/package-staging" | Out-Null | |
| $sharedUninstall = "artifacts/release/package-staging/uninstall.bat" | |
| $sharedLicense = "artifacts/release/package-staging/LICENSE.md" | |
| $uninstallContent = @' | |
| @echo off | |
| setlocal EnableExtensions | |
| title ThreadPilot Uninstaller | |
| set "APP_DIR=%~dp0" | |
| if "%APP_DIR:~-1%"=="\" set "APP_DIR=%APP_DIR:~0,-1%" | |
| echo ====================================================== | |
| echo ThreadPilot Uninstaller | |
| echo ====================================================== | |
| echo. | |
| echo [1/4] Closing running ThreadPilot processes... | |
| taskkill /IM "ThreadPilot.exe" /F >nul 2>&1 | |
| echo [2/4] Removing startup task and startup registry entry... | |
| schtasks /Delete /TN "ThreadPilot_Startup" /F >nul 2>&1 | |
| reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "ThreadPilot" /f >nul 2>&1 | |
| echo [3/4] Optional user data cleanup... | |
| set "REMOVE_DATA=N" | |
| set /p REMOVE_DATA=Do you want to remove user settings at "%APPDATA%\ThreadPilot"? [y/N]: | |
| if /I "%REMOVE_DATA%"=="Y" ( | |
| if exist "%APPDATA%\ThreadPilot" ( | |
| rd /s /q "%APPDATA%\ThreadPilot" | |
| echo User settings removed. | |
| ) else ( | |
| echo No user settings folder found. | |
| ) | |
| ) else ( | |
| echo User settings were kept. | |
| ) | |
| echo [4/4] Scheduling app folder removal... | |
| start "" /min cmd /c "timeout /t 5 /nobreak >nul & rd /s /q \"%APP_DIR%\"" | |
| echo. | |
| echo Uninstall completed. Remaining files will be removed in a few seconds. | |
| endlocal | |
| exit /b 0 | |
| '@ | |
| Set-Content -LiteralPath $sharedUninstall -Value $uninstallContent -Encoding Ascii | |
| Copy-Item "LICENSE" -Destination $sharedLicense -Force | |
| $singleStage = "artifacts/release/package-staging/singlefile" | |
| $readyToRunStage = "artifacts/release/package-staging/readytorun" | |
| New-Item -ItemType Directory -Force -Path $singleStage | Out-Null | |
| New-Item -ItemType Directory -Force -Path $readyToRunStage | Out-Null | |
| Copy-Item "artifacts/release/singlefile/*" -Destination $singleStage -Recurse -Force | |
| Copy-Item "artifacts/release/readytorun/*" -Destination $readyToRunStage -Recurse -Force | |
| Copy-Item $sharedUninstall -Destination (Join-Path $singleStage "uninstall.bat") -Force | |
| Copy-Item $sharedLicense -Destination (Join-Path $singleStage "LICENSE.md") -Force | |
| Copy-Item $sharedUninstall -Destination (Join-Path $readyToRunStage "uninstall.bat") -Force | |
| Copy-Item $sharedLicense -Destination (Join-Path $readyToRunStage "LICENSE.md") -Force | |
| $singleFileZip = "artifacts/release/packages/ThreadPilot_$version`_singlefile_win-x64.zip" | |
| $readyToRunZip = "artifacts/release/packages/ThreadPilot_$version`_readytorun_win-x64.zip" | |
| Compress-Archive -Path "$singleStage/*" -DestinationPath $singleFileZip -Force | |
| Compress-Archive -Path "$readyToRunStage/*" -DestinationPath $readyToRunZip -Force | |
| - name: Generate checksums | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $hashFile = "artifacts/release/SHA256SUMS.txt" | |
| $releaseFiles = @() | |
| $releaseFiles += Get-ChildItem "artifacts/release/packages" -File -ErrorAction SilentlyContinue | |
| $releaseFiles += Get-ChildItem "artifacts/release/installer/*.exe" -File -ErrorAction SilentlyContinue | |
| $releaseFiles | ForEach-Object { | |
| $hash = Get-FileHash $_.FullName -Algorithm SHA256 | |
| "$($hash.Hash) $($_.Name)" | Out-File -FilePath $hashFile -Append -Encoding utf8 | |
| } | |
| - name: Update winget installer manifest | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $version = "${{ steps.version.outputs.version }}" | |
| $tag = "${{ steps.version.outputs.tag }}" | |
| $installer = Get-ChildItem "artifacts/release/installer/*.exe" -File | Select-Object -First 1 | |
| if (-not $installer) | |
| { | |
| throw "Installer executable not found." | |
| } | |
| $sha = (Get-FileHash $installer.FullName -Algorithm SHA256).Hash.ToUpperInvariant() | |
| $url = "https://github.com/${{ github.repository }}/releases/download/$tag/$($installer.Name)" | |
| $manifestPath = "winget/manifests/p/PrimeBuild/ThreadPilot/$version/PrimeBuild.ThreadPilot.installer.yaml" | |
| if (-not (Test-Path $manifestPath)) | |
| { | |
| throw "Winget installer manifest not found at $manifestPath" | |
| } | |
| $content = Get-Content $manifestPath -Raw | |
| $content = $content.Replace("{{INSTALLER_URL}}", $url).Replace("{{INSTALLER_SHA256}}", $sha) | |
| Set-Content -Path $manifestPath -Value $content -Encoding utf8 | |
| - name: Generate SBOM | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| dotnet tool install --global Microsoft.Sbom.DotNetTool | |
| $env:PATH += ";$env:USERPROFILE\.dotnet\tools" | |
| sbom-tool generate -b ./artifacts -bc . -pn ThreadPilot -pv "${{ steps.version.outputs.version }}" -ps PrimeBuild -nsb https://github.com/PrimeBuild-pc/ThreadPilot | |
| $sbomReleaseDir = "artifacts/release/sbom" | |
| New-Item -ItemType Directory -Force -Path $sbomReleaseDir | Out-Null | |
| $sbomManifest = Get-ChildItem "artifacts" -Recurse -File -Filter "manifest.spdx.json" -ErrorAction SilentlyContinue | | |
| Sort-Object LastWriteTimeUtc -Descending | | |
| Select-Object -First 1 | |
| if (-not $sbomManifest) | |
| { | |
| throw "SBOM manifest not found under artifacts after generation." | |
| } | |
| Copy-Item -LiteralPath $sbomManifest.FullName -Destination (Join-Path $sbomReleaseDir "manifest.spdx.json") -Force | |
| - name: Upload portable artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-portable | |
| path: artifacts/release/singlefile | |
| - name: Upload installer artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-installer | |
| path: artifacts/release/installer | |
| - name: Upload packages artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-packages | |
| path: artifacts/release/packages | |
| - name: Upload checksums artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-checksums | |
| path: artifacts/release/SHA256SUMS.txt | |
| - name: Upload winget manifests | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: winget-manifests | |
| path: winget/manifests/p/PrimeBuild/ThreadPilot/${{ steps.version.outputs.version }} | |
| - name: Upload SBOM | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sbom | |
| path: artifacts/release/sbom/manifest.spdx.json | |
| if-no-files-found: error | |
| smoke-test: | |
| runs-on: windows-latest | |
| needs: build | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: release-portable | |
| path: smoke | |
| - name: Smoke test | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $exe = Get-ChildItem "smoke" -Recurse -Filter "ThreadPilot.exe" -File | Select-Object -First 1 | |
| if (-not $exe) | |
| { | |
| throw "ThreadPilot.exe not found in smoke-test artifact." | |
| } | |
| $process = Start-Process -FilePath $exe.FullName -ArgumentList "--smoke-test" -Wait -PassThru | |
| if ($process.ExitCode -ne 0) | |
| { | |
| throw "Smoke test failed with exit code $($process.ExitCode)." | |
| } | |
| release: | |
| runs-on: ubuntu-latest | |
| needs: | |
| - build | |
| - smoke-test | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download installer artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-installer | |
| path: release-assets/installer | |
| - name: Download packages artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-packages | |
| path: release-assets/packages | |
| - name: Download checksums artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-checksums | |
| path: release-assets | |
| - name: Download winget manifests | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: winget-manifests | |
| path: winget-manifests | |
| - name: Download SBOM artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: sbom | |
| path: sbom | |
| - name: Validate release assets | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| compgen -G "release-assets/installer/*.exe" > /dev/null || { echo "Missing installer executable in release-assets/installer"; exit 1; } | |
| compgen -G "release-assets/packages/*.zip" > /dev/null || { echo "Missing release package zip in release-assets/packages"; exit 1; } | |
| test -f "release-assets/SHA256SUMS.txt" || { echo "Missing release checksums file"; exit 1; } | |
| find "winget-manifests" -type f -name "*.yaml" | grep -q . || { echo "Missing winget manifest yaml files"; exit 1; } | |
| test -f "sbom/manifest.spdx.json" || { echo "Missing SBOM manifest"; exit 1; } | |
| - name: Generate changelog | |
| id: git-cliff | |
| uses: orhun/git-cliff-action@v3 | |
| with: | |
| config: cliff.toml | |
| args: --latest | |
| - name: Contributors file check (manual maintenance mode) | |
| shell: pwsh | |
| run: | | |
| if (-not (Test-Path "CONTRIBUTORS.md")) { | |
| throw "CONTRIBUTORS.md not found." | |
| } | |
| Write-Host "CONTRIBUTORS.md is maintained manually in this workflow." | |
| - name: Publish GitHub release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.build.outputs.tag }} | |
| name: ThreadPilot ${{ needs.build.outputs.tag }} | |
| body: ${{ steps.git-cliff.outputs.content }} | |
| files: | | |
| release-assets/installer/*.exe | |
| release-assets/packages/*.zip | |
| release-assets/SHA256SUMS.txt | |
| winget-manifests/**/*.yaml | |
| sbom/manifest.spdx.json | |
| generate_release_notes: true |