Skip to content

Commit 1c1e660

Browse files
feat(bootstrap): winget auto-install for .NET 10 + optional WinAppSDK prompt
Reduces clean-machine friction. Instead of failing with a doc link when a prerequisite is missing, bootstrap.ps1 now drives a `winget install` itself (hard-failing if winget itself isn't on PATH). - New `Install-WithWinget` helper: single point for winget calls. Runs with `--silent --disable-interactivity --accept-source-agreements --accept-package-agreements`, treats "already installed" exit -1978335189 as success, and refreshes `$env:Path` from Machine + User registry strings so freshly-installed binaries resolve in this same shell (Path edits made by winget don't otherwise propagate to a running process). - .NET 10 SDK: new `Test-DotnetSdk10` probe. When the SDK is missing or pre-10, dump the installed-SDK list (for debugging) and call `Install-WithWinget Microsoft.DotNet.SDK.10`. Re-probe after install and fail with a "open a new shell" hint if it still isn't visible. - Windows App SDK: tri-state `-InstallWinAppSdk` parameter. Unspecified → prompt the dev (default no) since the framework defaults to WindowsAppSDKSelfContained=true and the runtime is only needed for framework-dependent deployment. `-InstallWinAppSdk` → force install non-interactively (use this from CI / fresh-dev-box automation). `-InstallWinAppSdk:$false` → skip the prompt silently. The prompt-by-default for WinAppSDK preserves the existing self-contained happy path (no surprise 50MB install) while making framework-dependent deployment one keystroke away. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a863f87 commit 1c1e660

1 file changed

Lines changed: 113 additions & 18 deletions

File tree

bootstrap.ps1

Lines changed: 113 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,39 @@
2222
.PARAMETER Configuration
2323
Build configuration for the CLI nupkg. Default: Release.
2424
25+
.PARAMETER InstallWinAppSdk
26+
Tri-state Windows App Runtime 2.0 install. When unspecified (default),
27+
prompt interactively (default no). Pass -InstallWinAppSdk to force-install
28+
non-interactively (useful for CI / one-shot dev-box setup); pass
29+
-InstallWinAppSdk:$false to skip the prompt silently. The framework
30+
defaults to self-contained, so the runtime is only required for
31+
framework-dependent deployment.
32+
2533
.EXAMPLE
2634
./bootstrap.ps1
27-
Full bootstrap.
35+
Full bootstrap (prompts before installing WindowsAppRuntime).
2836
2937
.EXAMPLE
3038
./bootstrap.ps1 -SkipPlugin
3139
Skip the Claude plugin step.
40+
41+
.EXAMPLE
42+
./bootstrap.ps1 -InstallWinAppSdk -SkipPlugin
43+
Non-interactive: install everything (incl. WindowsAppRuntime) and
44+
skip the agent plugin. Suitable for CI / fresh-dev-box automation.
3245
#>
3346
[CmdletBinding()]
3447
param(
3548
[switch]$SkipPlugin,
3649
[switch]$SkipMurInstall,
37-
[string]$Configuration = 'Release'
50+
[string]$Configuration = 'Release',
51+
# Windows App SDK runtime install: tri-state. When unspecified, prompt
52+
# interactively (default no) since the framework defaults to
53+
# WindowsAppSDKSelfContained=true and the machine runtime is only needed
54+
# for framework-dependent deployment. Pass -InstallWinAppSdk to force-
55+
# install non-interactively; pass -InstallWinAppSdk:$false to skip the
56+
# prompt and continue.
57+
[Nullable[bool]]$InstallWinAppSdk = $null
3858
)
3959

4060
$ErrorActionPreference = 'Stop'
@@ -56,32 +76,107 @@ function Fail($msg) {
5676
exit 1
5777
}
5878

79+
# Install a winget package and refresh $env:Path so the freshly-installed tool
80+
# is resolvable in this same shell. Hard-fails if winget itself is missing —
81+
# that's an OS-level prerequisite this script doesn't try to repair.
82+
function Install-WithWinget {
83+
param(
84+
[Parameter(Mandatory)][string]$Id,
85+
[string]$Reason = $Id
86+
)
87+
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
88+
Fail "Need to install '$Reason' but winget is not on PATH. Install App Installer from the Microsoft Store, then re-run ./bootstrap.ps1."
89+
}
90+
Write-Host " Installing $Reason via winget ($Id)..." -ForegroundColor Yellow
91+
& winget install --id $Id --accept-source-agreements --accept-package-agreements --silent --disable-interactivity
92+
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne -1978335189) {
93+
# -1978335189 = APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE (already installed / up-to-date)
94+
Fail "winget install $Id failed (exit $LASTEXITCODE). Install $Reason manually and re-run ./bootstrap.ps1."
95+
}
96+
# winget edits the Machine + User PATH but the current process keeps its
97+
# original. Rebuild $env:Path from the registry so subsequent commands in
98+
# this script can find the freshly-installed binaries.
99+
$env:Path = (
100+
[Environment]::GetEnvironmentVariable('Path', 'Machine'),
101+
[Environment]::GetEnvironmentVariable('Path', 'User')
102+
) -join ';'
103+
}
104+
59105
# ---------------------------------------------------------------------------
60106
# 1. Pre-flight
61107
# ---------------------------------------------------------------------------
62108
Write-Step 'Pre-flight checks'
63109

64-
$dotnetCmd = Get-Command dotnet -ErrorAction SilentlyContinue
65-
if (-not $dotnetCmd) {
66-
Fail '`dotnet` not found on PATH. Install the .NET 10+ SDK: https://dotnet.microsoft.com/download'
67-
}
68-
$sdkOutput = & dotnet --list-sdks
69-
$has10OrLater = $false
70-
foreach ($line in $sdkOutput) {
71-
if ($line -match '^(\d+)\.') {
72-
if ([int]$Matches[1] -ge 10) { $has10OrLater = $true; break }
110+
function Test-DotnetSdk10 {
111+
if (-not (Get-Command dotnet -ErrorAction SilentlyContinue)) { return $false }
112+
foreach ($line in (& dotnet --list-sdks)) {
113+
if ($line -match '^(\d+)\.' -and [int]$Matches[1] -ge 10) { return $true }
73114
}
115+
return $false
74116
}
75-
if (-not $has10OrLater) {
76-
Fail @"
77-
.NET 10+ SDK not detected — Reactor requires 10 or later.
78-
Installed SDKs:
79-
$($sdkOutput -join "`n")
80-
Install the latest .NET SDK from https://dotnet.microsoft.com/download and re-run ./bootstrap.ps1.
81-
"@
117+
118+
if (-not (Test-DotnetSdk10)) {
119+
if (-not (Get-Command dotnet -ErrorAction SilentlyContinue)) {
120+
Write-Host " [info] dotnet not found on PATH." -ForegroundColor Yellow
121+
} else {
122+
Write-Host " [info] dotnet present but no .NET 10+ SDK detected. Installed:" -ForegroundColor Yellow
123+
& dotnet --list-sdks | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
124+
}
125+
Install-WithWinget -Id 'Microsoft.DotNet.SDK.10' -Reason '.NET 10 SDK'
126+
if (-not (Test-DotnetSdk10)) {
127+
Fail '.NET 10 SDK install reported success but `dotnet --list-sdks` still does not show a 10.x entry. Open a new shell and re-run ./bootstrap.ps1.'
128+
}
82129
}
83130
Write-Ok ".NET SDK present"
84131

132+
# Windows App SDK runtime — optional but recommended.
133+
#
134+
# The framework defaults to WindowsAppSDKSelfContained=true (see
135+
# Directory.Build.props), so builds and scaffolded apps work *without* the
136+
# machine-wide runtime — every app's bin/ output ships its own copy of WAS
137+
# native binaries from NuGet restore.
138+
#
139+
# But many devs prefer framework-dependent deployment: smaller per-app
140+
# output, faster incremental builds, and the runtime installed once on the
141+
# machine. For that path the user needs the WindowsAppRuntime 2.0 install
142+
# matching our WindowsAppSDKVersion=2.0.1.
143+
#
144+
# So we prompt by default. `-InstallWinAppSdk` to force-install,
145+
# `-InstallWinAppSdk:$false` to skip the prompt non-interactively.
146+
147+
function Test-WindowsAppRuntime20 {
148+
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { return $true } # nothing we can check without winget
149+
& winget list --id Microsoft.WindowsAppRuntime.2.0 --exact 2>$null | Out-Null
150+
return ($LASTEXITCODE -eq 0)
151+
}
152+
153+
if (-not (Test-WindowsAppRuntime20)) {
154+
$shouldInstall = $false
155+
if ($null -ne $InstallWinAppSdk) {
156+
$shouldInstall = [bool]$InstallWinAppSdk
157+
if (-not $shouldInstall) {
158+
Write-Host ' [skip] Windows App Runtime 2.0 not installed (skipped per -InstallWinAppSdk:$false).' -ForegroundColor Yellow
159+
}
160+
} else {
161+
Write-Host ''
162+
Write-Host ' Windows App Runtime 2.0 is not installed on this machine.' -ForegroundColor Yellow
163+
Write-Host ' Reactor builds default to WindowsAppSDKSelfContained=true, so this is optional —'
164+
Write-Host ' your apps will work either way. Installing it enables framework-dependent'
165+
Write-Host ' deployment (smaller per-app output, faster builds) when you override'
166+
Write-Host ' WindowsAppSDKSelfContained=false in a consuming project.'
167+
$answer = Read-Host ' Install Windows App Runtime 2.0 via winget now? [y/N]'
168+
$shouldInstall = ($answer -match '^[Yy]')
169+
if (-not $shouldInstall) {
170+
Write-Host " Skipped. Re-run later with: winget install Microsoft.WindowsAppRuntime.2.0" -ForegroundColor Cyan
171+
}
172+
}
173+
if ($shouldInstall) {
174+
Install-WithWinget -Id 'Microsoft.WindowsAppRuntime.2.0' -Reason 'Windows App Runtime 2.0'
175+
}
176+
} else {
177+
Write-Ok 'Windows App Runtime 2.0 installed'
178+
}
179+
85180
# ---------------------------------------------------------------------------
86181
# 2. Pack `mur` as a global-tool nupkg
87182
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)