diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index 52f47847a..5bf9b365e 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -35,7 +35,7 @@ class Bun(SimpleNamespace): """Bun constants.""" # The Bun version. - VERSION = "1.1.5" + VERSION = "1.1.6" # Min Bun Version MIN_VERSION = "0.7.0" # The directory to store the bun. @@ -46,6 +46,10 @@ class Bun(SimpleNamespace): ) # URL to bun install script. INSTALL_URL = "https://bun.sh/install" + # URL to windows install script. + WINDOWS_INSTALL_URL = ( + "https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/install.ps1" + ) # FNM config. diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index fb25496d4..606f70d2b 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -743,8 +743,15 @@ def install_bun(): # if unzip is installed if constants.IS_WINDOWS: processes.new_process( - ["powershell", "-c", f"irm {constants.Bun.INSTALL_URL}.ps1|iex"], - env={"BUN_INSTALL": constants.Bun.ROOT_PATH}, + [ + "powershell", + "-c", + f"irm {constants.Bun.INSTALL_URL}.ps1|iex", + ], # TODO: change install url to constants.BUN.WINDOWS_INSTALL_URL + env={ + "BUN_INSTALL": constants.Bun.ROOT_PATH, + "BUN_VERSION": constants.Bun.VERSION, + }, shell=True, run=True, show_logs=console.is_debug(), diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 000000000..3c432ac3b --- /dev/null +++ b/scripts/install.ps1 @@ -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