55 tags :
66 - " v*.*.*"
77 workflow_dispatch :
8+ inputs :
9+ dry_run_publish :
10+ description : " Build and validate release without external channel publication"
11+ required : false
12+ default : " false"
813
914permissions :
1015 contents : write
5661 run : dotnet build "ThreadPilot_1.sln" --configuration Release --no-restore
5762
5863 - name : Test
59- run : dotnet test "ThreadPilot_1.sln " --configuration Release --no-build --collect:"XPlat Code Coverage" --results-directory TestResults
64+ 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
6065
6166 - name : Publish self-contained single-file
6267 run : dotnet publish "ThreadPilot.csproj" --configuration Release -p:PublishProfile=WinX64-SingleFile
@@ -216,7 +221,7 @@ jobs:
216221 "$($hash.Hash) $($_.Name)" | Out-File -FilePath $hashFile -Append -Encoding utf8
217222 }
218223
219- - name : Update winget installer manifest
224+ - name : Generate winget manifests
220225 shell : pwsh
221226 run : |
222227 $ErrorActionPreference = "Stop"
@@ -230,16 +235,13 @@ jobs:
230235
231236 $sha = (Get-FileHash $installer.FullName -Algorithm SHA256).Hash.ToUpperInvariant()
232237 $url = "https://github.com/${{ github.repository }}/releases/download/$tag/$($installer.Name)"
233- $manifestPath = "winget/manifests/p/PrimeBuild/ThreadPilot/$version/PrimeBuild.ThreadPilot.installer.yaml"
234238
235- if (-not (Test-Path $manifestPath))
236- {
237- throw "Winget installer manifest not found at $manifestPath"
238- }
239-
240- $content = Get-Content $manifestPath -Raw
241- $content = $content.Replace("{{INSTALLER_URL}}", $url).Replace("{{INSTALLER_SHA256}}", $sha)
242- Set-Content -Path $manifestPath -Value $content -Encoding utf8
239+ ./build/generate-winget-manifests.ps1 `
240+ -Version $version `
241+ -Tag $tag `
242+ -InstallerUrl $url `
243+ -InstallerSha256 $sha `
244+ -OutputRoot "winget-manifests"
243245
244246 - name : Generate SBOM
245247 shell : pwsh
@@ -291,7 +293,7 @@ jobs:
291293 uses : actions/upload-artifact@v4
292294 with :
293295 name : winget-manifests
294- path : winget/ manifests/p/PrimeBuild/ThreadPilot/${{ steps.version.outputs.version }}
296+ path : winget- manifests
295297
296298 - name : Upload SBOM
297299 uses : actions/upload-artifact@v4
@@ -452,20 +454,59 @@ jobs:
452454 if : needs.release.result == 'success'
453455
454456 steps :
455- - name : Check winget publish secrets
457+ - name : Resolve winget publish policy
458+ id : policy
459+ shell : pwsh
460+ run : |
461+ $ErrorActionPreference = "Stop"
462+ $dryRun = "${{ github.event_name }}" -eq "workflow_dispatch" -and "${{ github.event.inputs.dry_run_publish }}" -eq "true"
463+ "dry_run=$($dryRun.ToString().ToLowerInvariant())" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
464+
465+ - name : Check winget publish policy
456466 id : gate
457467 shell : pwsh
458468 env :
459469 WINGET_GITHUB_TOKEN : ${{ secrets.WINGET_GITHUB_TOKEN }}
460470 WINGET_FORK_OWNER : ${{ secrets.WINGET_FORK_OWNER }}
461471 run : |
462- if ([string]::IsNullOrWhiteSpace($env:WINGET_GITHUB_TOKEN) -or [string]::IsNullOrWhiteSpace($env:WINGET_FORK_OWNER))
472+ $ErrorActionPreference = "Stop"
473+ $logsDir = "release-channel-logs"
474+ New-Item -ItemType Directory -Force -Path $logsDir | Out-Null
475+ $logPath = Join-Path $logsDir "winget-submit-log.txt"
476+ $dryRun = "${{ steps.policy.outputs.dry_run }}" -eq "true"
477+
478+ if ($dryRun)
463479 {
464- Write-Host "Winget publish secrets missing. Skipping publish-winget job."
480+ $summary = @(
481+ "## winget publish",
482+ "- attempted: false",
483+ "- version: ${{ needs.build.outputs.version }}",
484+ "- result: dry-run",
485+ "- reason: workflow_dispatch requested dry_run_publish=true"
486+ )
487+
488+ $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
489+ "Dry run requested. Winget submission intentionally skipped." | Set-Content -Path $logPath -Encoding utf8
465490 "enabled=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
466491 exit 0
467492 }
468493
494+ if ([string]::IsNullOrWhiteSpace($env:WINGET_GITHUB_TOKEN) -or [string]::IsNullOrWhiteSpace($env:WINGET_FORK_OWNER))
495+ {
496+ $message = "Winget publish secrets are missing. Refusing to report a green public release without a submission attempt."
497+ $summary = @(
498+ "## winget publish",
499+ "- attempted: false",
500+ "- version: ${{ needs.build.outputs.version }}",
501+ "- result: failed-policy",
502+ "- reason: missing WINGET_GITHUB_TOKEN and/or WINGET_FORK_OWNER"
503+ )
504+
505+ $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
506+ $message | Set-Content -Path $logPath -Encoding utf8
507+ throw $message
508+ }
509+
469510 "enabled=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
470511
471512 - name : Checkout
@@ -487,13 +528,50 @@ jobs:
487528 WINGET_FORK_OWNER : ${{ secrets.WINGET_FORK_OWNER }}
488529 run : |
489530 $ErrorActionPreference = "Stop"
490- ./build/submit-winget-pr.ps1 `
491- -Version "${{ needs.build.outputs.version }}" `
492- -ManifestSourcePath "winget-manifests" `
493- -ForkOwner "$env:WINGET_FORK_OWNER" `
494- -RepositoryOwner "PrimeBuild" `
495- -PackageIdentifier "PrimeBuild.ThreadPilot" `
496- -GithubToken "$env:WINGET_GITHUB_TOKEN"
531+ $logsDir = "release-channel-logs"
532+ New-Item -ItemType Directory -Force -Path $logsDir | Out-Null
533+ $logPath = Join-Path $logsDir "winget-submit-log.txt"
534+
535+ try
536+ {
537+ & ./build/submit-winget-pr.ps1 `
538+ -Version "${{ needs.build.outputs.version }}" `
539+ -ManifestSourcePath "winget-manifests" `
540+ -ForkOwner "$env:WINGET_FORK_OWNER" `
541+ -RepositoryOwner "PrimeBuild" `
542+ -PackageIdentifier "PrimeBuild.ThreadPilot" `
543+ -GithubToken "$env:WINGET_GITHUB_TOKEN" *>&1 | Tee-Object -FilePath $logPath
544+
545+ if ($LASTEXITCODE -ne 0)
546+ {
547+ throw "submit-winget-pr.ps1 exited with code $LASTEXITCODE"
548+ }
549+
550+ @(
551+ "## winget publish",
552+ "- attempted: true",
553+ "- version: ${{ needs.build.outputs.version }}",
554+ "- result: submitted"
555+ ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
556+ }
557+ catch
558+ {
559+ @(
560+ "## winget publish",
561+ "- attempted: true",
562+ "- version: ${{ needs.build.outputs.version }}",
563+ "- result: failed"
564+ ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
565+ throw
566+ }
567+
568+ - name : Upload winget publish logs
569+ if : always()
570+ uses : actions/upload-artifact@v4
571+ with :
572+ name : winget-publish-logs
573+ path : release-channel-logs/winget-submit-log.txt
574+ if-no-files-found : error
497575
498576 publish-chocolatey :
499577 runs-on : windows-latest
@@ -503,19 +581,61 @@ jobs:
503581 if : needs.release.result == 'success'
504582
505583 steps :
506- - name : Check chocolatey publish secret
584+ - name : Resolve chocolatey publish policy
585+ id : policy
586+ shell : pwsh
587+ run : |
588+ $ErrorActionPreference = "Stop"
589+ $dryRun = "${{ github.event_name }}" -eq "workflow_dispatch" -and "${{ github.event.inputs.dry_run_publish }}" -eq "true"
590+ "dry_run=$($dryRun.ToString().ToLowerInvariant())" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
591+
592+ - name : Check chocolatey publish policy
507593 id : gate
508594 shell : pwsh
509595 env :
510596 CHOCOLATEY_API_KEY : ${{ secrets.CHOCOLATEY_API_KEY }}
511597 run : |
512- if ([string]::IsNullOrWhiteSpace($env:CHOCOLATEY_API_KEY))
598+ $ErrorActionPreference = "Stop"
599+ $logsDir = "release-channel-logs"
600+ New-Item -ItemType Directory -Force -Path $logsDir | Out-Null
601+ $packLogPath = Join-Path $logsDir "choco-pack-log.txt"
602+ $pushLogPath = Join-Path $logsDir "choco-push-log.txt"
603+ $dryRun = "${{ steps.policy.outputs.dry_run }}" -eq "true"
604+
605+ if ($dryRun)
513606 {
514- Write-Host "Chocolatey API key missing. Skipping publish-chocolatey job."
607+ $summary = @(
608+ "## chocolatey publish",
609+ "- attempted: false",
610+ "- version: ${{ needs.build.outputs.version }}",
611+ "- result: dry-run",
612+ "- reason: workflow_dispatch requested dry_run_publish=true"
613+ )
614+
615+ $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
616+ "Dry run requested. Chocolatey pack validation and push intentionally skipped." | Set-Content -Path $packLogPath -Encoding utf8
617+ "Dry run requested. Chocolatey push intentionally skipped." | Set-Content -Path $pushLogPath -Encoding utf8
515618 "enabled=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
516619 exit 0
517620 }
518621
622+ if ([string]::IsNullOrWhiteSpace($env:CHOCOLATEY_API_KEY))
623+ {
624+ $message = "CHOCOLATEY_API_KEY is missing. Refusing to report a green public release without a package publish attempt."
625+ $summary = @(
626+ "## chocolatey publish",
627+ "- attempted: false",
628+ "- version: ${{ needs.build.outputs.version }}",
629+ "- result: failed-policy",
630+ "- reason: missing CHOCOLATEY_API_KEY"
631+ )
632+
633+ $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
634+ $message | Set-Content -Path $packLogPath -Encoding utf8
635+ $message | Set-Content -Path $pushLogPath -Encoding utf8
636+ throw $message
637+ }
638+
519639 "enabled=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
520640
521641 - name : Checkout
@@ -529,21 +649,93 @@ jobs:
529649 name : release-installer
530650 path : release-assets/installer
531651
652+ - name : Validate chocolatey package
653+ if : steps.gate.outputs.enabled == 'true'
654+ shell : pwsh
655+ run : |
656+ $ErrorActionPreference = "Stop"
657+ $logsDir = (Resolve-Path "release-channel-logs").Path
658+ $packLogPath = Join-Path $logsDir "choco-pack-log.txt"
659+ $artifactRoot = "release-channel-artifacts/chocolatey"
660+ New-Item -ItemType Directory -Force -Path $artifactRoot | Out-Null
661+
662+ $installer = Get-ChildItem "release-assets/installer/*.exe" -File | Select-Object -First 1
663+ if (-not $installer)
664+ {
665+ throw "Installer executable not found for Chocolatey validation."
666+ }
667+
668+ & ./build/publish-chocolatey.ps1 `
669+ -Version "${{ needs.build.outputs.version }}" `
670+ -Tag "${{ needs.build.outputs.tag }}" `
671+ -InstallerPath $installer.FullName `
672+ -DryRun `
673+ -PackageOutputDirectory $artifactRoot `
674+ -MetadataOutputPath (Join-Path $artifactRoot "chocolatey-package-metadata.json") *>&1 | Tee-Object -FilePath $packLogPath
675+
676+ if ($LASTEXITCODE -ne 0)
677+ {
678+ throw "Chocolatey dry-run validation failed with exit code $LASTEXITCODE"
679+ }
680+
532681 - name : Publish chocolatey package
533682 if : steps.gate.outputs.enabled == 'true'
534683 shell : pwsh
535684 env :
536685 CHOCOLATEY_API_KEY : ${{ secrets.CHOCOLATEY_API_KEY }}
537686 run : |
538687 $ErrorActionPreference = "Stop"
688+ $logsDir = (Resolve-Path "release-channel-logs").Path
689+ $pushLogPath = Join-Path $logsDir "choco-push-log.txt"
690+ $artifactRoot = "release-channel-artifacts/chocolatey"
691+ New-Item -ItemType Directory -Force -Path $artifactRoot | Out-Null
539692 $installer = Get-ChildItem "release-assets/installer/*.exe" -File | Select-Object -First 1
540693 if (-not $installer)
541694 {
542695 throw "Installer executable not found for Chocolatey publish."
543696 }
544697
545- ./build/publish-chocolatey.ps1 `
546- -Version "${{ needs.build.outputs.version }}" `
547- -Tag "${{ needs.build.outputs.tag }}" `
548- -InstallerPath $installer.FullName `
549- -ApiKey "$env:CHOCOLATEY_API_KEY"
698+ try
699+ {
700+ & ./build/publish-chocolatey.ps1 `
701+ -Version "${{ needs.build.outputs.version }}" `
702+ -Tag "${{ needs.build.outputs.tag }}" `
703+ -InstallerPath $installer.FullName `
704+ -ApiKey "$env:CHOCOLATEY_API_KEY" `
705+ -PackageOutputDirectory $artifactRoot `
706+ -MetadataOutputPath (Join-Path $artifactRoot "chocolatey-publish-metadata.json") *>&1 | Tee-Object -FilePath $pushLogPath
707+
708+ if ($LASTEXITCODE -ne 0)
709+ {
710+ throw "publish-chocolatey.ps1 exited with code $LASTEXITCODE"
711+ }
712+
713+ @(
714+ "## chocolatey publish",
715+ "- attempted: true",
716+ "- version: ${{ needs.build.outputs.version }}",
717+ "- result: published"
718+ ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
719+ }
720+ catch
721+ {
722+ @(
723+ "## chocolatey publish",
724+ "- attempted: true",
725+ "- version: ${{ needs.build.outputs.version }}",
726+ "- result: failed"
727+ ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
728+ throw
729+ }
730+
731+ - name : Upload chocolatey publish logs
732+ if : always()
733+ uses : actions/upload-artifact@v4
734+ with :
735+ name : chocolatey-publish-logs
736+ path : |
737+ release-channel-logs/choco-pack-log.txt
738+ release-channel-logs/choco-push-log.txt
739+ release-channel-artifacts/chocolatey/*.nupkg
740+ release-channel-artifacts/chocolatey/*.json
741+ if-no-files-found : error
0 commit comments