mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
windows: changes to install/upgrade/uninstallation process (#9025)
This commit is contained in:
Submodule src/bun.js/WebKit updated: c0c648e5a7...c3712c13dc
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
10
src/bun.zig
10
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,
|
||||
|
||||
@@ -2307,7 +2307,7 @@ const GitHandler = struct {
|
||||
process.stderr_behavior = .Inherit;
|
||||
|
||||
_ = try process.spawnAndWait();
|
||||
_ = process.kill() catch undefined;
|
||||
_ = process.kill() catch {};
|
||||
}
|
||||
|
||||
Output.prettyError("\n", .{});
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Install-Bun -Version $Version -ForceBaseline $ForceBaseline
|
||||
|
||||
@@ -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\<whatever>.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("<r><red>error:<r> 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 => {},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
112
src/cli/uninstall.ps1
Normal file
112
src/cli/uninstall.ps1
Normal file
@@ -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
|
||||
}
|
||||
@@ -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: <r><red>{s}<r>", .{@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: <r><red>tag_name<r> 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("<r><b>Downgrading from Bun <blue>{s}-canary<r> to Bun <cyan>v{s}<r><r>\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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user