Skip to content

Commit e7fc8fe

Browse files
authored
Merge pull request #1150 from marshalhayes/dogfood
Add dogfooding scripts and workflow for PR testing
2 parents 462b995 + 9cb6924 commit e7fc8fe

3 files changed

Lines changed: 648 additions & 0 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Add Dogfooding Comment
2+
3+
on:
4+
# Use pull_request_target to run in the context of the base branch
5+
# This allows commenting on PRs from forks
6+
pull_request_target:
7+
types: [opened, reopened, synchronize]
8+
branches:
9+
- 'main'
10+
# Allow manual triggering
11+
workflow_dispatch:
12+
inputs:
13+
pr_number:
14+
description: 'PR number to add dogfooding comment to'
15+
required: true
16+
type: number
17+
18+
jobs:
19+
add-dogfood-comment:
20+
# Only run on the CommunityToolkit org to avoid running on forks
21+
if: ${{ github.repository_owner == 'CommunityToolkit' }}
22+
runs-on: ubuntu-latest
23+
permissions:
24+
pull-requests: write
25+
steps:
26+
- name: Add dogfooding comment
27+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
28+
with:
29+
script: |
30+
// Get PR number from either the PR event or manual input
31+
const prNumber = context.payload.number || context.payload.inputs.pr_number;
32+
const bashScript = 'https://raw.githubusercontent.com/CommunityToolkit/Aspire/main/eng/scripts/dogfood-pr.sh';
33+
const psScript = 'https://raw.githubusercontent.com/CommunityToolkit/Aspire/main/eng/scripts/dogfood-pr.ps1';
34+
35+
// Unique marker to identify dogfooding comments
36+
const dogfoodMarker = '<!-- dogfood-pr -->';
37+
38+
const comment = `${dogfoodMarker}
39+
🚀 **Dogfood this PR with:**
40+
41+
> **⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.**
42+
43+
\`\`\`bash
44+
curl -fsSL ${bashScript} | bash -s -- ${prNumber}
45+
\`\`\`
46+
Or
47+
- Run remotely in PowerShell:
48+
\`\`\`powershell
49+
iex "& { $(irm ${psScript}) } ${prNumber}"
50+
\`\`\``;
51+
52+
// Check for existing dogfooding comment
53+
const comments = await github.rest.issues.listComments({
54+
issue_number: prNumber,
55+
owner: context.repo.owner,
56+
repo: context.repo.repo,
57+
});
58+
59+
const existingComment = comments.data.find(comment => comment.body.includes(dogfoodMarker));
60+
61+
if (existingComment) {
62+
// Update existing comment
63+
await github.rest.issues.updateComment({
64+
comment_id: existingComment.id,
65+
owner: context.repo.owner,
66+
repo: context.repo.repo,
67+
body: comment
68+
});
69+
} else {
70+
// Create new comment
71+
await github.rest.issues.createComment({
72+
issue_number: prNumber,
73+
owner: context.repo.owner,
74+
repo: context.repo.repo,
75+
body: comment
76+
});
77+
}

eng/scripts/dogfood-pr.ps1

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
#!/usr/bin/env pwsh
2+
3+
<#
4+
.SYNOPSIS
5+
Download and install NuGet packages from a PR's build artifacts for local testing.
6+
.EXAMPLE
7+
./dogfood-pr.ps1 1129
8+
./dogfood-pr.ps1 1129 -WorkflowRunId 12345678
9+
#>
10+
11+
param(
12+
[Parameter(Position = 0, Mandatory = $true, HelpMessage = "Pull request number")]
13+
[ValidateRange(1, [int]::MaxValue)]
14+
[int]$PRNumber,
15+
16+
[Parameter(HelpMessage = "Workflow run ID (skip PR resolution)")]
17+
[ValidateRange(1, [long]::MaxValue)]
18+
[long]$WorkflowRunId = 0,
19+
20+
[Parameter(HelpMessage = "Install prefix directory")]
21+
[string]$InstallPath = "",
22+
23+
[Parameter(HelpMessage = "Verbose output")]
24+
[switch]$VerboseOutput,
25+
26+
[Parameter(HelpMessage = "Keep temp download directory")]
27+
[switch]$KeepArchive,
28+
29+
[Parameter(HelpMessage = "Show help")]
30+
[switch]$Help
31+
)
32+
33+
$ErrorActionPreference = "Stop"
34+
35+
$Script:Repo = "CommunityToolkit/Aspire"
36+
$Script:CIWorkflow = "dotnet-ci.yml"
37+
$Script:ArtifactName = "nuget-packages"
38+
39+
# --- Output Helpers ---
40+
41+
function Write-Link {
42+
param([string]$Url, [string]$Label)
43+
return "$([char]27)]8;;${Url}$([char]27)\${Label}$([char]27)]8;;$([char]27)\"
44+
}
45+
46+
function Get-DisplayPath {
47+
param([string]$Path)
48+
if ($Path.StartsWith($HOME)) {
49+
return "~" + $Path.Substring($HOME.Length)
50+
}
51+
return $Path
52+
}
53+
54+
function Invoke-WithSpinner {
55+
param([string]$Message, [string]$Command, [string[]]$Arguments)
56+
$psi = [System.Diagnostics.ProcessStartInfo]::new($Command)
57+
foreach ($arg in $Arguments) { $psi.ArgumentList.Add($arg) }
58+
$psi.UseShellExecute = $false
59+
$psi.RedirectStandardOutput = $true
60+
$psi.RedirectStandardError = $true
61+
$proc = [System.Diagnostics.Process]::Start($psi)
62+
$chars = [char[]]'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
63+
$i = 0
64+
while (-not $proc.HasExited) {
65+
Write-Host "`r$($chars[$i++ % $chars.Length]) $Message" -NoNewline
66+
Start-Sleep -Milliseconds 100
67+
}
68+
$proc.WaitForExit()
69+
Write-Host "`r`e[K" -NoNewline
70+
return $proc.ExitCode
71+
}
72+
73+
function Show-Help {
74+
@"
75+
Usage: dogfood-pr.ps1 [-PRNumber] <int> [OPTIONS]
76+
77+
Download and install NuGet packages from a PR's build artifacts for local testing.
78+
79+
OPTIONS:
80+
-WorkflowRunId ID Workflow run ID (skip PR resolution)
81+
-InstallPath PATH Install prefix (default: `$HOME/.aspire)
82+
-VerboseOutput Verbose output
83+
-KeepArchive Keep temp download directory
84+
-Help Show this help
85+
86+
EXAMPLES:
87+
./dogfood-pr.ps1 1129
88+
./dogfood-pr.ps1 1129 -WorkflowRunId 12345678
89+
./dogfood-pr.ps1 1129 -InstallPath ./local-packages
90+
91+
REQUIREMENTS:
92+
- GitHub CLI (gh) authenticated: https://cli.github.com
93+
"@
94+
}
95+
96+
# --- Core Functions ---
97+
98+
function Test-Prerequisites {
99+
if (-not (Get-Command gh -ErrorAction SilentlyContinue)) {
100+
Write-Host "✗ GitHub CLI (gh) is required. Install from: https://cli.github.com" -ForegroundColor Red
101+
exit 1
102+
}
103+
104+
& gh auth status 2>&1 | Out-Null
105+
if ($LASTEXITCODE -ne 0) {
106+
Write-Host "✗ GitHub CLI is not authenticated. Run: gh auth login" -ForegroundColor Red
107+
exit 1
108+
}
109+
}
110+
111+
function Resolve-PullRequest {
112+
$prUrl = "https://github.com/$($Script:Repo)/pull/$PRNumber"
113+
try {
114+
$prJson = & gh api "repos/$($Script:Repo)/pulls/$PRNumber" --jq '{sha: .head.sha, title: .title, author: .user.login}' 2>$null | ConvertFrom-Json
115+
} catch {
116+
Write-Host "✗ PR #$PRNumber not found in $($Script:Repo)" -ForegroundColor Red
117+
exit 1
118+
}
119+
120+
$script:HeadSha = $prJson.sha
121+
$prTitle = $prJson.title
122+
$prAuthor = $prJson.author
123+
124+
$authorDisplay = Write-Link "https://github.com/$prAuthor" "@$prAuthor"
125+
126+
Write-Host ""
127+
$cols = if ($Host.UI.RawUI.WindowSize.Width) { $Host.UI.RawUI.WindowSize.Width } else { 80 }
128+
$prefix = "PR #$PRNumber"
129+
$suffix = " by @$prAuthor"
130+
$maxTitle = $cols - $prefix.Length - $suffix.Length - 2
131+
if ($prTitle.Length -gt $maxTitle -and $maxTitle -gt 3) {
132+
$prTitle = $prTitle.Substring(0, $maxTitle - 1) + ""
133+
}
134+
Write-Host "$(Write-Link $prUrl "PR #$PRNumber")$prTitle by $authorDisplay"
135+
if ($VerboseOutput) { Write-Host " Head commit: $(Write-Link "https://github.com/$($Script:Repo)/commit/$($script:HeadSha)" $script:HeadSha.Substring(0,7))" -ForegroundColor DarkGray }
136+
Write-Host ""
137+
}
138+
139+
function Find-WorkflowRun {
140+
if ($WorkflowRunId -gt 0) {
141+
$script:RunId = $WorkflowRunId
142+
return
143+
}
144+
145+
$script:RunId = & gh api "repos/$($Script:Repo)/actions/workflows/$($Script:CIWorkflow)/runs?event=pull_request&head_sha=$($script:HeadSha)" `
146+
--jq '.workflow_runs | sort_by(.created_at, .updated_at) | reverse | .[0].id' 2>$null
147+
148+
if (-not $script:RunId -or $script:RunId -eq "null") {
149+
Write-Host "✗ No workflow run found for PR #$PRNumber (SHA: $($script:HeadSha))" -ForegroundColor Red
150+
Write-Host " Check: https://github.com/$($Script:Repo)/actions/workflows/$($Script:CIWorkflow)"
151+
exit 1
152+
}
153+
154+
if ($VerboseOutput) { Write-Host " Workflow run: $(Write-Link "https://github.com/$($Script:Repo)/actions/runs/$($script:RunId)" $script:RunId)" -ForegroundColor DarkGray }
155+
}
156+
157+
function Save-Artifacts {
158+
$script:DownloadDir = Join-Path $tempDir "nuget-packages"
159+
$runUrl = "https://github.com/$($Script:Repo)/actions/runs/$($script:RunId)"
160+
161+
$exitCode = Invoke-WithSpinner "📦 Downloading packages..." "gh" @("run", "download", $script:RunId, "-R", $Script:Repo, "--name", $Script:ArtifactName, "-D", $script:DownloadDir)
162+
if ($exitCode -ne 0) {
163+
Write-Host "✗ Failed to download artifacts — build may still be in progress or artifacts may have expired" -ForegroundColor Red
164+
Write-Host " $(Write-Link $runUrl "View workflow run")" -ForegroundColor DarkGray
165+
exit 1
166+
}
167+
168+
$script:Packages = Get-ChildItem -Path $script:DownloadDir -Filter "*.nupkg" -Recurse
169+
if ($script:Packages.Count -eq 0) {
170+
Write-Host "✗ No NuGet packages found in downloaded artifacts" -ForegroundColor Red
171+
exit 1
172+
}
173+
174+
$totalSize = ($script:Packages | Measure-Object -Property Length -Sum).Sum
175+
$sizeDisplay = if ($totalSize -ge 1MB) { "{0:N1} MB" -f ($totalSize / 1MB) } elseif ($totalSize -ge 1KB) { "{0:N0} KB" -f ($totalSize / 1KB) } else { "$totalSize B" }
176+
Write-Host "📦 Downloaded $($script:Packages.Count) packages ($sizeDisplay)"
177+
}
178+
179+
function Install-Packages {
180+
New-Item -ItemType Directory -Path $hiveDir -Force | Out-Null
181+
$script:Packages | Copy-Item -Destination $hiveDir -Force
182+
Write-Host "📂 Installed to $(Get-DisplayPath $hiveDir)"
183+
184+
$script:Version = ""
185+
$firstPkg = $script:Packages | Select-Object -First 1
186+
if ($firstPkg) {
187+
Add-Type -AssemblyName System.IO.Compression.FileSystem
188+
$zip = [System.IO.Compression.ZipFile]::OpenRead($firstPkg.FullName)
189+
try {
190+
$nuspec = $zip.Entries | Where-Object { $_.Name -like "*.nuspec" } | Select-Object -First 1
191+
if ($nuspec) {
192+
$reader = [System.IO.StreamReader]::new($nuspec.Open())
193+
$xml = [xml]$reader.ReadToEnd()
194+
$reader.Close()
195+
$script:Version = $xml.package.metadata.version
196+
}
197+
} finally {
198+
$zip.Dispose()
199+
}
200+
}
201+
}
202+
203+
function Register-NuGetSource {
204+
$script:NuGetConfig = $null
205+
206+
if (-not (Get-Command dotnet -ErrorAction SilentlyContinue)) {
207+
Write-Host "⚠ dotnet CLI not found — configure NuGet source manually:" -ForegroundColor Yellow
208+
Write-Host " dotnet nuget add source `"$hiveDir`" --name `"$sourceName`"" -ForegroundColor DarkGray
209+
return
210+
}
211+
212+
$existingSources = & dotnet nuget list source 2>$null
213+
if ($existingSources -match [regex]::Escape($sourceName)) {
214+
& dotnet nuget update source $sourceName --source $hiveDir 2>&1 | Out-Null
215+
} else {
216+
& dotnet nuget add source $hiveDir --name $sourceName 2>&1 | Out-Null
217+
}
218+
219+
$script:NuGetConfig = (& dotnet nuget config paths 2>$null | Select-Object -First 1)
220+
if ($script:NuGetConfig) {
221+
Write-Host "🔧 Configured source $sourceName in $(Get-DisplayPath $script:NuGetConfig)"
222+
} else {
223+
Write-Host "🔧 Configured source $sourceName"
224+
}
225+
}
226+
227+
function Write-Summary {
228+
Write-Host ""
229+
if ($script:Version) {
230+
Write-Host "🐶 Ready — use version $($script:Version) to test these changes" -ForegroundColor Green
231+
}
232+
233+
Write-Host ""
234+
Write-Host "To undo:" -ForegroundColor DarkGray
235+
$hivePath = Join-Path $InstallPath "hives" "community-toolkit-pr-$PRNumber"
236+
if ($script:NuGetConfig) {
237+
Write-Host " dotnet nuget remove source `"$sourceName`" --configfile `"$($script:NuGetConfig)`" | Out-Null; Remove-Item -Recurse -Force `"$hivePath`"" -ForegroundColor DarkGray
238+
} else {
239+
Write-Host " dotnet nuget remove source `"$sourceName`" | Out-Null; Remove-Item -Recurse -Force `"$hivePath`"" -ForegroundColor DarkGray
240+
}
241+
242+
if ($KeepArchive) {
243+
Write-Host ""
244+
Write-Host "Archive kept at: $tempDir" -ForegroundColor DarkGray
245+
}
246+
247+
if ($VerboseOutput) {
248+
Write-Host ""
249+
Write-Host "Packages:" -ForegroundColor DarkGray
250+
Get-ChildItem -Path $hiveDir -Filter "*.nupkg" | Sort-Object Name | ForEach-Object {
251+
Write-Host " $($_.Name)" -ForegroundColor DarkGray
252+
}
253+
}
254+
}
255+
256+
# --- Entry Point ---
257+
258+
if ($Help) {
259+
Show-Help
260+
exit 0
261+
}
262+
263+
Test-Prerequisites
264+
265+
if (-not $InstallPath) {
266+
$InstallPath = Join-Path $HOME ".aspire"
267+
}
268+
$hiveDir = Join-Path $InstallPath "hives" "community-toolkit-pr-$PRNumber" "packages"
269+
$sourceName = "CommunityToolkit-PR-$PRNumber"
270+
271+
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "dogfood-pr-$([System.Guid]::NewGuid().ToString('N').Substring(0,8))"
272+
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
273+
274+
try {
275+
Resolve-PullRequest
276+
Find-WorkflowRun
277+
Save-Artifacts
278+
Install-Packages
279+
Register-NuGetSource
280+
Write-Summary
281+
} finally {
282+
if (-not $KeepArchive -and (Test-Path $tempDir)) {
283+
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
284+
}
285+
}

0 commit comments

Comments
 (0)