-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
19d8f6c
commit 93c4f50
Showing
3 changed files
with
319 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,305 @@ | ||
#!/usr/bin/env pwsh | ||
param( | ||
# Forces installing the baseline build regardless of what CPU you are actually using. | ||
[Switch]$ForceBaseline = $false, | ||
# Skips adding the bun.exe directory to the user's %PATH% | ||
[Switch]$NoPathUpdate = $false, | ||
# Skips adding the bun to the list of installed programs | ||
[Switch]$NoRegisterInstallation = $false, | ||
# Skips installing powershell completions to your profile | ||
[Switch]$NoCompletions = $false, | ||
|
||
# Debugging: Always download with 'Invoke-RestMethod' instead of 'curl.exe' | ||
[Switch]$DownloadWithoutCurl = $false | ||
); | ||
$Version = if ($env:BUN_VERSION) { $env:BUN_VERSION } else { "latest" } | ||
# filter out 32 bit + ARM | ||
if (-not ((Get-CimInstance Win32_ComputerSystem)).SystemType -match "x64-based") { | ||
Write-Output "Install Failed:" | ||
Write-Output "Bun for Windows is currently only available for x86 64-bit Windows.`n" | ||
return 1 | ||
} | ||
|
||
# This corresponds to .win10_rs5 in build.zig | ||
$MinBuild = 17763; | ||
$MinBuildName = "Windows 10 1809" | ||
|
||
$WinVer = [System.Environment]::OSVersion.Version | ||
if ($WinVer.Major -lt 10 -or ($WinVer.Major -eq 10 -and $WinVer.Build -lt $MinBuild)) { | ||
Write-Warning "Bun requires at ${MinBuildName} or newer.`n`nThe install will still continue but it may not work.`n" | ||
return 1 | ||
} | ||
|
||
$ErrorActionPreference = "Stop" | ||
|
||
# These three environment functions are roughly copied from https://github.com/prefix-dev/pixi/pull/692 | ||
# They are used instead of `SetEnvironmentVariable` because of unwanted variable expansions. | ||
function Publish-Env { | ||
if (-not ("Win32.NativeMethods" -as [Type])) { | ||
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @" | ||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] | ||
public static extern IntPtr SendMessageTimeout( | ||
IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, | ||
uint fuFlags, uint uTimeout, out UIntPtr lpdwResult); | ||
"@ | ||
} | ||
$HWND_BROADCAST = [IntPtr] 0xffff | ||
$WM_SETTINGCHANGE = 0x1a | ||
$result = [UIntPtr]::Zero | ||
[Win32.NativeMethods]::SendMessageTimeout($HWND_BROADCAST, | ||
$WM_SETTINGCHANGE, | ||
[UIntPtr]::Zero, | ||
"Environment", | ||
2, | ||
5000, | ||
[ref] $result | ||
) | Out-Null | ||
} | ||
|
||
function Write-Env { | ||
param([String]$Key, [String]$Value) | ||
|
||
$RegisterKey = Get-Item -Path 'HKCU:' | ||
|
||
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment', $true) | ||
if ($null -eq $Value) { | ||
$EnvRegisterKey.DeleteValue($Key) | ||
} else { | ||
$RegistryValueKind = if ($Value.Contains('%')) { | ||
[Microsoft.Win32.RegistryValueKind]::ExpandString | ||
} elseif ($EnvRegisterKey.GetValue($Key)) { | ||
$EnvRegisterKey.GetValueKind($Key) | ||
} else { | ||
[Microsoft.Win32.RegistryValueKind]::String | ||
} | ||
$EnvRegisterKey.SetValue($Key, $Value, $RegistryValueKind) | ||
} | ||
|
||
Publish-Env | ||
} | ||
|
||
function Get-Env { | ||
param([String] $Key) | ||
|
||
$RegisterKey = Get-Item -Path 'HKCU:' | ||
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment') | ||
$EnvRegisterKey.GetValue($Key, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) | ||
} | ||
|
||
# The installation of bun is it's own function so that in the unlikely case the $IsBaseline check fails, we can do a recursive call. | ||
# There are also lots of sanity checks out of fear of anti-virus software or other weird Windows things happening. | ||
function Install-Bun { | ||
param( | ||
[string]$Version, | ||
[bool]$ForceBaseline = $False | ||
); | ||
|
||
# if a semver is given, we need to adjust it to this format: bun-v0.0.0 | ||
if ($Version -match "^\d+\.\d+\.\d+$") { | ||
$Version = "bun-v$Version" | ||
} | ||
elseif ($Version -match "^v\d+\.\d+\.\d+$") { | ||
$Version = "bun-$Version" | ||
} | ||
|
||
$Arch = "x64" | ||
$IsBaseline = $ForceBaseline | ||
if (!$IsBaseline) { | ||
$IsBaseline = !( ` | ||
Add-Type -MemberDefinition '[DllImport("kernel32.dll")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);' ` | ||
-Name 'Kernel32' -Namespace 'Win32' -PassThru ` | ||
)::IsProcessorFeaturePresent(40); | ||
} | ||
|
||
$BunRoot = if ($env:BUN_INSTALL) { $env:BUN_INSTALL } else { "${Home}\.bun" } | ||
$BunBin = mkdir -Force "${BunRoot}\bin" | ||
|
||
try { | ||
Remove-Item "${BunBin}\bun.exe" -Force | ||
} catch [System.Management.Automation.ItemNotFoundException] { | ||
# ignore | ||
} catch [System.UnauthorizedAccessException] { | ||
$openProcesses = Get-Process -Name bun | Where-Object { $_.Path -eq "${BunBin}\bun.exe" } | ||
if ($openProcesses.Count -gt 0) { | ||
Write-Output "Install Failed - An older installation exists and is open. Please close open Bun processes and try again." | ||
return 1 | ||
} | ||
Write-Output "Install Failed - An unknown error occurred while trying to remove the existing installation" | ||
Write-Output $_ | ||
return 1 | ||
} catch { | ||
Write-Output "Install Failed - An unknown error occurred while trying to remove the existing installation" | ||
Write-Output $_ | ||
return 1 | ||
} | ||
|
||
$Target = "bun-windows-$Arch" | ||
if ($IsBaseline) { | ||
$Target = "bun-windows-$Arch-baseline" | ||
} | ||
$BaseURL = "https://github.com/oven-sh/bun/releases" | ||
$URL = "$BaseURL/$(if ($Version -eq "latest") { "latest/download" } else { "download/$Version" })/$Target.zip" | ||
|
||
$ZipPath = "${BunBin}\$Target.zip" | ||
|
||
$DisplayVersion = $( | ||
if ($Version -eq "latest") { "Bun" } | ||
elseif ($Version -eq "canary") { "Bun Canary" } | ||
elseif ($Version -match "^bun-v\d+\.\d+\.\d+$") { "Bun $($Version.Substring(4))" } | ||
else { "Bun tag='${Version}'" } | ||
) | ||
|
||
$null = mkdir -Force $BunBin | ||
Remove-Item -Force $ZipPath -ErrorAction SilentlyContinue | ||
|
||
# curl.exe is faster than PowerShell 5's 'Invoke-WebRequest' | ||
# note: 'curl' is an alias to 'Invoke-WebRequest'. so the exe suffix is required | ||
if (-not $DownloadWithoutCurl) { | ||
curl.exe "-#SfLo" "$ZipPath" "$URL" | ||
} | ||
if ($DownloadWithoutCurl -or ($LASTEXITCODE -ne 0)) { | ||
Write-Warning "The command 'curl.exe $URL -o $ZipPath' exited with code ${LASTEXITCODE}`nTrying an alternative download method..." | ||
try { | ||
# Use Invoke-RestMethod instead of Invoke-WebRequest because Invoke-WebRequest breaks on | ||
# some machines, see | ||
Invoke-RestMethod -Uri $URL -OutFile $ZipPath | ||
} catch { | ||
Write-Output "Install Failed - could not download $URL" | ||
Write-Output "The command 'Invoke-RestMethod $URL -OutFile $ZipPath' exited with code ${LASTEXITCODE}`n" | ||
return 1 | ||
} | ||
} | ||
|
||
if (!(Test-Path $ZipPath)) { | ||
Write-Output "Install Failed - could not download $URL" | ||
Write-Output "The file '$ZipPath' does not exist. Did an antivirus delete it?`n" | ||
return 1 | ||
} | ||
|
||
try { | ||
$lastProgressPreference = $global:ProgressPreference | ||
$global:ProgressPreference = 'SilentlyContinue'; | ||
Expand-Archive "$ZipPath" "$BunBin" -Force | ||
$global:ProgressPreference = $lastProgressPreference | ||
if (!(Test-Path "${BunBin}\$Target\bun.exe")) { | ||
throw "The file '${BunBin}\$Target\bun.exe' does not exist. Download is corrupt or intercepted Antivirus?`n" | ||
} | ||
} catch { | ||
Write-Output "Install Failed - could not unzip $ZipPath" | ||
Write-Error $_ | ||
return 1 | ||
} | ||
|
||
Move-Item "${BunBin}\$Target\bun.exe" "${BunBin}\bun.exe" -Force | ||
|
||
Remove-Item "${BunBin}\$Target" -Recurse -Force | ||
Remove-Item $ZipPath -Force | ||
|
||
$BunRevision = "$(& "${BunBin}\bun.exe" --revision)" | ||
if ($LASTEXITCODE -eq 1073741795) { # STATUS_ILLEGAL_INSTRUCTION | ||
if ($IsBaseline) { | ||
Write-Output "Install Failed - bun.exe (baseline) is not compatible with your CPU.`n" | ||
Write-Output "Please open a GitHub issue with your CPU model:`nhttps://github.com/oven-sh/bun/issues/new/choose`n" | ||
return 1 | ||
} | ||
|
||
Write-Output "Install Failed - bun.exe is not compatible with your CPU. This should have been detected before downloading.`n" | ||
Write-Output "Attempting to download bun.exe (baseline) instead.`n" | ||
|
||
Install-Bun -Version $Version -ForceBaseline $True | ||
return 1 | ||
} | ||
# '-1073741515' was spotted in the wild, but not clearly documented as a status code: | ||
# https://discord.com/channels/876711213126520882/1149339379446325248/1205194965383250081 | ||
# http://community.sqlbackupandftp.com/t/error-1073741515-solved/1305 | ||
if (($LASTEXITCODE -eq 3221225781) -or ($LASTEXITCODE -eq -1073741515)) # STATUS_DLL_NOT_FOUND | ||
{ | ||
Write-Output "Install Failed - You are missing a DLL required to run bun.exe" | ||
Write-Output "This can be solved by installing the Visual C++ Redistributable from Microsoft:`nSee https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist`nDirect Download -> https://aka.ms/vs/17/release/vc_redist.x64.exe`n`n" | ||
Write-Output "The command '${BunBin}\bun.exe --revision' exited with code ${LASTEXITCODE}`n" | ||
return 1 | ||
} | ||
if ($LASTEXITCODE -ne 0) { | ||
Write-Output "Install Failed - could not verify bun.exe" | ||
Write-Output "The command '${BunBin}\bun.exe --revision' exited with code ${LASTEXITCODE}`n" | ||
return 1 | ||
} | ||
|
||
try { | ||
$env:IS_BUN_AUTO_UPDATE = "1" | ||
# TODO: When powershell completions are added, make this switch actually do something | ||
if ($NoCompletions) { | ||
$env:BUN_NO_INSTALL_COMPLETIONS = "1" | ||
} | ||
# This completions script in general will install some extra stuff, mainly the `bunx` link. | ||
# It also installs completions. | ||
$output = "$(& "${BunBin}\bun.exe" completions 2>&1)" | ||
if ($LASTEXITCODE -ne 0) { | ||
Write-Output $output | ||
Write-Output "Install Failed - could not finalize installation" | ||
Write-Output "The command '${BunBin}\bun.exe completions' exited with code ${LASTEXITCODE}`n" | ||
return 1 | ||
} | ||
} catch { | ||
# it is possible on powershell 5 that an error happens, but it is probably fine? | ||
} | ||
$env:IS_BUN_AUTO_UPDATE = $null | ||
$env:BUN_NO_INSTALL_COMPLETIONS = $null | ||
|
||
$DisplayVersion = if ($BunRevision -like "*-canary.*") { | ||
"${BunRevision}" | ||
} else { | ||
"$(& "${BunBin}\bun.exe" --version)" | ||
} | ||
|
||
$C_RESET = [char]27 + "[0m" | ||
$C_GREEN = [char]27 + "[1;32m" | ||
|
||
Write-Output "${C_GREEN}Bun ${DisplayVersion} was installed successfully!${C_RESET}" | ||
Write-Output "The binary is located at ${BunBin}\bun.exe`n" | ||
|
||
$hasExistingOther = $false; | ||
try { | ||
$existing = Get-Command bun -ErrorAction | ||
if ($existing.Source -ne "${BunBin}\bun.exe") { | ||
Write-Warning "Note: Another bun.exe is already in %PATH% at $($existing.Source)`nTyping 'bun' in your terminal will not use what was just installed.`n" | ||
$hasExistingOther = $true; | ||
} | ||
} catch {} | ||
|
||
if (-not $NoRegisterInstallation) { | ||
$rootKey = $null | ||
try { | ||
$RegistryKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\Bun" | ||
$rootKey = New-Item -Path $RegistryKey -Force | ||
New-ItemProperty -Path $RegistryKey -Name "DisplayName" -Value "Bun" -PropertyType String -Force | Out-Null | ||
New-ItemProperty -Path $RegistryKey -Name "InstallLocation" -Value "${BunRoot}" -PropertyType String -Force | Out-Null | ||
New-ItemProperty -Path $RegistryKey -Name "DisplayIcon" -Value $BunBin\bun.exe -PropertyType String -Force | Out-Null | ||
New-ItemProperty -Path $RegistryKey -Name "UninstallString" -Value "powershell -c `"& `'$BunRoot\uninstall.ps1`' -PauseOnError`" -ExecutionPolicy Bypass" -PropertyType String -Force | Out-Null | ||
} catch { | ||
if ($rootKey -ne $null) { | ||
Remove-Item -Path $RegistryKey -Force | ||
} | ||
} | ||
} | ||
|
||
if(!$hasExistingOther) { | ||
# Only try adding to path if there isn't already a bun.exe in the path | ||
$Path = (Get-Env -Key "Path") -split ';' | ||
if ($Path -notcontains $BunBin) { | ||
if (-not $NoPathUpdate) { | ||
$Path += $BunBin | ||
Write-Env -Key 'Path' -Value ($Path -join ';') | ||
$env:PATH = $Path; | ||
} else { | ||
Write-Output "Skipping adding '${BunBin}' to the user's %PATH%`n" | ||
} | ||
} | ||
|
||
Write-Output "To get started, restart your terminal/editor, then type `"bun`"`n" | ||
} | ||
|
||
$LASTEXITCODE = 0; | ||
} | ||
|
||
Install-Bun -Version $Version -ForceBaseline $ForceBaseline |