Fix git-cliff installation in release workflow #10
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: | |
| dry_run_publish: | |
| description: "Build and validate release without external channel publication" | |
| required: false | |
| default: "false" | |
| 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 "Tests/ThreadPilot.Core.Tests/ThreadPilot.Core.Tests.csproj" --configuration Release --no-restore --verbosity normal --collect:"XPlat Code Coverage" --settings "Tests/ThreadPilot.Core.Tests/coverlet.runsettings" --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: Generate winget manifests | |
| 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)" | |
| ./build/generate-winget-manifests.ps1 ` | |
| -Version $version ` | |
| -Tag $tag ` | |
| -InstallerUrl $url ` | |
| -InstallerSha256 $sha ` | |
| -OutputRoot "winget-manifests" | |
| - 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 | |
| - 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: Install git-cliff | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: [email protected] | |
| - name: Generate changelog | |
| id: git-cliff | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| git-cliff --config cliff.toml --latest > release-notes.md | |
| { | |
| echo "content<<EOF" | |
| cat release-notes.md | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - 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 | |
| publish-winget: | |
| runs-on: windows-latest | |
| needs: | |
| - build | |
| - release | |
| if: needs.release.result == 'success' | |
| steps: | |
| - name: Resolve winget publish policy | |
| id: policy | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $dryRun = "${{ github.event_name }}" -eq "workflow_dispatch" -and "${{ github.event.inputs.dry_run_publish }}" -eq "true" | |
| "dry_run=$($dryRun.ToString().ToLowerInvariant())" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| - name: Check winget publish policy | |
| id: gate | |
| shell: pwsh | |
| env: | |
| WINGET_GITHUB_TOKEN: ${{ secrets.WINGET_GITHUB_TOKEN }} | |
| WINGET_FORK_OWNER: ${{ secrets.WINGET_FORK_OWNER }} | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $logsDir = "release-channel-logs" | |
| New-Item -ItemType Directory -Force -Path $logsDir | Out-Null | |
| $logPath = Join-Path $logsDir "winget-submit-log.txt" | |
| $dryRun = "${{ steps.policy.outputs.dry_run }}" -eq "true" | |
| if ($dryRun) | |
| { | |
| $summary = @( | |
| "## winget publish", | |
| "- attempted: false", | |
| "- version: ${{ needs.build.outputs.version }}", | |
| "- result: dry-run", | |
| "- reason: workflow_dispatch requested dry_run_publish=true" | |
| ) | |
| $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append | |
| "Dry run requested. Winget submission intentionally skipped." | Set-Content -Path $logPath -Encoding utf8 | |
| "enabled=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| exit 0 | |
| } | |
| if ([string]::IsNullOrWhiteSpace($env:WINGET_GITHUB_TOKEN) -or [string]::IsNullOrWhiteSpace($env:WINGET_FORK_OWNER)) | |
| { | |
| $message = "Winget publish secrets are missing. Refusing to report a green public release without a submission attempt." | |
| $summary = @( | |
| "## winget publish", | |
| "- attempted: false", | |
| "- version: ${{ needs.build.outputs.version }}", | |
| "- result: failed-policy", | |
| "- reason: missing WINGET_GITHUB_TOKEN and/or WINGET_FORK_OWNER" | |
| ) | |
| $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append | |
| $message | Set-Content -Path $logPath -Encoding utf8 | |
| throw $message | |
| } | |
| "enabled=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| - name: Checkout | |
| if: steps.gate.outputs.enabled == 'true' | |
| uses: actions/checkout@v4 | |
| - name: Download winget manifests | |
| if: steps.gate.outputs.enabled == 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: winget-manifests | |
| path: winget-manifests | |
| - name: Submit winget PR | |
| if: steps.gate.outputs.enabled == 'true' | |
| shell: pwsh | |
| env: | |
| WINGET_GITHUB_TOKEN: ${{ secrets.WINGET_GITHUB_TOKEN }} | |
| WINGET_FORK_OWNER: ${{ secrets.WINGET_FORK_OWNER }} | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $logsDir = "release-channel-logs" | |
| New-Item -ItemType Directory -Force -Path $logsDir | Out-Null | |
| $logPath = Join-Path $logsDir "winget-submit-log.txt" | |
| try | |
| { | |
| & ./build/submit-winget-pr.ps1 ` | |
| -Version "${{ needs.build.outputs.version }}" ` | |
| -ManifestSourcePath "winget-manifests" ` | |
| -ForkOwner "$env:WINGET_FORK_OWNER" ` | |
| -RepositoryOwner "PrimeBuild" ` | |
| -PackageIdentifier "PrimeBuild.ThreadPilot" ` | |
| -GithubToken "$env:WINGET_GITHUB_TOKEN" *>&1 | Tee-Object -FilePath $logPath | |
| if ($LASTEXITCODE -ne 0) | |
| { | |
| throw "submit-winget-pr.ps1 exited with code $LASTEXITCODE" | |
| } | |
| @( | |
| "## winget publish", | |
| "- attempted: true", | |
| "- version: ${{ needs.build.outputs.version }}", | |
| "- result: submitted" | |
| ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append | |
| } | |
| catch | |
| { | |
| @( | |
| "## winget publish", | |
| "- attempted: true", | |
| "- version: ${{ needs.build.outputs.version }}", | |
| "- result: failed" | |
| ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append | |
| throw | |
| } | |
| - name: Upload winget publish logs | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: winget-publish-logs | |
| path: release-channel-logs/winget-submit-log.txt | |
| if-no-files-found: error | |
| publish-chocolatey: | |
| runs-on: windows-latest | |
| needs: | |
| - build | |
| - release | |
| if: needs.release.result == 'success' | |
| steps: | |
| - name: Resolve chocolatey publish policy | |
| id: policy | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $dryRun = "${{ github.event_name }}" -eq "workflow_dispatch" -and "${{ github.event.inputs.dry_run_publish }}" -eq "true" | |
| "dry_run=$($dryRun.ToString().ToLowerInvariant())" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| - name: Check chocolatey publish policy | |
| id: gate | |
| shell: pwsh | |
| env: | |
| CHOCOLATEY_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }} | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $logsDir = "release-channel-logs" | |
| New-Item -ItemType Directory -Force -Path $logsDir | Out-Null | |
| $packLogPath = Join-Path $logsDir "choco-pack-log.txt" | |
| $pushLogPath = Join-Path $logsDir "choco-push-log.txt" | |
| $dryRun = "${{ steps.policy.outputs.dry_run }}" -eq "true" | |
| if ($dryRun) | |
| { | |
| $summary = @( | |
| "## chocolatey publish", | |
| "- attempted: false", | |
| "- version: ${{ needs.build.outputs.version }}", | |
| "- result: dry-run", | |
| "- reason: workflow_dispatch requested dry_run_publish=true" | |
| ) | |
| $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append | |
| "Dry run requested. Chocolatey pack validation and push intentionally skipped." | Set-Content -Path $packLogPath -Encoding utf8 | |
| "Dry run requested. Chocolatey push intentionally skipped." | Set-Content -Path $pushLogPath -Encoding utf8 | |
| "enabled=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| exit 0 | |
| } | |
| if ([string]::IsNullOrWhiteSpace($env:CHOCOLATEY_API_KEY)) | |
| { | |
| $message = "CHOCOLATEY_API_KEY is missing. Refusing to report a green public release without a package publish attempt." | |
| $summary = @( | |
| "## chocolatey publish", | |
| "- attempted: false", | |
| "- version: ${{ needs.build.outputs.version }}", | |
| "- result: failed-policy", | |
| "- reason: missing CHOCOLATEY_API_KEY" | |
| ) | |
| $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append | |
| $message | Set-Content -Path $packLogPath -Encoding utf8 | |
| $message | Set-Content -Path $pushLogPath -Encoding utf8 | |
| throw $message | |
| } | |
| "enabled=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| - name: Checkout | |
| if: steps.gate.outputs.enabled == 'true' | |
| uses: actions/checkout@v4 | |
| - name: Download installer artifact | |
| if: steps.gate.outputs.enabled == 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-installer | |
| path: release-assets/installer | |
| - name: Validate chocolatey package | |
| if: steps.gate.outputs.enabled == 'true' | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $logsDir = (Resolve-Path "release-channel-logs").Path | |
| $packLogPath = Join-Path $logsDir "choco-pack-log.txt" | |
| $artifactRoot = "release-channel-artifacts/chocolatey" | |
| New-Item -ItemType Directory -Force -Path $artifactRoot | Out-Null | |
| $installer = Get-ChildItem "release-assets/installer/*.exe" -File | Select-Object -First 1 | |
| if (-not $installer) | |
| { | |
| throw "Installer executable not found for Chocolatey validation." | |
| } | |
| & ./build/publish-chocolatey.ps1 ` | |
| -Version "${{ needs.build.outputs.version }}" ` | |
| -Tag "${{ needs.build.outputs.tag }}" ` | |
| -InstallerPath $installer.FullName ` | |
| -DryRun ` | |
| -PackageOutputDirectory $artifactRoot ` | |
| -MetadataOutputPath (Join-Path $artifactRoot "chocolatey-package-metadata.json") *>&1 | Tee-Object -FilePath $packLogPath | |
| if ($LASTEXITCODE -ne 0) | |
| { | |
| throw "Chocolatey dry-run validation failed with exit code $LASTEXITCODE" | |
| } | |
| - name: Publish chocolatey package | |
| if: steps.gate.outputs.enabled == 'true' | |
| shell: pwsh | |
| env: | |
| CHOCOLATEY_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }} | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $logsDir = (Resolve-Path "release-channel-logs").Path | |
| $pushLogPath = Join-Path $logsDir "choco-push-log.txt" | |
| $artifactRoot = "release-channel-artifacts/chocolatey" | |
| New-Item -ItemType Directory -Force -Path $artifactRoot | Out-Null | |
| $installer = Get-ChildItem "release-assets/installer/*.exe" -File | Select-Object -First 1 | |
| if (-not $installer) | |
| { | |
| throw "Installer executable not found for Chocolatey publish." | |
| } | |
| try | |
| { | |
| & ./build/publish-chocolatey.ps1 ` | |
| -Version "${{ needs.build.outputs.version }}" ` | |
| -Tag "${{ needs.build.outputs.tag }}" ` | |
| -InstallerPath $installer.FullName ` | |
| -ApiKey "$env:CHOCOLATEY_API_KEY" ` | |
| -PackageOutputDirectory $artifactRoot ` | |
| -MetadataOutputPath (Join-Path $artifactRoot "chocolatey-publish-metadata.json") *>&1 | Tee-Object -FilePath $pushLogPath | |
| if ($LASTEXITCODE -ne 0) | |
| { | |
| throw "publish-chocolatey.ps1 exited with code $LASTEXITCODE" | |
| } | |
| @( | |
| "## chocolatey publish", | |
| "- attempted: true", | |
| "- version: ${{ needs.build.outputs.version }}", | |
| "- result: published" | |
| ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append | |
| } | |
| catch | |
| { | |
| @( | |
| "## chocolatey publish", | |
| "- attempted: true", | |
| "- version: ${{ needs.build.outputs.version }}", | |
| "- result: failed" | |
| ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append | |
| throw | |
| } | |
| - name: Upload chocolatey publish logs | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: chocolatey-publish-logs | |
| path: | | |
| release-channel-logs/choco-pack-log.txt | |
| release-channel-logs/choco-push-log.txt | |
| release-channel-artifacts/chocolatey/*.nupkg | |
| release-channel-artifacts/chocolatey/*.json | |
| if-no-files-found: error |