Skip to content

Commit d09b6a1

Browse files
ci: bootstrap.yml — exercise bootstrap.ps1 end-to-end on a clean runner
Adds a Windows-only workflow that would have caught the clean-machine pack-local regression fixed in a863f87. Runs on PRs that touch bootstrap.ps1, the CLI source, the templates project, or Directory.Build.props. Pipeline (single job, ~25 min): 1. Pre-flight diagnostic dump of dotnet/winget/WindowsAppRuntime state so failures are debuggable from the logs alone. 2. `./bootstrap.ps1 -InstallWinAppSdk -SkipPlugin` non-interactively. Exercises both winget install paths when the runner image is bare. 3. From a fresh-shell step, `mur --version` resolves on PATH (proves the global-tool install propagated to User PATH). 4. `mur doctor` exits 0 with all checks PASS (plugin shows `info` when skipped, not FAIL). 5. Verify `local-nupkgs/Microsoft.UI.Reactor.0.0.0-local.nupkg`, `Microsoft.UI.Reactor.ProjectTemplates.0.0.0-local.nupkg`, and any `Microsoft.UI.Reactor.Cli.*.nupkg` are produced. 6. `dotnet new list reactorapp` finds the template. 7. `dotnet new reactorapp -n TestApp` + restore + build -c Release. 8. `mur upgrade --skip-plugin` succeeds against the bootstrapped tree. 9. Re-run bootstrap.ps1 to validate idempotence (winget already- installed exit code handled, dotnet tool update is a no-op, dotnet new uninstall+install handles the template-engine cache). Deliberately omits actions/setup-dotnet so bootstrap.ps1 itself is responsible for the SDK install. If windows-latest already ships .NET 10, the install branch silently no-ops and the rest of the pipeline still runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1c1e660 commit d09b6a1

1 file changed

Lines changed: 174 additions & 0 deletions

File tree

.github/workflows/bootstrap.yml

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
name: Bootstrap
2+
3+
# Exercises bootstrap.ps1 end-to-end on a fresh windows-latest runner to
4+
# validate the source-checkout developer flow (`git clone` → `bootstrap.ps1`
5+
# → `dotnet new reactorapp`). The script is invoked non-interactively via
6+
# `-InstallWinAppSdk -SkipPlugin`, which also exercises the winget install
7+
# path for the Windows App Runtime when the runner image doesn't already
8+
# have it.
9+
10+
on:
11+
pull_request:
12+
paths:
13+
- 'bootstrap.ps1'
14+
- 'src/Reactor.Cli/**'
15+
- 'tools/Templates/**'
16+
- 'Directory.Build.props'
17+
- '.github/workflows/bootstrap.yml'
18+
push:
19+
branches: [main]
20+
paths:
21+
- 'bootstrap.ps1'
22+
- 'src/Reactor.Cli/**'
23+
- 'tools/Templates/**'
24+
- 'Directory.Build.props'
25+
- '.github/workflows/bootstrap.yml'
26+
workflow_dispatch:
27+
28+
# Cancel in-progress runs against the same ref when a new push arrives.
29+
concurrency:
30+
group: bootstrap-${{ github.workflow }}-${{ github.ref }}
31+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
32+
33+
jobs:
34+
bootstrap:
35+
name: bootstrap.ps1 (windows-latest)
36+
runs-on: windows-latest
37+
timeout-minutes: 35
38+
steps:
39+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
40+
41+
# NOTE: deliberately *not* running actions/setup-dotnet here. We want
42+
# bootstrap.ps1 to detect the SDK situation on the runner and, if
43+
# necessary, winget-install .NET 10 itself. If the runner image
44+
# already has 10.x the install branch is a silent no-op and we
45+
# still exercise the rest of the pipeline.
46+
47+
- name: Pre-flight — show toolchain baseline
48+
shell: pwsh
49+
run: |
50+
Write-Host "=== dotnet --list-sdks ==="
51+
if (Get-Command dotnet -ErrorAction SilentlyContinue) {
52+
dotnet --list-sdks
53+
} else {
54+
Write-Host "(dotnet not on PATH)"
55+
}
56+
Write-Host ""
57+
Write-Host "=== winget --version ==="
58+
winget --version
59+
Write-Host ""
60+
Write-Host "=== Windows App Runtime 2.0 (winget list) ==="
61+
winget list --id Microsoft.WindowsAppRuntime.2.0 --exact 2>$null
62+
Write-Host "exit=$LASTEXITCODE"
63+
64+
- name: Run bootstrap.ps1 (non-interactive)
65+
shell: pwsh
66+
run: |
67+
./bootstrap.ps1 -InstallWinAppSdk -SkipPlugin
68+
if ($LASTEXITCODE -ne 0) { throw "bootstrap.ps1 exited $LASTEXITCODE" }
69+
70+
- name: Verify mur resolves from a fresh shell
71+
shell: pwsh
72+
run: |
73+
# A new step gets a fresh process with its own $env:Path snapshot.
74+
# `dotnet tool install -g` updates the *User* PATH, which fresh
75+
# processes pick up at start. If GH Actions doesn't propagate that
76+
# (varies by image), prepend defensively.
77+
$toolsDir = Join-Path $env:USERPROFILE '.dotnet\tools'
78+
if (-not (($env:Path -split ';') -contains $toolsDir)) {
79+
$env:Path = "$toolsDir;$env:Path"
80+
}
81+
$cmd = Get-Command mur -ErrorAction SilentlyContinue
82+
if (-not $cmd) { throw "mur is not resolvable on PATH after bootstrap" }
83+
Write-Host "mur resolved at: $($cmd.Source)"
84+
mur --version
85+
if ($LASTEXITCODE -ne 0) { throw "mur --version exited $LASTEXITCODE" }
86+
87+
- name: Run mur doctor (must report all checks PASS / info-only)
88+
shell: pwsh
89+
run: |
90+
$env:Path = "$env:USERPROFILE\.dotnet\tools;$env:Path"
91+
mur doctor
92+
if ($LASTEXITCODE -ne 0) { throw "mur doctor failed (exit $LASTEXITCODE)" }
93+
94+
- name: Verify local-nupkgs/ produced
95+
shell: pwsh
96+
run: |
97+
$expected = @(
98+
'local-nupkgs/Microsoft.UI.Reactor.0.0.0-local.nupkg',
99+
'local-nupkgs/Microsoft.UI.Reactor.ProjectTemplates.0.0.0-local.nupkg',
100+
'local-nupkgs/Microsoft.UI.Reactor.Cli.1.0.0.nupkg'
101+
)
102+
$missing = @()
103+
foreach ($p in $expected) {
104+
if (Test-Path $p) {
105+
$size = (Get-Item $p).Length
106+
Write-Host " [ok] $p ($size bytes)"
107+
} else {
108+
Write-Host " [missing] $p" -ForegroundColor Red
109+
# Microsoft.UI.Reactor.Cli is versioned from MinVer/etc and
110+
# may not match the literal "1.0.0" filename. Don't fail on
111+
# it specifically — just on the two 0.0.0-local nupkgs.
112+
if ($p -notlike '*Microsoft.UI.Reactor.Cli*') { $missing += $p }
113+
}
114+
}
115+
if ($missing.Count -gt 0) { throw "Missing nupkgs: $($missing -join ', ')" }
116+
# Sanity: at least one *.Cli.*.nupkg under local-nupkgs/.
117+
$cliPkgs = @(Get-ChildItem local-nupkgs -Filter 'Microsoft.UI.Reactor.Cli.*.nupkg')
118+
if ($cliPkgs.Count -eq 0) { throw "No Microsoft.UI.Reactor.Cli.*.nupkg found in local-nupkgs/" }
119+
Write-Host " [ok] CLI tool nupkg: $($cliPkgs[0].Name)"
120+
121+
- name: Verify dotnet new reactorapp template registered
122+
shell: pwsh
123+
run: |
124+
$listing = dotnet new list reactorapp 2>&1
125+
$rc = $LASTEXITCODE
126+
Write-Host $listing
127+
if ($rc -ne 0) { throw "dotnet new list reactorapp exited $rc" }
128+
if ($listing -notmatch 'reactorapp') {
129+
throw "reactorapp template not found in `dotnet new list` output"
130+
}
131+
132+
- name: Scaffold a TestApp and restore against the local feed
133+
shell: pwsh
134+
run: |
135+
# Inside the repo so the root nuget.config (which maps local-nupkgs/)
136+
# is picked up by NuGet's parent-dir walk.
137+
New-Item -ItemType Directory -Path TestProjects | Out-Null
138+
Push-Location TestProjects
139+
try {
140+
dotnet new reactorapp -n TestApp
141+
if ($LASTEXITCODE -ne 0) { throw "dotnet new reactorapp exited $LASTEXITCODE" }
142+
if (-not (Test-Path 'TestApp/TestApp.csproj')) { throw "TestApp/TestApp.csproj not produced" }
143+
144+
dotnet restore TestApp/TestApp.csproj --nologo -v:m
145+
if ($LASTEXITCODE -ne 0) { throw "dotnet restore for TestApp exited $LASTEXITCODE" }
146+
} finally {
147+
Pop-Location
148+
}
149+
150+
- name: Build TestApp (default WindowsAppSDKSelfContained=true)
151+
shell: pwsh
152+
run: |
153+
dotnet build TestProjects/TestApp/TestApp.csproj `
154+
-c Release `
155+
--nologo -v:m
156+
if ($LASTEXITCODE -ne 0) { throw "TestApp build exited $LASTEXITCODE" }
157+
158+
- name: Verify mur upgrade is idempotent
159+
shell: pwsh
160+
run: |
161+
# `mur upgrade` should succeed against an already-bootstrapped tree:
162+
# re-pack, re-install template (uninstall-first), refresh plugin.
163+
$env:Path = "$env:USERPROFILE\.dotnet\tools;$env:Path"
164+
mur upgrade --skip-plugin
165+
if ($LASTEXITCODE -ne 0) { throw "mur upgrade exited $LASTEXITCODE" }
166+
167+
- name: Re-run bootstrap.ps1 (idempotence check)
168+
shell: pwsh
169+
run: |
170+
# Re-running on the same machine should be a clean no-op for any
171+
# already-correct piece (winget detects already-installed runtimes,
172+
# dotnet tool update is a no-op when up to date, etc.).
173+
./bootstrap.ps1 -InstallWinAppSdk -SkipPlugin
174+
if ($LASTEXITCODE -ne 0) { throw "bootstrap.ps1 re-run exited $LASTEXITCODE" }

0 commit comments

Comments
 (0)