fix(installer): complete literal paths and recovery guidance

This commit is contained in:
drakezhang
2026-09-09 23:22:42 +08:00
parent defa54293b
commit f995620b53
2 changed files with 35 additions and 16 deletions
+16 -9
View File
@@ -131,8 +131,13 @@ function Install-Binary {
# The running daemon may come from another installation directory. # The running daemon may come from another installation directory.
# Use the downloaded CLI: older versions have broken Windows liveness checks. # Use the downloaded CLI: older versions have broken Windows liveness checks.
# Stop verifies daemon identity and uses the current BSK_HOME. # Stop verifies daemon identity and uses the current BSK_HOME.
# Fail closed for both new installs and replacements; never discard daemon metadata here.
& $Source daemon stop & $Source daemon stop
if ($LASTEXITCODE -ne 0) { throw "could not stop bsk daemon; installation was not changed" } if ($LASTEXITCODE -ne 0) {
$daemonHome = if ($env:BSK_HOME) { $env:BSK_HOME } else { Join-Path $HOME ".bsk" }
$daemonInfoPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath((Join-Path $daemonHome "daemon.json"))
throw "could not stop bsk daemon; installation was not changed. Stop any running bsk daemon, remove '$daemonInfoPath', then retry the installer."
}
if ([System.IO.File]::Exists($Target)) { if ([System.IO.File]::Exists($Target)) {
# PowerShell 5.1 converts $null to an empty path for string parameters. # PowerShell 5.1 converts $null to an empty path for string parameters.
[System.IO.File]::Replace($staged, $Target, [NullString]::Value) [System.IO.File]::Replace($staged, $Target, [NullString]::Value)
@@ -140,7 +145,6 @@ function Install-Binary {
else { else {
[System.IO.File]::Move($staged, $Target) [System.IO.File]::Move($staged, $Target)
} }
Write-Log "daemon will restart automatically on the next browser command"
} }
finally { finally {
if ([System.IO.File]::Exists($staged)) { [System.IO.File]::Delete($staged) } if ([System.IO.File]::Exists($staged)) { [System.IO.File]::Delete($staged) }
@@ -193,13 +197,17 @@ function Main {
} }
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName()) $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null [System.IO.Directory]::CreateDirectory($tempDir) | Out-Null
try { try {
$archivePath = Join-Path $tempDir $archiveName $archivePath = Join-Path $tempDir $archiveName
Write-Log "downloading ${downloadUrl}" Write-Log "downloading ${downloadUrl}"
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath -UseBasicParsing -ErrorAction Stop # PowerShell 5.1's -OutFile treats brackets as wildcards. Write raw HTTP bytes literally.
$response = Invoke-WebRequest -Uri $downloadUrl -UseBasicParsing -ErrorAction Stop
try {
[System.IO.File]::WriteAllBytes($archivePath, $response.RawContentStream.ToArray())
} finally { $response.RawContentStream.Dispose() }
if ($expectedSha) { if ($expectedSha) {
Write-Log "verifying checksum" Write-Log "verifying checksum"
@@ -221,9 +229,7 @@ function Main {
Write-Die "bsk.exe not found in archive" Write-Die "bsk.exe not found in archive"
} }
if (-not (Test-Path -LiteralPath $InstallDir)) { [System.IO.Directory]::CreateDirectory($InstallDir) | Out-Null
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
}
Install-Binary -Source (Join-Path $tempDir "bsk.exe") -Target (Join-Path $InstallDir "bsk.exe") Install-Binary -Source (Join-Path $tempDir "bsk.exe") -Target (Join-Path $InstallDir "bsk.exe")
@@ -245,12 +251,13 @@ function Main {
$command = Get-Command bsk -ErrorAction SilentlyContinue $command = Get-Command bsk -ErrorAction SilentlyContinue
if (-not $command -or $command.CommandType -ne "Application" -or $command.Source -ine $bskPath) { 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 "warning: 'bsk' does not resolve to $bskPath in this session; 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 "This PATH check covers the current session only; a new terminal may prefer a Machine PATH entry or alias."
Write-Host "In a new PowerShell terminal, run Get-Command bsk -All and confirm the first result is $bskPath."
} }
finally { finally {
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
+19 -7
View File
@@ -24,9 +24,12 @@ foreach ($statement in $ast.EndBlock.Statements) {
function Assert-Equal($Actual, $Expected) { function Assert-Equal($Actual, $Expected) {
if ($Actual -cne $Expected) { throw "expected [$Expected], got [$Actual]" } if ($Actual -cne $Expected) { throw "expected [$Expected], got [$Actual]" }
} }
function Assert-Fails([scriptblock]$Action) { function Assert-Fails([scriptblock]$Action, [string]$MessageContains) {
$failed = $false $failed = $false
try { & $Action } catch { $failed = $true } try { & $Action } catch {
$failed = $true
if ($MessageContains -and -not $_.Exception.Message.Contains($MessageContains)) { throw }
}
if (-not $failed) { throw "expected failure" } if (-not $failed) { throw "expected failure" }
} }
@@ -127,7 +130,7 @@ try {
if ($LASTEXITCODE -ne 0) { throw "Git Bash failed" } if ($LASTEXITCODE -ne 0) { throw "Git Bash failed" }
Assert-Equal $actual ('/c' + $special.Substring(2).Replace('\', '/')) Assert-Equal $actual ('/c' + $special.Substring(2).Replace('\', '/'))
$env:BSK_HOME = Join-Path $root "home" $env:BSK_HOME = Join-Path $root "home [state]"
$env:BSK_AUTO_UPDATE = "off" $env:BSK_AUTO_UPDATE = "off"
[IO.Directory]::CreateDirectory($env:BSK_HOME) | Out-Null [IO.Directory]::CreateDirectory($env:BSK_HOME) | Out-Null
$sourceDir = Join-Path $root "download" $sourceDir = Join-Path $root "download"
@@ -137,7 +140,8 @@ try {
$targetDir = Join-Path $root "中文 space [x] & install" $targetDir = Join-Path $root "中文 space [x] & install"
[IO.Directory]::CreateDirectory($targetDir) | Out-Null [IO.Directory]::CreateDirectory($targetDir) | Out-Null
$target = Join-Path $targetDir "bsk.exe" $target = Join-Path $targetDir "bsk.exe"
Install-Binary $source $target $firstInstallOutput = Install-Binary $source $target 6>&1 | Out-String
if ($firstInstallOutput -match 'daemon will restart') { throw 'fresh install reported a daemon restart' }
Assert-Equal (Get-FileHash -LiteralPath $target).Hash (Get-FileHash -LiteralPath $source).Hash Assert-Equal (Get-FileHash -LiteralPath $target).Hash (Get-FileHash -LiteralPath $source).Hash
$daemon = Start-Process -FilePath $target -ArgumentList @('daemon', 'start', '--foreground', '--port', '0') -WindowStyle Hidden -PassThru $daemon = Start-Process -FilePath $target -ArgumentList @('daemon', 'start', '--foreground', '--port', '0') -WindowStyle Hidden -PassThru
@@ -182,15 +186,19 @@ try {
# Refuse replacement when daemon identity cannot be verified. # Refuse replacement when daemon identity cannot be verified.
[IO.File]::WriteAllText($infoPath, 'invalid daemon metadata') [IO.File]::WriteAllText($infoPath, 'invalid daemon metadata')
$before = (Get-FileHash -LiteralPath $target).Hash $before = (Get-FileHash -LiteralPath $target).Hash
Assert-Fails { Install-Binary $source $target } Assert-Fails { Install-Binary $source $target } -MessageContains $infoPath
Assert-Equal (Get-FileHash -LiteralPath $target).Hash $before Assert-Equal (Get-FileHash -LiteralPath $target).Hash $before
# A failed stop must also prevent installation to a previously empty target. # A failed stop must also prevent installation to a previously empty target.
$blockedTarget = Join-Path $newTargetDir "blocked.exe" $blockedTarget = Join-Path $newTargetDir "blocked.exe"
Assert-Fails { Install-Binary $source $blockedTarget } Assert-Fails { Install-Binary $source $blockedTarget } -MessageContains $infoPath
if (Test-Path -LiteralPath $blockedTarget) { throw "failed stop created an installation" } if (Test-Path -LiteralPath $blockedTarget) { throw "failed stop created an installation" }
if (@(Get-ChildItem -LiteralPath $newTargetDir -Filter "*.install-*").Count) { throw "failed stop leaked staging files" } if (@(Get-ChildItem -LiteralPath $newTargetDir -Filter "*.install-*").Count) { throw "failed stop leaked staging files" }
Assert-Equal ([IO.File]::ReadAllText($infoPath)) 'invalid daemon metadata'
# Following the reported recovery path must allow the same installation to succeed.
[IO.File]::Delete($infoPath) [IO.File]::Delete($infoPath)
Install-Binary $source $blockedTarget
Assert-Equal (Get-FileHash -LiteralPath $blockedTarget).Hash (Get-FileHash -LiteralPath $source).Hash
# A remaining lock must fail without truncation or staging debris. # A remaining lock must fail without truncation or staging debris.
$lock = [IO.File]::Open($target, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::None) $lock = [IO.File]::Open($target, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::None)
@@ -271,7 +279,11 @@ try {
$script:UserPath = "$targetDir;$InstallDir;$($InstallDir.ToUpperInvariant())" $script:UserPath = "$targetDir;$InstallDir;$($InstallDir.ToUpperInvariant())"
$env:PATH = "$targetDir;$InstallDir;$oldPath" $env:PATH = "$targetDir;$InstallDir;$oldPath"
Assert-Equal (Get-Command bsk).Source $target Assert-Equal (Get-Command bsk).Source $target
Main $installOutput = Main 6>&1 | Out-String
if ($installOutput -notmatch 'current session only' -or
$installOutput -notmatch 'Machine PATH' -or $installOutput -notmatch 'Get-Command bsk -All') {
throw 'install did not explain PATH verification in a new terminal'
}
Assert-Equal $downloads.Count 1 Assert-Equal $downloads.Count 1
Assert-Equal (Get-Command bsk).Source $installed Assert-Equal (Get-Command bsk).Source $installed
Assert-Equal $script:UserPath "$InstallDir;$targetDir" Assert-Equal $script:UserPath "$InstallDir;$targetDir"