|
| 1 | +<# |
| 2 | +.SYNOPSIS |
| 3 | + Prepares a Windows checkout for OpenClaw developer and agent work. |
| 4 | +
|
| 5 | +.DESCRIPTION |
| 6 | + Installs missing local prerequisites with winget, refreshes the current |
| 7 | + process PATH, trusts the checkout for GitVersion, and runs the repository |
| 8 | + prerequisite check. Use -CheckOnly to report what is missing without |
| 9 | + installing anything. |
| 10 | +
|
| 11 | +.PARAMETER CheckOnly |
| 12 | + Check prerequisites without installing or changing git safe.directory. |
| 13 | +
|
| 14 | +.PARAMETER RunValidation |
| 15 | + After setup, run the full build plus the required shared and tray test |
| 16 | + projects used by AGENTS.md closeout validation. |
| 17 | +
|
| 18 | +.PARAMETER NoTrustRepository |
| 19 | + Do not add this checkout to git safe.directory. |
| 20 | +
|
| 21 | +.EXAMPLE |
| 22 | + .\scripts\setup-dev.ps1 |
| 23 | + .\scripts\setup-dev.ps1 -CheckOnly |
| 24 | + .\scripts\setup-dev.ps1 -RunValidation |
| 25 | +#> |
| 26 | + |
| 27 | +param( |
| 28 | + [switch]$CheckOnly, |
| 29 | + [switch]$RunValidation, |
| 30 | + [switch]$NoTrustRepository |
| 31 | +) |
| 32 | + |
| 33 | +$ErrorActionPreference = "Stop" |
| 34 | + |
| 35 | +$repoRoot = Split-Path -Parent $PSScriptRoot |
| 36 | +Set-Location $repoRoot |
| 37 | + |
| 38 | +function Write-Header($text) { Write-Host "`n=== $text ===" -ForegroundColor Cyan } |
| 39 | +function Write-Success($text) { Write-Host "[OK] $text" -ForegroundColor Green } |
| 40 | +function Write-WarningMessage($text) { Write-Host "[WARN] $text" -ForegroundColor Yellow } |
| 41 | +function Write-ErrorMessage($text) { Write-Host "[ERROR] $text" -ForegroundColor Red } |
| 42 | +function Write-Info($text) { Write-Host " $text" -ForegroundColor Gray } |
| 43 | + |
| 44 | +function Test-WindowsHost { |
| 45 | + $isWindowsVariable = Get-Variable -Name IsWindows -ErrorAction SilentlyContinue |
| 46 | + if ($isWindowsVariable) { |
| 47 | + return [bool]$isWindowsVariable.Value |
| 48 | + } |
| 49 | + |
| 50 | + return [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT |
| 51 | +} |
| 52 | + |
| 53 | +function Update-ProcessPath { |
| 54 | + $machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine") |
| 55 | + $userPath = [Environment]::GetEnvironmentVariable("Path", "User") |
| 56 | + $env:Path = @($machinePath, $userPath) -join ";" |
| 57 | +} |
| 58 | + |
| 59 | +function Test-CommandAvailable($name) { |
| 60 | + return $null -ne (Get-Command $name -ErrorAction SilentlyContinue) |
| 61 | +} |
| 62 | + |
| 63 | +function Test-DotNet10Sdk { |
| 64 | + if (-not (Test-CommandAvailable "dotnet")) { |
| 65 | + return $false |
| 66 | + } |
| 67 | + |
| 68 | + $sdks = & dotnet --list-sdks 2>$null |
| 69 | + return $LASTEXITCODE -eq 0 -and ($sdks | Where-Object { $_ -match "^10\." }) |
| 70 | +} |
| 71 | + |
| 72 | +function Test-NodeAndNpm { |
| 73 | + return (Test-CommandAvailable "node") -and (Test-CommandAvailable "npm") |
| 74 | +} |
| 75 | + |
| 76 | +function Get-WindowsSdkVersion { |
| 77 | + $windowsSdkPath = "${env:ProgramFiles(x86)}\Windows Kits\10\Include" |
| 78 | + if (-not (Test-Path $windowsSdkPath)) { |
| 79 | + return $null |
| 80 | + } |
| 81 | + |
| 82 | + $versions = @( |
| 83 | + Get-ChildItem $windowsSdkPath -Directory | |
| 84 | + Where-Object { $_.Name -match "^\d+\.\d+\.\d+\.\d+$" } | |
| 85 | + Sort-Object { [version]$_.Name } -Descending | |
| 86 | + Select-Object -ExpandProperty Name |
| 87 | + ) |
| 88 | + |
| 89 | + if ($versions.Count -eq 0) { |
| 90 | + return $null |
| 91 | + } |
| 92 | + |
| 93 | + return $versions[0] |
| 94 | +} |
| 95 | + |
| 96 | +function Get-WebView2RuntimeVersion { |
| 97 | + $keys = @( |
| 98 | + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}", |
| 99 | + "HKCU:\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" |
| 100 | + ) |
| 101 | + |
| 102 | + foreach ($key in $keys) { |
| 103 | + if (Test-Path $key) { |
| 104 | + $version = (Get-ItemProperty $key -ErrorAction SilentlyContinue).pv |
| 105 | + if ($version) { |
| 106 | + return $version |
| 107 | + } |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + return $null |
| 112 | +} |
| 113 | + |
| 114 | +function Install-WingetPackage($id, $displayName) { |
| 115 | + if ($CheckOnly) { |
| 116 | + Write-WarningMessage "$displayName is missing." |
| 117 | + Write-Info "Install with: winget install --id $id -e" |
| 118 | + return |
| 119 | + } |
| 120 | + |
| 121 | + if (-not (Test-CommandAvailable "winget")) { |
| 122 | + throw "winget is not available. Install App Installer from the Microsoft Store, then rerun this script." |
| 123 | + } |
| 124 | + |
| 125 | + Write-Header "Installing $displayName" |
| 126 | + $arguments = @( |
| 127 | + "install", |
| 128 | + "--id", $id, |
| 129 | + "-e", |
| 130 | + "--accept-source-agreements", |
| 131 | + "--accept-package-agreements", |
| 132 | + "--disable-interactivity" |
| 133 | + ) |
| 134 | + & winget @arguments |
| 135 | + if ($LASTEXITCODE -ne 0) { |
| 136 | + throw "winget failed to install $displayName ($id)." |
| 137 | + } |
| 138 | + |
| 139 | + Update-ProcessPath |
| 140 | +} |
| 141 | + |
| 142 | +function ConvertTo-GitSafeDirectoryPath($path) { |
| 143 | + return ([System.IO.Path]::GetFullPath($path).TrimEnd("\") -replace "\\", "/") |
| 144 | +} |
| 145 | + |
| 146 | +function Test-GitSafeDirectoryContains($path) { |
| 147 | + if (-not (Test-CommandAvailable "git")) { |
| 148 | + return $false |
| 149 | + } |
| 150 | + |
| 151 | + $expected = (ConvertTo-GitSafeDirectoryPath $path).ToLowerInvariant() |
| 152 | + $safeDirectories = & git config --global --get-all safe.directory 2>$null |
| 153 | + if ($LASTEXITCODE -ne 0 -or -not $safeDirectories) { |
| 154 | + return $false |
| 155 | + } |
| 156 | + |
| 157 | + foreach ($safeDirectory in $safeDirectories) { |
| 158 | + if ($safeDirectory -eq "*") { |
| 159 | + return $true |
| 160 | + } |
| 161 | + |
| 162 | + $normalized = ($safeDirectory.Trim().TrimEnd("\", "/") -replace "\\", "/").ToLowerInvariant() |
| 163 | + if ($normalized -eq $expected) { |
| 164 | + return $true |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + return $false |
| 169 | +} |
| 170 | + |
| 171 | +function Ensure-RepositoryTrust { |
| 172 | + if ($NoTrustRepository -or $CheckOnly -or -not (Test-CommandAvailable "git")) { |
| 173 | + return |
| 174 | + } |
| 175 | + |
| 176 | + if (-not (Test-Path (Join-Path $repoRoot ".git"))) { |
| 177 | + return |
| 178 | + } |
| 179 | + |
| 180 | + if (Test-GitSafeDirectoryContains $repoRoot) { |
| 181 | + Write-Success "Repository already trusted for GitVersion." |
| 182 | + return |
| 183 | + } |
| 184 | + |
| 185 | + $safeDirectory = ConvertTo-GitSafeDirectoryPath $repoRoot |
| 186 | + Write-Info "Adding git safe.directory entry: $safeDirectory" |
| 187 | + & git config --global --add safe.directory $safeDirectory |
| 188 | + if ($LASTEXITCODE -ne 0) { |
| 189 | + throw "Failed to add git safe.directory entry for $safeDirectory." |
| 190 | + } |
| 191 | + Write-Success "Repository trusted for GitVersion." |
| 192 | +} |
| 193 | + |
| 194 | +function Require-Prerequisite($name, $isAvailable, $packageId) { |
| 195 | + if ($isAvailable) { |
| 196 | + Write-Success "$name detected." |
| 197 | + return |
| 198 | + } |
| 199 | + |
| 200 | + Install-WingetPackage $packageId $name |
| 201 | +} |
| 202 | + |
| 203 | +if (-not (Test-WindowsHost)) { |
| 204 | + throw "OpenClaw Windows development requires Windows." |
| 205 | +} |
| 206 | + |
| 207 | +Write-Header "OpenClaw developer setup" |
| 208 | +if ($CheckOnly) { |
| 209 | + Write-Info "CheckOnly mode: no packages will be installed and git safe.directory will not be changed." |
| 210 | +} |
| 211 | + |
| 212 | +Update-ProcessPath |
| 213 | + |
| 214 | +Require-Prerequisite "Git" (Test-CommandAvailable "git") "Git.Git" |
| 215 | +Require-Prerequisite ".NET 10 SDK" (Test-DotNet10Sdk) "Microsoft.DotNet.SDK.10" |
| 216 | +Require-Prerequisite "Node.js LTS with npm" (Test-NodeAndNpm) "OpenJS.NodeJS.LTS" |
| 217 | +Require-Prerequisite "Windows SDK 10.0.26100" ([bool](Get-WindowsSdkVersion)) "Microsoft.WindowsSDK.10.0.26100" |
| 218 | + |
| 219 | +$webView2Version = Get-WebView2RuntimeVersion |
| 220 | +if ($webView2Version) { |
| 221 | + Write-Success "WebView2 Runtime detected ($webView2Version)." |
| 222 | +} else { |
| 223 | + Install-WingetPackage "Microsoft.EdgeWebView2Runtime" "WebView2 Runtime" |
| 224 | +} |
| 225 | + |
| 226 | +Update-ProcessPath |
| 227 | +Ensure-RepositoryTrust |
| 228 | + |
| 229 | +$missing = @() |
| 230 | +if (-not (Test-CommandAvailable "git")) { $missing += "Git" } |
| 231 | +if (-not (Test-DotNet10Sdk)) { $missing += ".NET 10 SDK" } |
| 232 | +if (-not (Test-NodeAndNpm)) { $missing += "Node.js LTS with npm" } |
| 233 | +if (-not (Get-WindowsSdkVersion)) { $missing += "Windows SDK 10.0.26100" } |
| 234 | +if (-not (Get-WebView2RuntimeVersion)) { $missing += "WebView2 Runtime" } |
| 235 | + |
| 236 | +if ($missing.Count -gt 0) { |
| 237 | + Write-ErrorMessage "Setup is incomplete:" |
| 238 | + foreach ($item in $missing) { |
| 239 | + Write-Info "- $item" |
| 240 | + } |
| 241 | + |
| 242 | + if (-not $CheckOnly) { |
| 243 | + Write-Info "If packages were just installed, open a new terminal and rerun .\scripts\setup-dev.ps1 -CheckOnly." |
| 244 | + } |
| 245 | + exit 1 |
| 246 | +} |
| 247 | + |
| 248 | +Write-Header "Repository prerequisite check" |
| 249 | +& "$repoRoot\build.ps1" -CheckOnly |
| 250 | +if ($LASTEXITCODE -ne 0) { |
| 251 | + exit $LASTEXITCODE |
| 252 | +} |
| 253 | + |
| 254 | +if ($RunValidation) { |
| 255 | + Write-Header "Required validation" |
| 256 | + $env:OPENCLAW_REPO_ROOT = $repoRoot |
| 257 | + |
| 258 | + & "$repoRoot\build.ps1" |
| 259 | + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } |
| 260 | + |
| 261 | + dotnet build "$repoRoot\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj" |
| 262 | + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } |
| 263 | + |
| 264 | + dotnet build "$repoRoot\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj" |
| 265 | + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } |
| 266 | + |
| 267 | + dotnet test "$repoRoot\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj" --no-restore |
| 268 | + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } |
| 269 | + |
| 270 | + dotnet test "$repoRoot\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj" --no-restore |
| 271 | + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } |
| 272 | +} |
| 273 | + |
| 274 | +Write-Success "OpenClaw developer setup is ready." |
0 commit comments