From 85c1a97c5d7c41bc9b5988bd566fe77d2a5de65e Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 24 Sep 2026 18:14:23 -0500 Subject: [PATCH 1/2] Add -NoCheckout switch to PRTest.ps1 Add a -NoCheckout switch to Tools\PRTest.ps1 that fetches the PR's validated merged manifest from the WinGet Validator artifact instead of checking out the pull request locally. * Resolve the PR head SHA and locate the app-scoped 10. Validation Completed check run * Parse the artifact download URL from the check output and download, extract, and stage the singleton YAML manifest into a folder named for the PR number under $env:TEMP\WinGet-PRTest * Run SandboxTest.ps1 against the staged manifest with -SkipManifestValidation implied * Preserve existing checkout-mode behavior * Document exit codes for missing dependencies, checkout failure, missing validation check, missing artifact URL, and missing YAML --- Tools/PRTest.ps1 | 101 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 88 insertions(+), 13 deletions(-) diff --git a/Tools/PRTest.ps1 b/Tools/PRTest.ps1 index fc5bbf7cb3529..c06465654c5c5 100644 --- a/Tools/PRTest.ps1 +++ b/Tools/PRTest.ps1 @@ -1,11 +1,22 @@ -# This script does a checkout of a Pull Request using the GitHub CLI, and then runs it using SandboxTest.ps1. +### Exit Codes: +# 0 = Success +# 1 = Missing dependency +# 2 = Pull request checkout error +# 3 = Validation completed check was not found +# 4 = Artifact download URL was not found +# 5 = YAML manifest was not found +### + +# This script checks out a Pull Request or downloads its validated merged manifest, +# and then runs it using SandboxTest.ps1. Param( - [Parameter(Position = 0, HelpMessage = 'The Pull Request to checkout.', Mandatory = $true)] + [Parameter(Position = 0, HelpMessage = 'The Pull Request to test.', Mandatory = $true)] [String] $PullRequest, [Parameter(HelpMessage = "Open the Pull Request's review page in the default browser")] [Switch] $Review = $false, [Switch] $KeepBranch = $false, + [Switch] $NoCheckout = $false, [Switch] $Prerelease = $false, [Switch] $EnableExperimentalFeatures = $false, [string] $WinGetVersion = $null, @@ -23,6 +34,18 @@ filter Initialize-VirtualTerminalSequence { } } +function Write-PRTestError { + param( + [Parameter(Mandatory = $true)] + [string] $Message, + [Parameter(Mandatory = $true)] + [int] $ExitCode + ) + + Write-Error -Message $Message -ErrorAction Continue + exit $ExitCode +} + # Flags Write-Debug 'Checking for supported features' $script:vtSupported = (Get-Host).UI.SupportsVirtualTerminal @@ -37,21 +60,73 @@ $script:vtForegroundGreen = 32 | Initialize-VirtualTerminalSequence Write-Debug 'Creating internal state' $PullRequest = $PullRequest.TrimStart('#') $ErrorActionPreference = 'Stop' -$repositoryRoot = 'https://github.com/microsoft/winget-pkgs/' -$rootDirectory = ((Resolve-Path (git rev-parse --show-toplevel)).ToString() + '\') +$repository = 'microsoft/winget-pkgs' Write-Verbose 'Ensuring Dependencies are Present' -if (!$script:GhIsPresent) { Write-Error "The GitHub CLI is not installed. Install it via 'winget install GitHub.cli' and come back here!" -ErrorAction Stop } -if (!$script:GitIsPresent) { Write-Error "Git is not installed. Install it via 'winget install Git.Git' and come back here!" -ErrorAction Stop } -if (!$script:SandboxIsPresent) { Write-Error 'Windows Sandbox is not enabled. Enable it and come back here!' -ErrorAction Stop } +if (!$script:GhIsPresent) { Write-PRTestError "The GitHub CLI is not installed. Install it via 'winget install GitHub.cli' and come back here!" 1 } +if (!$NoCheckout -and !$script:GitIsPresent) { Write-PRTestError "Git is not installed. Install it via 'winget install Git.Git' and come back here!" 1 } +if (!$script:SandboxIsPresent) { Write-PRTestError 'Windows Sandbox is not enabled. Enable it and come back here!' 1 } + +if ($NoCheckout) { + Write-Output '--> Retrieving PR check runs' + $headSha = gh api "repos/$repository/pulls/$PullRequest" --jq '.head.sha' + $checkRuns = gh api "repos/$repository/commits/$headSha/check-runs?app_id=1451866&filter=latest&per_page=100" | + ConvertFrom-Json -ErrorAction SilentlyContinue + $validationCompletedCheck = $checkRuns.check_runs | + Where-Object { + $_.name -eq '10. Validation Completed' -and + $_.app.slug -eq 'wingetvalidator-prod' -and + $_.head_sha -eq $headSha -and + $_.status -eq 'completed' + } | + Select-Object -First 1 + + if (!$validationCompletedCheck) { + Write-PRTestError "The check run was not found for PR #$PullRequest." 3 + } + + $json = [regex]::Match( + [string] $validationCompletedCheck.output.text, + '(?ms)```json\s*(?.*?)\s*```' + ).Groups['json'].Value + $artifactDownloadUrl = ($json | ConvertFrom-Json -ErrorAction SilentlyContinue).Artifacts.ArtifactDownloadUrl + if ([string]::IsNullOrWhiteSpace($artifactDownloadUrl)) { + Write-PRTestError "The artifact download URL was not found for PR #$PullRequest." 4 + } + + $tempFolder = Join-Path $env:TEMP 'WinGet-PRTest' + $zipPath = Join-Path $env:TEMP 'WinGet-PRTest.zip' + if (Test-Path $tempFolder) { Remove-Item $tempFolder -Recurse -Force } + if (Test-Path $zipPath) { Remove-Item $zipPath -Force } + New-Item $tempFolder -ItemType Directory | Out-Null + + Write-Output '--> Downloading validation artifact' + Invoke-WebRequest $artifactDownloadUrl -OutFile $zipPath + Expand-Archive $zipPath -DestinationPath $tempFolder -ErrorAction SilentlyContinue + Remove-Item $zipPath -Force -ErrorAction SilentlyContinue + + $manifest = @(Get-ChildItem $tempFolder -Filter '*.yaml' -File -Recurse) + if ($manifest.Count -ne 1) { + Write-PRTestError "A single YAML manifest was not found in the validation artifact for PR #$PullRequest." 5 + } + + $path = Join-Path $tempFolder $PullRequest + New-Item $path -ItemType Directory | Out-Null + Move-Item $manifest[0].FullName $path +} else { + $repositoryRoot = 'https://github.com/microsoft/winget-pkgs/' + $rootDirectory = ((Resolve-Path (git rev-parse --show-toplevel)).ToString() + '\') -Write-Verbose 'Checking out PR' -gh pr checkout $PullRequest $(if (!$KeepBranch) { '--detach' }) -f -R $repositoryRoot | Out-Null -if ($LASTEXITCODE -ne 0) { Write-Error "There was an error checking out the PR. Make sure you're logged into GitHub via 'gh auth login' and come back here!" -ErrorAction Stop } + Write-Verbose 'Checking out PR' + gh pr checkout $PullRequest $(if (!$KeepBranch) { '--detach' }) -f -R $repositoryRoot | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-PRTestError "There was an error checking out the PR. Make sure you're logged into GitHub via 'gh auth login' and come back here!" 2 + } -Write-Verbose 'Parsing changed files' -$manifest = @(gh pr diff $PullRequest --name-only) -$path = (Get-Item (Resolve-Path ($rootDirectory + $manifest[0]))).Directory + Write-Verbose 'Parsing changed files' + $manifest = @(gh pr diff $PullRequest --name-only) + $path = (Get-Item (Resolve-Path ($rootDirectory + $manifest[0]))).Directory +} Write-Verbose 'Passing execution to SandboxTest.ps1' $sandboxTestPath = (Resolve-Path ($PSScriptRoot.ToString() + '\SandboxTest.ps1')).ToString() From 248d38dc426a6eea15dc018efe0c66753864f9f5 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 24 Sep 2026 21:02:34 -0500 Subject: [PATCH 2/2] Address PR review feedback for -NoCheckout artifact handling * Extract only the YAML manifest entry from the validation artifact using [System.IO.Compression.ZipFile] and [System.IO.Compression.ZipFileExtensions]::ExtractToFile(), avoiding extraction of installation verification logs that can hit long-path issues * Nest artifact paths as WinGet-PRTest\\\, retaining artifacts.zip alongside the extracted Manifest folder * Consolidate cleanup to a single upfront removal of the run folder * Support -Clean by removing the entire WinGet-PRTest root folder before retrieving the artifact --- Tools/PRTest.ps1 | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/Tools/PRTest.ps1 b/Tools/PRTest.ps1 index c06465654c5c5..68a4401a08d31 100644 --- a/Tools/PRTest.ps1 +++ b/Tools/PRTest.ps1 @@ -68,6 +68,9 @@ if (!$NoCheckout -and !$script:GitIsPresent) { Write-PRTestError "Git is not ins if (!$script:SandboxIsPresent) { Write-PRTestError 'Windows Sandbox is not enabled. Enable it and come back here!' 1 } if ($NoCheckout) { + $rootFolder = Join-Path $env:TEMP 'WinGet-PRTest' + if ($Clean -and (Test-Path $rootFolder)) { Remove-Item $rootFolder -Recurse -Force } + Write-Output '--> Retrieving PR check runs' $headSha = gh api "repos/$repository/pulls/$PullRequest" --jq '.head.sha' $checkRuns = gh api "repos/$repository/commits/$headSha/check-runs?app_id=1451866&filter=latest&per_page=100" | @@ -94,25 +97,35 @@ if ($NoCheckout) { Write-PRTestError "The artifact download URL was not found for PR #$PullRequest." 4 } - $tempFolder = Join-Path $env:TEMP 'WinGet-PRTest' - $zipPath = Join-Path $env:TEMP 'WinGet-PRTest.zip' - if (Test-Path $tempFolder) { Remove-Item $tempFolder -Recurse -Force } - if (Test-Path $zipPath) { Remove-Item $zipPath -Force } - New-Item $tempFolder -ItemType Directory | Out-Null + $runFolder = Join-Path $rootFolder "$PullRequest\$headSha" + $zipPath = Join-Path $runFolder 'artifacts.zip' + $manifestFolder = Join-Path $runFolder 'Manifest' + if (Test-Path $runFolder) { Remove-Item $runFolder -Recurse -Force } + New-Item $manifestFolder -ItemType Directory | Out-Null Write-Output '--> Downloading validation artifact' Invoke-WebRequest $artifactDownloadUrl -OutFile $zipPath - Expand-Archive $zipPath -DestinationPath $tempFolder -ErrorAction SilentlyContinue - Remove-Item $zipPath -Force -ErrorAction SilentlyContinue - $manifest = @(Get-ChildItem $tempFolder -Filter '*.yaml' -File -Recurse) - if ($manifest.Count -ne 1) { + # Only extract the YAML manifest entry from the archive; the artifact also contains + # installation verification logs whose long, nested paths can fail to extract. + $manifestFile = $null + try { + $zipArchive = [System.IO.Compression.ZipFile]::OpenRead($zipPath) + $yamlEntries = @($zipArchive.Entries | Where-Object { $_.Name -like '*.yaml' }) + if ($yamlEntries.Count -eq 1) { + Write-Output "--> Extracting file: $($yamlEntries[0].Name)" + $manifestFile = Join-Path $manifestFolder $yamlEntries[0].Name + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($yamlEntries[0], $manifestFile) + } + } finally { + if ($zipArchive) { $zipArchive.Dispose() } + } + + if (!$manifestFile -or !(Test-Path $manifestFile -PathType Leaf)) { Write-PRTestError "A single YAML manifest was not found in the validation artifact for PR #$PullRequest." 5 } - $path = Join-Path $tempFolder $PullRequest - New-Item $path -ItemType Directory | Out-Null - Move-Item $manifest[0].FullName $path + $path = $manifestFolder } else { $repositoryRoot = 'https://github.com/microsoft/winget-pkgs/' $rootDirectory = ((Resolve-Path (git rev-parse --show-toplevel)).ToString() + '\')