diff --git a/src/bun.js/WebKit b/src/bun.js/WebKit index c0c648e5a793ac..c3712c13dcdc09 160000 --- a/src/bun.js/WebKit +++ b/src/bun.js/WebKit @@ -1 +1 @@ -Subproject commit c0c648e5a793acb9a6ee48c898e98a9afc8bd9cd +Subproject commit c3712c13dcdc091cfe4c7cb8f2c1fd16472e6f92 diff --git a/src/bun.js/bindings/JSEnvironmentVariableMap.cpp b/src/bun.js/bindings/JSEnvironmentVariableMap.cpp index d0514ae781935e..3d59d9c45947df 100644 --- a/src/bun.js/bindings/JSEnvironmentVariableMap.cpp +++ b/src/bun.js/bindings/JSEnvironmentVariableMap.cpp @@ -166,11 +166,10 @@ JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject) if (auto index = parseIndex(identifier)) { ZigString valueString = { nullptr, 0 }; ZigString nameStr = toZigString(name); - JSValue value = jsUndefined(); if (Bun__getEnvValue(globalObject, &nameStr, &valueString)) { - value = jsString(vm, Zig::toStringCopy(valueString)); + JSValue value = jsString(vm, Zig::toStringCopy(valueString)); + object->putDirectIndex(globalObject, *index, value, 0, PutDirectIndexLikePutDirect); } - object->putDirectIndex(globalObject, *index, value, 0, PutDirectIndexLikePutDirect); continue; } } diff --git a/src/bun.zig b/src/bun.zig index 605855ddbaea85..dfa870098e1ca8 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -2005,12 +2005,12 @@ pub const win32 = struct { ) !void { const flags: std.os.windows.DWORD = w.CREATE_UNICODE_ENVIRONMENT; - const image_path = &w.peb().ProcessParameters.ImagePathName; + const image_path = windows.exePathW(); var wbuf: WPathBuffer = undefined; - @memcpy(wbuf[0..image_path.Length], image_path.Buffer); - wbuf[image_path.Length] = 0; + @memcpy(wbuf[0..image_path.len], image_path); + wbuf[image_path.len] = 0; - const image_pathZ = wbuf[0..image_path.Length :0]; + const image_pathZ = wbuf[0..image_path.len :0]; const kernelenv = w.kernel32.GetEnvironmentStringsW(); defer { @@ -2062,7 +2062,7 @@ pub const win32 = struct { .hStdError = std.io.getStdErr().handle, }; const rc = w.kernel32.CreateProcessW( - image_pathZ, + image_pathZ.ptr, w.kernel32.GetCommandLineW(), null, null, diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index 6e6a5cabcc8c4b..bd209664ed9eb9 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -2307,7 +2307,7 @@ const GitHandler = struct { process.stderr_behavior = .Inherit; _ = try process.spawnAndWait(); - _ = process.kill() catch undefined; + _ = process.kill() catch {}; } Output.prettyError("\n", .{}); diff --git a/src/cli/install.ps1 b/src/cli/install.ps1 index 22a907a4f7fed8..285a1b073bcfe9 100644 --- a/src/cli/install.ps1 +++ b/src/cli/install.ps1 @@ -1,35 +1,96 @@ #!/usr/bin/env pwsh param( # TODO: change this to 'latest' when Bun for Windows is stable. - [string]$Version = "canary" + [String]$Version = "canary", + # 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 ); +# filter out 32 bit + ARM +if ($env:PROCESSOR_ARCHITECTURE -ne "AMD64") { + Write-Output "Install Failed:" + Write-Output "Bun for Windows is only available for x86 64-bit Windows.`n" + exit 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" + exit 1 +} + $ErrorActionPreference = "Stop" -# This is a functions so that in the unlikely case the baseline check fails but is is needed, we can do a recursive call. +# 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 + [string]$Version, [bool]$ForceBaseline = $False ); - # filter out 32 bit and arm - if ($env:PROCESSOR_ARCHITECTURE -ne "AMD64") { - Write-Output "Install Failed:" - Write-Output "Bun for Windows is only available for x86 64-bit Windows.`n" - exit 1 - } - - # .win10_rs5 - $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" - exit 1 - } - # 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" @@ -44,16 +105,35 @@ function Install-Bun { $Arch = "x64" $IsBaseline = $ForceBaseline - - $EnabledXStateFeatures = ( ` - Add-Type -MemberDefinition '[DllImport("kernel32.dll")]public static extern long GetEnabledXStateFeatures();' ` - -Name 'Kernel32' -Namespace 'Win32' -PassThru ` - )::GetEnabledXStateFeatures(); - $IsBaseline = ($EnabledXStateFeatures -band 4) -neq 4; + 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." + exit 1 + } + Write-Output "Install Failed - An unknown error occurred while trying to remove the existing installation" + Write-Output $_ + exit 1 + } catch { + Write-Output "Install Failed - An unknown error occurred while trying to remove the existing installation" + Write-Output $_ + exit 1 + } + $Target = "bun-windows-$Arch" if ($IsBaseline) { $Target = "bun-windows-$Arch-baseline" @@ -72,12 +152,16 @@ function Install-Bun { $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 curl.exe "-#SfLo" "$ZipPath" "$URL" if ($LASTEXITCODE -ne 0) { Write-Output "Install Failed - could not download $URL" Write-Output "The command 'curl.exe $URL -o $ZipPath' exited with code ${LASTEXITCODE}`n" exit 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" @@ -90,14 +174,14 @@ function Install-Bun { 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 / Antivirus intercepted?`n" + 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 $_ exit 1 } - Remove-Item "${BunBin}\bun.exe" -ErrorAction SilentlyContinue + Move-Item "${BunBin}\$Target\bun.exe" "${BunBin}\bun.exe" -Force Remove-Item "${BunBin}\$Target" -Recurse -Force @@ -117,10 +201,10 @@ function Install-Bun { Install-Bun -Version $Version -ForceBaseline $True exit 1 } - if (($LASTEXITCODE -eq 3221225781) # STATUS_DLL_NOT_FOUND + # '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 - || ($LASTEXITCODE -eq 1073741515)) + 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" @@ -132,6 +216,16 @@ function Install-Bun { Write-Output "The command '${BunBin}\bun.exe --revision' exited with code ${LASTEXITCODE}`n" exit 1 } + + $env:IS_BUN_AUTO_UPDATE = "1" + $null = "$(& "${BunBin}\bun.exe" completions)" + # if ($LASTEXITCODE -ne 0) { + # Write-Output "Install Failed - could not finalize installation" + # Write-Output "The command '${BunBin}\bun.exe completions' exited with code ${LASTEXITCODE}`n" + # exit 1 + # } + $env:IS_BUN_AUTO_UPDATE = $null + $DisplayVersion = if ($BunRevision -like "*-canary.*") { "${BunRevision}" } else { @@ -141,18 +235,6 @@ function Install-Bun { $C_RESET = [char]27 + "[0m" $C_GREEN = [char]27 + "[1;32m" - # delete bunx if it exists already. this happens if you re-install - # we don't want to hit an "already exists" error. - Remove-Item "${BunBin}\bunx.exe" -ErrorAction SilentlyContinue - Remove-Item "${BunBin}\bunx.cmd" -ErrorAction SilentlyContinue - - try { - $null = New-Item -ItemType HardLink -Path "${BunBin}\bunx.exe" -Target "${BunBin}\bun.exe" -Force - } catch { - Write-Warning "Could not create a hard link for bunx, falling back to a cmd script`n" - Set-Content -Path "${BunBin}\bunx.cmd" -Value "@%~dp0bun.exe x %*" - } - Write-Output "${C_GREEN}Bun ${DisplayVersion} was installed successfully!${C_RESET}" Write-Output "The binary is located at ${BunBin}\bun.exe`n" @@ -167,17 +249,36 @@ function Install-Bun { } } catch {} - $User = [System.EnvironmentVariableTarget]::User - $Path = [System.Environment]::GetEnvironmentVariable('Path', $User) -split ';' - if ($Path -notcontains $BunBin) { - $Path += $BunBin - [System.Environment]::SetEnvironmentVariable('Path', $Path -join ';', $User) - } - if ($env:PATH -notcontains ";${BunBin}") { - $env:PATH = "${env:Path};${BunBin}" + 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`"" -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 ';') + } 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" } -} \ No newline at end of file +} + +Install-Bun -Version $Version -ForceBaseline $ForceBaseline diff --git a/src/cli/install_completions_command.zig b/src/cli/install_completions_command.zig index b0953d9b99fb9a..362f922b46f2c9 100644 --- a/src/cli/install_completions_command.zig +++ b/src/cli/install_completions_command.zig @@ -44,13 +44,10 @@ const ShellCompletions = @import("./shell_completions.zig"); pub const InstallCompletionsCommand = struct { pub fn testPath(_: string) !std.fs.Dir {} - fn installBunxSymlink(allocator: std.mem.Allocator, cwd: []const u8) !void { - if (comptime Environment.isWindows) { - @panic("TODO on Windows"); - } + const bunx_name = if (Environment.isDebug) "bunx-debug" else "bunx"; + fn installBunxSymlinkPosix(allocator: std.mem.Allocator, cwd: []const u8) !void { var buf: [bun.MAX_PATH_BYTES]u8 = undefined; - const bunx_name = if (Environment.isDebug) "bunx-debug" else "bunx"; // don't install it if it's already there if (bun.which(&buf, bun.getenvZ("PATH") orelse cwd, cwd, bunx_name) != null) @@ -92,6 +89,81 @@ pub const InstallCompletionsCommand = struct { }; } + fn installBunxSymlinkWindows(_: std.mem.Allocator, _: []const u8) !void { + // Because symlinks are not always allowed on windows, + // `bunx.exe` on windows is a hardlink to `bun.exe` + // for this to work, we need to delete and recreate the hardlink every time + const image_path = bun.windows.exePathW(); + const image_dirname = image_path[0 .. (std.mem.lastIndexOfScalar(u16, image_path, '\\') orelse unreachable) + 1]; + + var bunx_path_buf: bun.WPathBuffer = undefined; + + std.os.windows.DeleteFile(try bun.strings.concatBufT(u16, &bunx_path_buf, .{ + &bun.windows.nt_object_prefix, + image_dirname, + comptime bun.strings.literal(u16, bunx_name ++ ".cmd"), + }), .{ .dir = null }) catch {}; + + const bunx_path_with_z = try bun.strings.concatBufT(u16, &bunx_path_buf, .{ + &bun.windows.nt_object_prefix, + image_dirname, + comptime bun.strings.literal(u16, bunx_name ++ ".exe\x00"), + }); + const bunx_path = bunx_path_with_z[0 .. bunx_path_with_z.len - 1 :0]; + std.os.windows.DeleteFile(bunx_path, .{ .dir = null }) catch {}; + + if (bun.windows.CreateHardLinkW(bunx_path, image_path, null) == 0) { + // if hard link fails, use a cmd script + const script = "@%~dp0bun.exe x %*\n"; + + const bunx_cmd_with_z = try bun.strings.concatBufT(u16, &bunx_path_buf, .{ + &bun.windows.nt_object_prefix, + image_dirname, + comptime bun.strings.literal(u16, bunx_name ++ ".exe\x00"), + }); + const bunx_cmd = bunx_cmd_with_z[0 .. bunx_cmd_with_z.len - 1 :0]; + // TODO: fix this zig bug, it is one line change to a few functions. + // const file = try std.fs.createFileAbsoluteW(bunx_cmd, .{}); + const file = try std.fs.cwd().createFileW(bunx_cmd, .{}); + defer file.close(); + try file.writeAll(script); + } + } + + fn installBunxSymlink(allocator: std.mem.Allocator, cwd: []const u8) !void { + if (Environment.isWindows) { + try installBunxSymlinkWindows(allocator, cwd); + } else { + try installBunxSymlinkPosix(allocator, cwd); + } + } + + fn installUninstallerWindows() !void { + // This uninstaller file is only written if the current exe is within a path + // like `\bun\bin\.exe` so that it probably only runs when the + // powershell `install.ps1` was used to install. + + const image_path = bun.windows.exePathW(); + const image_dirname = image_path[0..(std.mem.lastIndexOfScalar(u16, image_path, '\\') orelse unreachable)]; + + if (!std.mem.endsWith(u16, image_dirname, comptime bun.strings.literal(u16, "\\bun\\bin"))) + return; + + const content = @embedFile("uninstall.ps1"); + + var bunx_path_buf: bun.WPathBuffer = undefined; + const uninstaller_path = try bun.strings.concatBufT(u16, &bunx_path_buf, .{ + &bun.windows.nt_object_prefix, + image_dirname[0 .. image_dirname.len - 3], + comptime bun.strings.literal(u16, "uninstall.ps1"), + }); + + const file = try std.fs.cwd().createFileW(uninstaller_path, .{}); + defer file.close(); + + try file.writeAll(content); + } + pub fn exec(allocator: std.mem.Allocator) !void { // Fail silently on auto-update. const fail_exit_code: u8 = if (bun.getenvZ("IS_BUN_AUTO_UPDATE") == null) 1 else 0; @@ -120,9 +192,21 @@ pub const InstallCompletionsCommand = struct { installBunxSymlink(allocator, cwd) catch {}; + if (Environment.isWindows) { + installUninstallerWindows() catch {}; + } + + // TODO: https://github.com/oven-sh/bun/issues/8939 + if (Environment.isWindows) { + Output.errGeneric("PowerShell completions are not yet written for Bun yet.", .{}); + Output.printErrorln("See https://github.com/oven-sh/bun/issues/8939", .{}); + return; + } + switch (shell) { .unknown => { - Output.prettyErrorln("error: Unknown or unsupported shell. Please set $SHELL to one of zsh, fish, or bash. To manually output completions, run this:\n bun getcompletes", .{}); + Output.errGeneric("Unknown or unsupported shell. Please set $SHELL to one of zsh, fish, or bash.", .{}); + Output.note("To manually output completions, run 'bun getcompletes'", .{}); Global.exit(fail_exit_code); }, else => {}, diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index d3bf2526669bde..9eb484312cd4c9 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -610,15 +610,14 @@ pub const RunCommand = struct { const file_slice = target_path_buffer[0 .. prefix.len + len + file_name.len - "\x00".len]; const dir_slice = target_path_buffer[0 .. prefix.len + len + dir_name.len]; - const ImagePathName = std.os.windows.peb().ProcessParameters.ImagePathName; - std.debug.assert(ImagePathName.Buffer[ImagePathName.Length / 2] == 0); // trust windows + const image_path = bun.windows.exePathW(); if (Environment.isDebug) { // the link becomes out of date on rebuild std.os.unlinkW(file_slice) catch {}; } - if (bun.windows.CreateHardLinkW(@ptrCast(file_slice.ptr), @ptrCast(ImagePathName.Buffer), null) == 0) { + if (bun.windows.CreateHardLinkW(@ptrCast(file_slice.ptr), image_path.ptr, null) == 0) { switch (std.os.windows.kernel32.GetLastError()) { .ALREADY_EXISTS => {}, else => { @@ -629,7 +628,7 @@ pub const RunCommand = struct { target_path_buffer[dir_slice.len] = '\\'; } - if (bun.windows.CreateHardLinkW(@ptrCast(file_slice.ptr), @ptrCast(ImagePathName.Buffer), null) == 0) { + if (bun.windows.CreateHardLinkW(@ptrCast(file_slice.ptr), image_path.ptr, null) == 0) { return; } }, diff --git a/src/cli/shell_completions.zig b/src/cli/shell_completions.zig index 35f0ff00b94cf3..4df3eb70b831d6 100644 --- a/src/cli/shell_completions.zig +++ b/src/cli/shell_completions.zig @@ -15,6 +15,7 @@ pub const Shell = enum { bash, zsh, fish, + pwsh, const bash_completions = @import("root").completions.bash; const zsh_completions = @import("root").completions.zsh; @@ -32,13 +33,17 @@ pub const Shell = enum { pub fn fromEnv(comptime Type: type, SHELL: Type) Shell { const basename = std.fs.path.basename(SHELL); if (strings.eqlComptime(basename, "bash")) { - return Shell.bash; + return .bash; } else if (strings.eqlComptime(basename, "zsh")) { - return Shell.zsh; + return .zsh; } else if (strings.eqlComptime(basename, "fish")) { - return Shell.fish; + return .fish; + } else if (strings.eqlComptime(basename, "pwsh") or + strings.eqlComptime(basename, "powershell")) + { + return .pwsh; } else { - return Shell.unknown; + return .unknown; } } }; diff --git a/src/cli/uninstall.ps1 b/src/cli/uninstall.ps1 new file mode 100644 index 00000000000000..df74be1872f7d2 --- /dev/null +++ b/src/cli/uninstall.ps1 @@ -0,0 +1,112 @@ +# This script will remove the Bun installation at the location of this +# script, removing it from %PATH%, deleting caches, and removing it from +# the list of installed programs. +param( + [switch]$PauseOnError = $false +) + +$ErrorActionPreference = "Stop" + +# These two 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 Write-Env { + param([String]$Key, [String]$Value) + + $EnvRegisterKey = Get-Item -Path 'HKCU:Environment' + 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) + } +} + +function Get-Env { + param([String] $Key) + + $RegisterKey = Get-Item -Path 'HKCU:Environment' + $EnvRegisterKey.GetValue($Key, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) +} + +if (-not (Test-Path "${PSScriptRoot}\bin\bun.exe")) { + Write-Host "bun.exe not found in ${PSScriptRoot}\bin`n`nRefusing to delete this directory as it may.`n`nIf this uninstallation is still intentional, please just manually delete this folder." + if ($PauseOnError) { pause } + exit 1 +} + +function Stop-Bun { + try { + Get-Process -Name bun | Where-Object { $_.Path -eq "${PSScriptRoot}\bin\bun.exe" } | Stop-Process -Force + } catch [Microsoft.PowerShell.Commands.ProcessCommandException] { + # ignore + } catch { + Write-Host "There are open instances of bun.exe that could not be automatically closed." + if ($PauseOnError) { pause } + exit 1 + } +} + +# Remove ~\.bun\bin\bun.exe +try { + Stop-Bun + Remove-Item "${PSScriptRoot}\bin\bun.exe" -Force +} catch { + # Try a second time + Stop-Bun + Start-Sleep -Seconds 1 + try { + Remove-Item "${PSScriptRoot}\bin\bun.exe" -Force + } catch { + Write-Host $_ + Write-Host "`n`nCould not delete ${PSScriptRoot}\bin\bun.exe." + Write-Host "Please close all instances of bun.exe and try again." + if ($PauseOnError) { pause } + exit 1 + } +} + +# Remove ~\.bun +try { + Remove-Item "${PSScriptRoot}" -Recurse -Force +} catch { + Write-Host "Could not delete ${PSScriptRoot}." + if ($PauseOnError) { pause } + exit 1 +} + +# Delete some tempdir files. Do not fail if an error happens here +try { + Remove-Item "${Temp}\bun-*" -Recurse -Force +} catch {} +try { + Remove-Item "${Temp}\bunx-*" -Recurse -Force +} catch {} + +# Remove Entry from path +try { + $Path = Get-Env -Key 'Path' + $Path = $Path -split ';' + $Path = $Path | Where-Object { $_ -ne "${PSScriptRoot}\bin" } + Write-Env -Key 'Path' -Value ($Path -join ';') +} catch { + Write-Host "Could not remove ${PSScriptRoot}\bin from PATH." + if ($PauseOnError) { pause } + exit 1 +} + +# Remove Entry from Windows Installer, if it is owned by this installation. +try { + $item = Get-Item "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\Bun"; + $location = $item.GetValue("InstallLocation"); + if ($location -eq "${PSScriptRoot}") { + Remove-Item "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\Bun" -Recurse + } +} catch { + # unlucky tbh +} diff --git a/src/cli/upgrade_command.zig b/src/cli/upgrade_command.zig index bfc0619b81a224..ba677b0b95cbd5 100644 --- a/src/cli/upgrade_command.zig +++ b/src/cli/upgrade_command.zig @@ -69,7 +69,7 @@ pub const Version = struct { ), ), }, - ) catch unreachable; + ) catch bun.outOfMemory(); } return this.tag; } @@ -117,13 +117,12 @@ pub const Version = struct { }; pub const UpgradeCheckerThread = struct { - var update_checker_thread: std.Thread = undefined; pub fn spawn(env_loader: *DotEnv.Loader) void { if (env_loader.map.get("BUN_DISABLE_UPGRADE_CHECK") != null or env_loader.map.get("CI") != null or strings.eqlComptime(env_loader.get("BUN_CANARY") orelse "0", "1")) return; - update_checker_thread = std.Thread.spawn(.{}, run, .{env_loader}) catch return; + var update_checker_thread = std.Thread.spawn(.{}, run, .{env_loader}) catch return; update_checker_thread.detach(); } @@ -133,13 +132,14 @@ pub const UpgradeCheckerThread = struct { std.time.sleep(std.time.ns_per_ms * delay); Output.Source.configureThread(); - HTTP.HTTPThread.init() catch unreachable; + try HTTP.HTTPThread.init(); defer { js_ast.Expr.Data.Store.deinit(); js_ast.Stmt.Data.Store.deinit(); } - var version = (try UpgradeCommand.getLatestVersion(default_allocator, env_loader, undefined, undefined, false, true)) orelse return; + + var version = (try UpgradeCommand.getLatestVersion(default_allocator, env_loader, null, null, false, true)) orelse return; if (!version.isCurrent()) { if (version.name()) |name| { @@ -171,8 +171,8 @@ pub const UpgradeCommand = struct { pub fn getLatestVersion( allocator: std.mem.Allocator, env_loader: *DotEnv.Loader, - refresher: *std.Progress, - progress: *std.Progress.Node, + refresher: ?*std.Progress, + progress: ?*std.Progress.Node, use_profile: bool, comptime silent: bool, ) !?Version { @@ -234,7 +234,7 @@ pub const UpgradeCommand = struct { var metadata_body = try MutableString.init(allocator, 2048); // ensure very stable memory address - var async_http: *HTTP.AsyncHTTP = allocator.create(HTTP.AsyncHTTP) catch unreachable; + var async_http: *HTTP.AsyncHTTP = try allocator.create(HTTP.AsyncHTTP); async_http.* = HTTP.AsyncHTTP.initSync( allocator, .GET, @@ -250,7 +250,7 @@ pub const UpgradeCommand = struct { ); async_http.client.reject_unauthorized = env_loader.getTLSRejectUnauthorized(); - if (!silent) async_http.client.progress_node = progress; + if (!silent) async_http.client.progress_node = progress.?; const response = try async_http.sendSync(true); switch (response.status_code) { @@ -268,8 +268,8 @@ pub const UpgradeCommand = struct { initializeStore(); var expr = ParseJSON(&source, &log, allocator) catch |err| { if (!silent) { - progress.end(); - refresher.refresh(); + progress.?.end(); + refresher.?.refresh(); if (log.errors > 0) { if (Output.enable_ansi_colors) { @@ -277,6 +277,7 @@ pub const UpgradeCommand = struct { } else { try log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false); } + Global.exit(1); } else { Output.prettyErrorln("Error parsing releases from GitHub: {s}", .{@errorName(err)}); @@ -288,9 +289,9 @@ pub const UpgradeCommand = struct { }; if (log.errors > 0) { - if (comptime !silent) { - progress.end(); - refresher.refresh(); + if (!silent) { + progress.?.end(); + refresher.?.refresh(); if (Output.enable_ansi_colors) { try log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true); @@ -306,9 +307,9 @@ pub const UpgradeCommand = struct { var version = Version{ .zip_url = "", .tag = "", .buf = metadata_body, .size = 0 }; if (expr.data != .e_object) { - if (comptime !silent) { - progress.end(); - refresher.refresh(); + if (!silent) { + progress.?.end(); + refresher.?.refresh(); const json_type: js_ast.Expr.Tag = @as(js_ast.Expr.Tag, expr.data); Output.prettyErrorln("JSON error - expected an object but received {s}", .{@tagName(json_type)}); @@ -326,8 +327,8 @@ pub const UpgradeCommand = struct { if (version.tag.len == 0) { if (comptime !silent) { - progress.end(); - refresher.refresh(); + progress.?.end(); + refresher.?.refresh(); Output.prettyErrorln("JSON Error parsing releases from GitHub: tag_name is missing?\n{s}", .{metadata_body.list.items}); Global.exit(1); @@ -380,8 +381,8 @@ pub const UpgradeCommand = struct { } if (comptime !silent) { - progress.end(); - refresher.refresh(); + progress.?.end(); + refresher.?.refresh(); if (version.name()) |name| { Output.prettyErrorln("Bun v{s} is out, but not for this platform ({s}) yet.", .{ name, Version.triplet, @@ -433,8 +434,6 @@ pub const UpgradeCommand = struct { }; env_loader.loadProcess(); - var version: Version = undefined; - const use_canary = brk: { const default_use_canary = Environment.is_canary; @@ -447,11 +446,11 @@ pub const UpgradeCommand = struct { const use_profile = strings.containsAny(bun.argv(), "--profile"); - if (!use_canary) { + const version: Version = if (!use_canary) v: { var refresher = std.Progress{}; var progress = refresher.start("Fetching version tags", 0); - version = (try getLatestVersion(ctx.allocator, &env_loader, &refresher, progress, use_profile, false)) orelse return; + const version = (try getLatestVersion(ctx.allocator, &env_loader, &refresher, progress, use_profile, false)) orelse return; progress.end(); refresher.refresh(); @@ -482,14 +481,14 @@ pub const UpgradeCommand = struct { Output.prettyErrorln("Downgrading from Bun {s}-canary to Bun v{s}\n", .{ Global.package_json_version, version.name().? }); } Output.flush(); - } else { - version = Version{ - .tag = "canary", - .zip_url = "https://github.com/oven-sh/bun/releases/download/canary/" ++ Version.zip_filename, - .size = 0, - .buf = MutableString.initEmpty(bun.default_allocator), - }; - } + + break :v version; + } else Version{ + .tag = "canary", + .zip_url = "https://github.com/oven-sh/bun/releases/download/canary/" ++ Version.zip_filename, + .size = 0, + .buf = MutableString.initEmpty(bun.default_allocator), + }; const zip_url = URL.parse(version.zip_url); const http_proxy: ?URL = env_loader.getHttpProxy(zip_url); @@ -498,7 +497,7 @@ pub const UpgradeCommand = struct { var refresher = std.Progress{}; var progress = refresher.start("Downloading", version.size); refresher.refresh(); - var async_http = ctx.allocator.create(HTTP.AsyncHTTP) catch unreachable; + var async_http = try ctx.allocator.create(HTTP.AsyncHTTP); var zip_file_buffer = try ctx.allocator.create(MutableString); zip_file_buffer.* = try MutableString.init(ctx.allocator, @max(version.size, 1024)); @@ -865,7 +864,7 @@ pub const UpgradeCommand = struct { "completions", }; - env_loader.map.put("IS_BUN_AUTO_UPDATE", "true") catch unreachable; + env_loader.map.put("IS_BUN_AUTO_UPDATE", "true") catch bun.outOfMemory(); var buf_map = try env_loader.map.cloneToEnvMap(ctx.allocator); _ = std.ChildProcess.run(.{ .allocator = ctx.allocator, @@ -873,7 +872,7 @@ pub const UpgradeCommand = struct { .cwd = target_dirname, .max_output_bytes = 4096, .env_map = &buf_map, - }) catch undefined; + }) catch {}; } Output.printStartEnd(ctx.start_time, std.time.nanoTimestamp()); diff --git a/src/output.zig b/src/output.zig index 78df0b8c9c0271..2c6160b80e3c47 100644 --- a/src/output.zig +++ b/src/output.zig @@ -829,7 +829,7 @@ fn scopedWriter() std.fs.File.Writer { std.fs.cwd().fd, path, std.os.O.TRUNC | std.os.O.CREAT | std.os.O.WRONLY, - 0o644, + if (Environment.isWindows) 0 else 0o644, ) catch |err_| { // Ensure we don't panic inside panic Scoped.loaded_env = false; diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 2cd489ee793752..62cf83aa8fb443 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -1033,8 +1033,8 @@ pub inline fn append(allocator: std.mem.Allocator, self: string, other: string) return buf; } -pub inline fn joinAlloc(allocator: std.mem.Allocator, strs: anytype) ![]u8 { - const buf = try allocator.alloc(u8, len: { +pub inline fn concatAllocT(comptime T: type, allocator: std.mem.Allocator, strs: anytype) ![]T { + const buf = try allocator.alloc(T, len: { var len: usize = 0; inline for (strs) |s| { len += s.len; @@ -1042,28 +1042,24 @@ pub inline fn joinAlloc(allocator: std.mem.Allocator, strs: anytype) ![]u8 { break :len len; }); - var remain = buf; - inline for (strs) |s| { - if (s.len > 0) { - @memcpy(remain, s); - remain = remain[s.len..]; - } - } - - return buf; + return concatBufT(T, buf, strs) catch |e| switch (e) { + error.NoSpaceLeft => unreachable, // exact size calculated + }; } -pub inline fn joinBuf(out: []u8, parts: anytype, comptime parts_len: usize) []u8 { +pub inline fn concatBufT(comptime T: type, out: []T, strs: anytype) ![]T { var remain = out; - var count: usize = 0; - inline for (0..parts_len) |i| { - const part = parts[i]; - bun.copy(u8, remain, part); - remain = remain[part.len..]; - count += part.len; + var n: usize = 0; + inline for (strs) |s| { + if (s.len > remain.len) { + return error.NoSpaceLeft; + } + @memcpy(remain.ptr, s); + remain = remain[s.len..]; + n += s.len; } - return out[0..count]; + return out[0..n]; } pub fn index(self: string, str: string) i32 { diff --git a/src/windows.zig b/src/windows.zig index df50b493dfb5f0..dcd57e14014333 100644 --- a/src/windows.zig +++ b/src/windows.zig @@ -3091,6 +3091,11 @@ pub extern "kernel32" fn OpenProcess( // https://learn.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights pub const PROCESS_QUERY_LIMITED_INFORMATION: DWORD = 0x1000; +pub fn exePathW() [:0]const u16 { + const image_path_unicode_string = &std.os.windows.peb().ProcessParameters.ImagePathName; + return image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0]; +} + pub const KEY_EVENT_RECORD = extern struct { bKeyDown: BOOL, wRepeatCount: WORD,