fix(installer): correct Windows paths and release checks

This commit is contained in:
drakezhang
2026-09-09 19:24:39 +08:00
parent 3d1d7b5b87
commit d57dcb5633
2 changed files with 138 additions and 33 deletions
+29 -22
View File
@@ -3,7 +3,7 @@
install.ps1 — install the bsk CLI on Windows from GitHub Releases. install.ps1 — install the bsk CLI on Windows from GitHub Releases.
.DESCRIPTION .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. extracts bsk.exe to a user-local directory, and adds it to PATH.
Usage: Usage:
@@ -73,25 +73,21 @@ function Add-ToUserPath {
$currentUserPath = @([Environment]::GetEnvironmentVariable("PATH", "User") -split ";" | Where-Object { $_ }) $currentUserPath = @([Environment]::GetEnvironmentVariable("PATH", "User") -split ";" | Where-Object { $_ })
if ($currentUserPath -contains $Dir) { $newUserPath = (@($Dir) + @($currentUserPath | Where-Object { $_ -ine $Dir })) -join ";"
Write-Log "$Dir is already in your user PATH" if (($currentUserPath -join ";") -ceq $newUserPath) {
Write-Log "$Dir is already first in your user PATH"
return return
} }
$newUserPath = ($currentUserPath + $Dir) -join ";"
[Environment]::SetEnvironmentVariable("PATH", $newUserPath, "User") [Environment]::SetEnvironmentVariable("PATH", $newUserPath, "User")
Write-Log "added ${Dir} to user PATH" Write-Log "placed ${Dir} first in user PATH"
} }
function Add-ToSessionPath { function Add-ToSessionPath {
param([string]$Dir) param([string]$Dir)
$pathEntries = $env:PATH -split ";" | Where-Object { $_ } $pathEntries = @($env:PATH -split ";" | Where-Object { $_ -and $_ -ine $Dir })
if ($pathEntries -contains $Dir) { $env:PATH = (@($Dir) + $pathEntries) -join ";"
return
}
$env:PATH = "$Dir;$env:PATH"
} }
# ── Git Bash (bash environment) PATH helper ────────────────────────────────── # ── Git Bash (bash environment) PATH helper ──────────────────────────────────
@@ -173,14 +169,20 @@ function Main {
Write-Log "latest version is ${version}" 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" $archiveName = "bsk-v${version}-$($platform.TargetTriple).zip"
$downloadUrl = "${GitHub}/releases/download/${tag}/${archiveName}" $downloadUrl = "${GitHub}/releases/download/${tag}/${archiveName}"
$expectedSha = if ($asset) { $asset.sha256 } else { $null }
$expectedSha = $null
$platformKey = $platform.PlatformKey
if ($manifest -and $manifest.assets) {
$expectedSha = $manifest.assets.$platformKey.sha256
}
if (-not $expectedSha) { if (-not $expectedSha) {
if (-not $manifest) { if (-not $manifest) {
Write-Log "warning: could not fetch version.json; skipping checksum verification" Write-Log "warning: could not fetch version.json; skipping checksum verification"
@@ -201,7 +203,7 @@ function Main {
if ($expectedSha) { if ($expectedSha) {
Write-Log "verifying checksum" Write-Log "verifying checksum"
$actualSha = (Get-FileHash -Algorithm SHA256 -Path $archivePath).Hash $actualSha = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash
if ($actualSha -ieq $expectedSha) { if ($actualSha -ieq $expectedSha) {
Write-Log "checksum OK" Write-Log "checksum OK"
} }
@@ -211,13 +213,13 @@ function Main {
} }
Write-Log "extracting ${archiveName}" 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" 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 New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
} }
@@ -239,12 +241,17 @@ function Main {
& $bskPath --version & $bskPath --version
if ($LASTEXITCODE -ne 0) { throw "installed bsk failed verification" } 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-Log "done"
Write-Host "" Write-Host ""
Write-Host "Open a new terminal (PowerShell / Git Bash) for PATH changes to take full effect." Write-Host "Open a new terminal (PowerShell / Git Bash) for PATH changes to take full effect."
} }
finally { finally {
Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
} }
} }
+109 -11
View File
@@ -1,11 +1,11 @@
#Requires -Version 5.1 #Requires -Version 5.1
param( param(
[string]$BskPath = (Join-Path $PSScriptRoot "../target/debug/bsk.exe"), [string]$BskPath = (Join-Path $PSScriptRoot "../target/debug/bsk.exe"),
[switch]$ArchitectureOnly [switch]$HelpersOnly
) )
$ErrorActionPreference = "Stop" $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" $installer = Join-Path $PSScriptRoot "../install.ps1"
$tokens = $null $tokens = $null
$errors = $null $errors = $null
@@ -69,22 +69,38 @@ function Assert-Fails([scriptblock]$Action) {
$env:PROCESSOR_ARCHITEW6432 = $oldNativeArch $env:PROCESSOR_ARCHITEW6432 = $oldNativeArch
} }
} }
if ($ArchitectureOnly) { return }
$dir = 'C:\Users\Alice\.local\bin' $dir = 'C:\Users\Alice\.local\bin'
foreach ($existing in @('', 'C:\WindowsApps', 'C:\A;C:\B')) { $pathCases = @(
$script:UserPath = $existing @{ Existing = ''; Expected = $dir }
Add-ToUserPath $dir @{ Existing = 'C:\WindowsApps'; Expected = "$dir;C:\WindowsApps" }
$expected = if ($existing) { "$existing;$dir" } else { $dir } @{ Existing = 'C:\A;C:\B'; Expected = "$dir;C:\A;C:\B" }
Assert-Equal $script:UserPath $expected @{ Existing = "C:\old-bsk;$dir;C:\B;$($dir.ToUpperInvariant())"; Expected = "$dir;C:\old-bsk;C:\B" }
Add-ToUserPath $dir )
Assert-Equal $script:UserPath $expected $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')) $root = Join-Path ([IO.Path]::GetTempPath()) ("bsk-install-test-" + [Guid]::NewGuid().ToString('N'))
[IO.Directory]::CreateDirectory($root) | Out-Null [IO.Directory]::CreateDirectory($root) | Out-Null
$oldBskHome = $env:BSK_HOME $oldBskHome = $env:BSK_HOME
$oldAutoUpdate = $env:BSK_AUTO_UPDATE $oldAutoUpdate = $env:BSK_AUTO_UPDATE
$oldVersion = $env:BSK_VERSION
$oldProcessArch = $env:PROCESSOR_ARCHITECTURE
$oldNativeArch = $env:PROCESSOR_ARCHITEW6432
$daemon = $null $daemon = $null
try { try {
$utf8 = New-Object System.Text.UTF8Encoding($false) $utf8 = New-Object System.Text.UTF8Encoding($false)
@@ -180,6 +196,84 @@ try {
try { Assert-Fails { Install-Binary $source $target } } finally { $lock.Dispose() } try { Assert-Fails { Install-Binary $source $target } } finally { $lock.Dispose() }
Assert-Equal (Get-FileHash -LiteralPath $target).Hash $before Assert-Equal (Get-FileHash -LiteralPath $target).Hash $before
if (@(Get-ChildItem -LiteralPath $targetDir -Filter "*.install-*").Count) { throw "staging files leaked" } 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))" Write-Host "Windows installer regressions passed ($($PSVersionTable.PSVersion))"
} }
catch { catch {
@@ -194,6 +288,10 @@ finally {
} }
$env:BSK_HOME = $oldBskHome $env:BSK_HOME = $oldBskHome
$env:BSK_AUTO_UPDATE = $oldAutoUpdate $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 Remove-Item Env:BSK_TEST_RC -ErrorAction SilentlyContinue
# Resolve and verify before recursive cleanup; only this fixture is removed. # Resolve and verify before recursive cleanup; only this fixture is removed.
$resolvedRoot = [IO.Path]::GetFullPath($root) $resolvedRoot = [IO.Path]::GetFullPath($root)