diff --git a/packages/cli/install.ps1 b/packages/cli/install.ps1 index 6adc7ed2ec..a3e18caf79 100644 --- a/packages/cli/install.ps1 +++ b/packages/cli/install.ps1 @@ -46,11 +46,87 @@ function Write-Warn { Write-Host $Message } +# Exit code when a Windows native binary cannot load required DLLs (STATUS_DLL_NOT_FOUND). +$script:DllNotFoundExitCode = -1073741515 + +function Test-IsDllNotFoundExitCode { + param([int]$ExitCode) + if ($ExitCode -eq $script:DllNotFoundExitCode) { + return $true + } + if ($ExitCode -eq 3221225781) { + return $true + } + if ($ExitCode -lt 0) { + $hex = '{0:X8}' -f ($ExitCode -band 0xFFFFFFFF) + return $hex -eq 'C0000135' + } + return $false +} + +function Get-DllNotFoundInstallMessage { + $arch = if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "x64" } + $vcUrl = if ($arch -eq "arm64") { + "https://aka.ms/vs/17/release/vc_redist.arm64.exe" + } else { + "https://aka.ms/vs/17/release/vc_redist.x64.exe" + } + return @" +vp.exe could not start (exit code 0xC0000135). +This usually means Microsoft Visual C++ 2015-2022 Redistributable ($arch) is not installed. + +Install: $vcUrl +Then re-run: irm https://vite.plus/ps1 | iex +"@ +} + +# Internal stop signal: halts install without re-printing an error we already wrote. +$script:InstallStopSignal = 'VP_INSTALL_STOP' + +function Test-IsInstallStopException { + param( + [System.Management.Automation.ErrorRecord]$ErrorRecord + ) + return $ErrorRecord.Exception.Message -eq $script:InstallStopSignal +} + +function Test-ShouldKeepShellOpenAfterFailure { + # Only `irm ... | iex` typed in an already-open interactive shell should keep the + # session alive. CI, script files, and `powershell -Command "..."` must exit non-zero. + if ($env:CI -eq "true") { + return $false + } + if ($PSCommandPath) { + return $false + } + if (-not [Environment]::UserInteractive) { + return $false + } + try { + $commandLine = (Get-CimInstance Win32_Process -Filter "ProcessId=$PID").CommandLine + if ($commandLine -match '(^|\s)-Command(\s|$)') { + return $false + } + } catch { + return $false + } + return $true +} + +function Exit-Installer { + param([int]$Code = 1) + $global:LASTEXITCODE = $Code + if (-not (Test-ShouldKeepShellOpenAfterFailure)) { + exit $Code + } + throw $script:InstallStopSignal +} + function Write-Error-Exit { param([string]$Message) Write-Host "error: " -ForegroundColor Red -NoNewline Write-Host $Message - exit 1 + Exit-Installer } function Test-ReleaseAgeError { @@ -114,11 +190,26 @@ function Write-ReleaseAgeOverride { } function Write-InstallFailure { - param([string]$LogPath) + param( + [string]$LogPath, + [int]$ExitCode = 0 + ) + + if (Test-IsDllNotFoundExitCode $ExitCode) { + $message = Get-DllNotFoundInstallMessage + if ($env:CI -eq "true") { + Write-Host "error: " -ForegroundColor Red -NoNewline + Write-Host $message + Exit-Installer + } + Write-Error-Exit $message + } + if ($env:CI -eq "true") { Write-Host "error: " -ForegroundColor Red -NoNewline Write-Host "Failed to install dependencies. Log output:" Get-Content -Path $LogPath | ForEach-Object { Write-Host $_ } + Exit-Installer } else { Write-Error-Exit "Failed to install dependencies. See log for details: $LogPath" } @@ -157,6 +248,7 @@ function Get-PackageMetadata { try { $script:PackageMetadata = Invoke-RestMethod $metadataUrl } catch { + if (Test-IsInstallStopException $_) { throw } # Try to extract npm error message from response $errorMsg = $_.ErrorDetails.Message if ($errorMsg) { @@ -166,6 +258,7 @@ function Get-PackageMetadata { Write-Error-Exit "Failed to fetch version '${versionPath}': $($errorJson.error)`n URL: $metadataUrl" } } catch { + if (Test-IsInstallStopException $_) { throw } # JSON parsing failed, fall through to generic error } } @@ -179,6 +272,7 @@ function Get-PackageMetadata { try { $script:PackageMetadata = $script:PackageMetadata | ConvertFrom-Json } catch { + if (Test-IsInstallStopException $_) { throw } # Not valid JSON - treat as plain string error Write-Error-Exit "Failed to fetch version '${versionPath}': $script:PackageMetadata`n URL: $metadataUrl" } @@ -593,16 +687,14 @@ function Main { $retryExitCode = $LASTEXITCODE $retryOutput | Out-File $installLog if ($retryExitCode -ne 0) { - Write-InstallFailure $installLog - exit 1 + Write-InstallFailure -LogPath $installLog -ExitCode $retryExitCode } } else { Write-ReleaseAgeFailure $installLog - exit 1 + Exit-Installer } } else { - Write-InstallFailure $installLog - exit 1 + Write-InstallFailure -LogPath $installLog -ExitCode $installExitCode } } } finally { @@ -749,4 +841,14 @@ exec "`$VP_HOME/current/bin/vp.exe" "`$@" Write-Host "" } -Main +try { + Main +} catch { + if (Test-IsInstallStopException $_) { + if (Test-ShouldKeepShellOpenAfterFailure) { + return + } + exit $global:LASTEXITCODE + } + throw +}