From d57dcb56339f77789be780859c7cb4caa7d929e8 Mon Sep 17 00:00:00 2001 From: drakezhang Date: Wed, 9 Sep 2026 19:24:39 +0800 Subject: [PATCH] fix(installer): correct Windows paths and release checks --- install.ps1 | 51 +++++++------ scripts/install-windows.test.ps1 | 120 ++++++++++++++++++++++++++++--- 2 files changed, 138 insertions(+), 33 deletions(-) diff --git a/install.ps1 b/install.ps1 index a344a5c..5fb4a79 100644 --- a/install.ps1 +++ b/install.ps1 @@ -3,7 +3,7 @@ install.ps1 — install the bsk CLI on Windows from GitHub Releases. .DESCRIPTION -Downloads the latest (or pinned) bsk release for Windows x64 or ARM64, +Downloads the latest (or pinned) bsk release for Windows x64, extracts bsk.exe to a user-local directory, and adds it to PATH. Usage: @@ -73,25 +73,21 @@ function Add-ToUserPath { $currentUserPath = @([Environment]::GetEnvironmentVariable("PATH", "User") -split ";" | Where-Object { $_ }) - if ($currentUserPath -contains $Dir) { - Write-Log "$Dir is already in your user PATH" + $newUserPath = (@($Dir) + @($currentUserPath | Where-Object { $_ -ine $Dir })) -join ";" + if (($currentUserPath -join ";") -ceq $newUserPath) { + Write-Log "$Dir is already first in your user PATH" return } - $newUserPath = ($currentUserPath + $Dir) -join ";" [Environment]::SetEnvironmentVariable("PATH", $newUserPath, "User") - Write-Log "added ${Dir} to user PATH" + Write-Log "placed ${Dir} first in user PATH" } function Add-ToSessionPath { param([string]$Dir) - $pathEntries = $env:PATH -split ";" | Where-Object { $_ } - if ($pathEntries -contains $Dir) { - return - } - - $env:PATH = "$Dir;$env:PATH" + $pathEntries = @($env:PATH -split ";" | Where-Object { $_ -and $_ -ine $Dir }) + $env:PATH = (@($Dir) + $pathEntries) -join ";" } # ── Git Bash (bash environment) PATH helper ────────────────────────────────── @@ -173,14 +169,20 @@ function Main { Write-Log "latest version is ${version}" } + $platformKey = $platform.PlatformKey + $asset = $null + if ($manifest -and $manifest.assets) { + $asset = $manifest.assets.$platformKey + } + # ARM64 is not in the current release matrix. Require a published entry + # before attempting it, while preserving legacy x64 installs without a manifest. + if ($platform.ArchId -eq "arm64" -and -not $asset) { + Write-Die "version.json does not list a Windows ARM64 package for bsk $version" + } + $archiveName = "bsk-v${version}-$($platform.TargetTriple).zip" $downloadUrl = "${GitHub}/releases/download/${tag}/${archiveName}" - - $expectedSha = $null - $platformKey = $platform.PlatformKey - if ($manifest -and $manifest.assets) { - $expectedSha = $manifest.assets.$platformKey.sha256 - } + $expectedSha = if ($asset) { $asset.sha256 } else { $null } if (-not $expectedSha) { if (-not $manifest) { Write-Log "warning: could not fetch version.json; skipping checksum verification" @@ -201,7 +203,7 @@ function Main { if ($expectedSha) { Write-Log "verifying checksum" - $actualSha = (Get-FileHash -Algorithm SHA256 -Path $archivePath).Hash + $actualSha = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash if ($actualSha -ieq $expectedSha) { Write-Log "checksum OK" } @@ -211,13 +213,13 @@ function Main { } Write-Log "extracting ${archiveName}" - Expand-Archive -Path $archivePath -DestinationPath $tempDir -Force + Expand-Archive -LiteralPath $archivePath -DestinationPath $tempDir -Force - if (-not (Test-Path (Join-Path $tempDir "bsk.exe"))) { + if (-not (Test-Path -LiteralPath (Join-Path $tempDir "bsk.exe"))) { Write-Die "bsk.exe not found in archive" } - if (-not (Test-Path $InstallDir)) { + if (-not (Test-Path -LiteralPath $InstallDir)) { New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null } @@ -239,12 +241,17 @@ function Main { & $bskPath --version if ($LASTEXITCODE -ne 0) { throw "installed bsk failed verification" } + $command = Get-Command bsk -ErrorAction SilentlyContinue + if (-not $command -or $command.CommandType -ne "Application" -or $command.Source -ine $bskPath) { + Write-Log "warning: 'bsk' does not resolve to $bskPath; check Get-Command bsk -All for a conflicting command" + } + Write-Log "done" Write-Host "" Write-Host "Open a new terminal (PowerShell / Git Bash) for PATH changes to take full effect." } finally { - Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue + Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue } } diff --git a/scripts/install-windows.test.ps1 b/scripts/install-windows.test.ps1 index 0555d23..190ca26 100644 --- a/scripts/install-windows.test.ps1 +++ b/scripts/install-windows.test.ps1 @@ -1,11 +1,11 @@ #Requires -Version 5.1 param( [string]$BskPath = (Join-Path $PSScriptRoot "../target/debug/bsk.exe"), - [switch]$ArchitectureOnly + [switch]$HelpersOnly ) $ErrorActionPreference = "Stop" -# Load definitions, never Main: no downloads or changes to the real user PATH. +# Load definitions without running Main. Registry PATH writes are isolated below. $installer = Join-Path $PSScriptRoot "../install.ps1" $tokens = $null $errors = $null @@ -69,22 +69,38 @@ function Assert-Fails([scriptblock]$Action) { $env:PROCESSOR_ARCHITEW6432 = $oldNativeArch } } -if ($ArchitectureOnly) { return } - $dir = 'C:\Users\Alice\.local\bin' -foreach ($existing in @('', 'C:\WindowsApps', 'C:\A;C:\B')) { - $script:UserPath = $existing - Add-ToUserPath $dir - $expected = if ($existing) { "$existing;$dir" } else { $dir } - Assert-Equal $script:UserPath $expected - Add-ToUserPath $dir - Assert-Equal $script:UserPath $expected +$pathCases = @( + @{ Existing = ''; Expected = $dir } + @{ Existing = 'C:\WindowsApps'; Expected = "$dir;C:\WindowsApps" } + @{ Existing = 'C:\A;C:\B'; Expected = "$dir;C:\A;C:\B" } + @{ Existing = "C:\old-bsk;$dir;C:\B;$($dir.ToUpperInvariant())"; Expected = "$dir;C:\old-bsk;C:\B" } +) +$oldPath = $env:PATH +try { + foreach ($case in $pathCases) { + $script:UserPath = $case.Existing + $env:PATH = $case.Existing + foreach ($attempt in 1..2) { + Add-ToUserPath $dir + Add-ToSessionPath $dir + Assert-Equal $script:UserPath $case.Expected + Assert-Equal $env:PATH $case.Expected + } + } +} finally { $env:PATH = $oldPath } +if ($HelpersOnly) { + Write-Host "Windows installer helper regressions passed ($($PSVersionTable.PSVersion))" + return } $root = Join-Path ([IO.Path]::GetTempPath()) ("bsk-install-test-" + [Guid]::NewGuid().ToString('N')) [IO.Directory]::CreateDirectory($root) | Out-Null $oldBskHome = $env:BSK_HOME $oldAutoUpdate = $env:BSK_AUTO_UPDATE +$oldVersion = $env:BSK_VERSION +$oldProcessArch = $env:PROCESSOR_ARCHITECTURE +$oldNativeArch = $env:PROCESSOR_ARCHITEW6432 $daemon = $null try { $utf8 = New-Object System.Text.UTF8Encoding($false) @@ -180,6 +196,84 @@ try { try { Assert-Fails { Install-Binary $source $target } } finally { $lock.Dispose() } Assert-Equal (Get-FileHash -LiteralPath $target).Hash $before if (@(Get-ChildItem -LiteralPath $targetDir -Filter "*.install-*").Count) { throw "staging files leaked" } + + # Exercise Main with real ZIPs/executables, isolating network and user state. + & { + $fixtureTemp = Join-Path $root 'temp [literal]' + [IO.Directory]::CreateDirectory($fixtureTemp) | Out-Null + $mainDefinition = ($ast.EndBlock.Statements | Where-Object { + $_ -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $_.Name -eq 'Main' + }).Extent.Text.Replace('[System.IO.Path]::GetTempPath()', '$fixtureTemp') + Invoke-Expression $mainDefinition + function Write-Die([string]$Message) { throw $Message } + $bashProfile = (Get-Command Add-ToBashProfile).ScriptBlock + function Add-ToBashProfile([string]$Dir) { + & $bashProfile $Dir (Join-Path $root 'flow.bashrc') + } + $fixtureArchive = Join-Path $root 'release.zip' + Compress-Archive -LiteralPath $source -DestinationPath $fixtureArchive -CompressionLevel Fastest + $asset = @{ sha256 = (Get-FileHash -LiteralPath $fixtureArchive).Hash } + $fixtureManifest = @{ version = '9.8.7'; assets = @{ 'windows-x64' = $asset } } + $downloads = New-Object 'System.Collections.Generic.List[string]' + function Invoke-RestMethod([string]$Uri) { + if (-not $fixtureManifest) { throw 'fixture manifest unavailable' } + return $fixtureManifest + } + function Invoke-WebRequest { + [CmdletBinding()] + param([string]$Uri, [string]$OutFile, [switch]$UseBasicParsing) + $downloads.Add($Uri) + [IO.File]::Copy($fixtureArchive, $OutFile) + } + $GitHub = 'https://example.invalid/fixture' + $InstallDir = Join-Path $root 'installed [literal]' + $installed = Join-Path $InstallDir 'bsk.exe' + $env:BSK_VERSION = $null + $env:PROCESSOR_ARCHITECTURE = 'AMD64' + $env:PROCESSOR_ARCHITEW6432 = $null + $script:UserPath = "$targetDir;$InstallDir;$($InstallDir.ToUpperInvariant())" + $env:PATH = "$targetDir;$InstallDir;$oldPath" + Assert-Equal (Get-Command bsk -CommandType Application).Source $target + Main + Assert-Equal $downloads.Count 1 + Assert-Equal (Get-Command bsk -CommandType Application).Source $installed + Assert-Equal $script:UserPath "$InstallDir;$targetDir" + Assert-Equal (Get-FileHash -LiteralPath $installed).Hash (Get-FileHash -LiteralPath $source).Hash + Assert-Equal @(Get-ChildItem -LiteralPath $fixtureTemp -Force).Count 0 + + # ARM64 must be listed in the manifest before any archive is requested. + $env:PROCESSOR_ARCHITECTURE = 'ARM64' + $downloads.Clear() + $message = $null + try { Main } catch { $message = $_.Exception.Message } + Assert-Equal $message 'version.json does not list a Windows ARM64 package for bsk 9.8.7' + Assert-Equal $downloads.Count 0 + $env:BSK_VERSION = '9.8.7' + $fixtureManifest = $null + $message = $null + try { Main } catch { $message = $_.Exception.Message } + Assert-Equal $message 'version.json does not list a Windows ARM64 package for bsk 9.8.7' + Assert-Equal $downloads.Count 0 + + # Keep pinned x64 installs compatible with older releases without manifests. + $env:PROCESSOR_ARCHITECTURE = 'AMD64' + Main + Assert-Equal $downloads.Count 1 + Assert-Equal @(Get-ChildItem -LiteralPath $fixtureTemp -Force).Count 0 + + # A listed ARM64 archive remains selectable (the fixture uses the host EXE). + $fixtureManifest = @{ version = '9.8.7'; assets = @{ 'windows-arm64' = $asset } } + $env:PROCESSOR_ARCHITECTURE = 'ARM64' + Main + Assert-Equal $downloads[$downloads.Count - 1] "$GitHub/releases/download/cli-v9.8.7/bsk-v9.8.7-aarch64-pc-windows-msvc.zip" + + # A checksum failure must clean the bracketed temp path and preserve the install. + $before = (Get-FileHash -LiteralPath $installed).Hash + $fixtureManifest.assets['windows-arm64'] = @{ sha256 = '0' * 64 } + Assert-Fails { Main } + Assert-Equal (Get-FileHash -LiteralPath $installed).Hash $before + Assert-Equal @(Get-ChildItem -LiteralPath $fixtureTemp -Force).Count 0 + } Write-Host "Windows installer regressions passed ($($PSVersionTable.PSVersion))" } catch { @@ -194,6 +288,10 @@ finally { } $env:BSK_HOME = $oldBskHome $env:BSK_AUTO_UPDATE = $oldAutoUpdate + $env:BSK_VERSION = $oldVersion + $env:PROCESSOR_ARCHITECTURE = $oldProcessArch + $env:PROCESSOR_ARCHITEW6432 = $oldNativeArch + $env:PATH = $oldPath Remove-Item Env:BSK_TEST_RC -ErrorAction SilentlyContinue # Resolve and verify before recursive cleanup; only this fixture is removed. $resolvedRoot = [IO.Path]::GetFullPath($root)