mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Add uninstall option for vt command in settings (#407)
This commit is contained in:
parent
af569666b7
commit
f9869293a7
2 changed files with 194 additions and 19 deletions
|
|
@ -40,37 +40,67 @@ struct CLIInstallationSection: View {
|
||||||
HStack {
|
HStack {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.scaleEffect(0.7)
|
.scaleEffect(0.7)
|
||||||
Text("Installing...")
|
Text(cliInstaller.isUninstalling ? "Uninstalling..." : "Installing...")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if cliInstaller.isInstalled {
|
if cliInstaller.isInstalled {
|
||||||
// Updated status
|
// Updated status
|
||||||
if cliInstaller.isOutdated {
|
if cliInstaller.isOutdated {
|
||||||
Button("Update 'vt' Command") {
|
HStack(spacing: 8) {
|
||||||
Task {
|
Button("Update 'vt' Command") {
|
||||||
await cliInstaller.install()
|
Task {
|
||||||
|
await cliInstaller.install()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.buttonStyle(.bordered)
|
||||||
.buttonStyle(.bordered)
|
.disabled(cliInstaller.isInstalling)
|
||||||
.disabled(cliInstaller.isInstalling)
|
|
||||||
} else {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundColor(.green)
|
|
||||||
Text("VT installed")
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
|
|
||||||
// Show reinstall button in debug mode
|
|
||||||
if debugMode {
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
cliInstaller.installCLITool()
|
Task {
|
||||||
|
await cliInstaller.uninstall()
|
||||||
|
}
|
||||||
}, label: {
|
}, label: {
|
||||||
Image(systemName: "arrow.clockwise.circle")
|
Image(systemName: "trash")
|
||||||
.font(.system(size: 14))
|
.font(.system(size: 14))
|
||||||
})
|
})
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.red)
|
||||||
.help("Reinstall CLI tool")
|
.disabled(cliInstaller.isInstalling)
|
||||||
|
.help("Uninstall CLI tool")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.foregroundColor(.green)
|
||||||
|
Text("VT installed")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
|
// Show reinstall button in debug mode
|
||||||
|
if debugMode {
|
||||||
|
Button(action: {
|
||||||
|
cliInstaller.installCLITool()
|
||||||
|
}, label: {
|
||||||
|
Image(systemName: "arrow.clockwise.circle")
|
||||||
|
.font(.system(size: 14))
|
||||||
|
})
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.help("Reinstall CLI tool")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
Task {
|
||||||
|
await cliInstaller.uninstall()
|
||||||
|
}
|
||||||
|
}, label: {
|
||||||
|
Image(systemName: "trash")
|
||||||
|
.font(.system(size: 14))
|
||||||
|
})
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.disabled(cliInstaller.isInstalling)
|
||||||
|
.help("Uninstall CLI tool")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ final class CLIInstaller {
|
||||||
var isInstalling = false
|
var isInstalling = false
|
||||||
var lastError: String?
|
var lastError: String?
|
||||||
var isOutdated = false
|
var isOutdated = false
|
||||||
|
var isUninstalling = false
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
|
|
||||||
|
|
@ -142,6 +143,43 @@ final class CLIInstaller {
|
||||||
performInstallation()
|
performInstallation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Uninstalls the vt CLI tool (async version)
|
||||||
|
func uninstall() async {
|
||||||
|
await MainActor.run {
|
||||||
|
uninstallCLITool()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uninstalls the vt CLI tool from /usr/local/bin and /opt/homebrew/bin
|
||||||
|
func uninstallCLITool() {
|
||||||
|
logger.info("CLIInstaller: Starting CLI tool uninstallation...")
|
||||||
|
isInstalling = true
|
||||||
|
isUninstalling = true
|
||||||
|
lastError = nil
|
||||||
|
|
||||||
|
// Show confirmation dialog
|
||||||
|
let confirmAlert = NSAlert()
|
||||||
|
confirmAlert.messageText = "Uninstall VT Command Line Tool"
|
||||||
|
confirmAlert
|
||||||
|
.informativeText =
|
||||||
|
"This will remove the 'vt' command from your system. Administrator privileges are required."
|
||||||
|
confirmAlert.addButton(withTitle: "Uninstall")
|
||||||
|
confirmAlert.addButton(withTitle: "Cancel")
|
||||||
|
confirmAlert.alertStyle = .informational
|
||||||
|
confirmAlert.icon = NSApp.applicationIconImage
|
||||||
|
|
||||||
|
let response = confirmAlert.runModal()
|
||||||
|
if response != .alertFirstButtonReturn {
|
||||||
|
logger.info("CLIInstaller: User cancelled uninstallation")
|
||||||
|
isInstalling = false
|
||||||
|
isUninstalling = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the uninstallation
|
||||||
|
performUninstallation()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private Implementation
|
// MARK: - Private Implementation
|
||||||
|
|
||||||
/// Performs the actual installation with sudo privileges
|
/// Performs the actual installation with sudo privileges
|
||||||
|
|
@ -250,6 +288,102 @@ final class CLIInstaller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs the actual uninstallation with sudo privileges
|
||||||
|
private func performUninstallation() {
|
||||||
|
logger.info("CLIInstaller: Uninstalling vt script")
|
||||||
|
|
||||||
|
// Create the uninstallation script
|
||||||
|
let script = """
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Remove vt script from /usr/local/bin
|
||||||
|
if [ -L "/usr/local/bin/vt" ] || [ -f "/usr/local/bin/vt" ]; then
|
||||||
|
rm -f "/usr/local/bin/vt"
|
||||||
|
echo "Removed vt from /usr/local/bin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove vt script from /opt/homebrew/bin (Apple Silicon Homebrew path)
|
||||||
|
if [ -L "/opt/homebrew/bin/vt" ] || [ -f "/opt/homebrew/bin/vt" ]; then
|
||||||
|
rm -f "/opt/homebrew/bin/vt"
|
||||||
|
echo "Removed vt from /opt/homebrew/bin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up old vibetunnel binary if it exists
|
||||||
|
if [ -f "/usr/local/bin/vibetunnel" ]; then
|
||||||
|
rm -f "/usr/local/bin/vibetunnel"
|
||||||
|
echo "Removed old vibetunnel binary"
|
||||||
|
fi
|
||||||
|
"""
|
||||||
|
|
||||||
|
// Write the script to a temporary file
|
||||||
|
let tempDir = FileManager.default.temporaryDirectory
|
||||||
|
let scriptURL = tempDir.appendingPathComponent("uninstall_vt_cli.sh")
|
||||||
|
|
||||||
|
do {
|
||||||
|
try script.write(to: scriptURL, atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
// Make the script executable
|
||||||
|
let attributes: [FileAttributeKey: Any] = [.posixPermissions: 0o755]
|
||||||
|
try FileManager.default.setAttributes(attributes, ofItemAtPath: scriptURL.path)
|
||||||
|
|
||||||
|
logger.info("CLIInstaller: Created uninstallation script at \(scriptURL.path)")
|
||||||
|
|
||||||
|
// Execute with osascript to get sudo dialog
|
||||||
|
let appleScript = """
|
||||||
|
do shell script "bash '\(scriptURL.path)'" with administrator privileges
|
||||||
|
"""
|
||||||
|
|
||||||
|
let task = Process()
|
||||||
|
task.launchPath = "/usr/bin/osascript"
|
||||||
|
task.arguments = ["-e", appleScript]
|
||||||
|
|
||||||
|
let pipe = Pipe()
|
||||||
|
let errorPipe = Pipe()
|
||||||
|
task.standardOutput = pipe
|
||||||
|
task.standardError = errorPipe
|
||||||
|
|
||||||
|
try task.run()
|
||||||
|
task.waitUntilExit()
|
||||||
|
|
||||||
|
// Clean up the temporary script
|
||||||
|
try? FileManager.default.removeItem(at: scriptURL)
|
||||||
|
|
||||||
|
if task.terminationStatus == 0 {
|
||||||
|
logger.info("CLIInstaller: Uninstallation completed successfully")
|
||||||
|
isInstalled = false
|
||||||
|
isInstalling = false
|
||||||
|
isUninstalling = false
|
||||||
|
showUninstallSuccess()
|
||||||
|
// Refresh installation status
|
||||||
|
checkInstallationStatus()
|
||||||
|
} else {
|
||||||
|
let errorString: String
|
||||||
|
do {
|
||||||
|
if let errorData = try errorPipe.fileHandleForReading.readToEnd() {
|
||||||
|
errorString = String(data: errorData, encoding: .utf8) ?? "Unknown error"
|
||||||
|
} else {
|
||||||
|
errorString = "Unknown error"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
logger.debug("Could not read error output: \(error.localizedDescription)")
|
||||||
|
errorString = "Unknown error (could not read stderr)"
|
||||||
|
}
|
||||||
|
logger.error("CLIInstaller: Uninstallation failed with status \(task.terminationStatus): \(errorString)")
|
||||||
|
lastError = "Uninstallation failed: \(errorString)"
|
||||||
|
isInstalling = false
|
||||||
|
isUninstalling = false
|
||||||
|
showError("Uninstallation failed: \(errorString)")
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
logger.error("CLIInstaller: Uninstallation failed with error: \(error)")
|
||||||
|
lastError = "Uninstallation failed: \(error.localizedDescription)"
|
||||||
|
isInstalling = false
|
||||||
|
isUninstalling = false
|
||||||
|
showError("Uninstallation failed: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Shows success message after installation
|
/// Shows success message after installation
|
||||||
private func showSuccess() {
|
private func showSuccess() {
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
|
|
@ -263,6 +397,17 @@ final class CLIInstaller {
|
||||||
alert.runModal()
|
alert.runModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shows success message after uninstallation
|
||||||
|
private func showUninstallSuccess() {
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.messageText = "CLI Tools Uninstalled Successfully"
|
||||||
|
alert.informativeText = "The 'vt' command has been removed from your system."
|
||||||
|
alert.addButton(withTitle: "OK")
|
||||||
|
alert.alertStyle = .informational
|
||||||
|
alert.icon = NSApp.applicationIconImage
|
||||||
|
alert.runModal()
|
||||||
|
}
|
||||||
|
|
||||||
/// Shows error message for installation failures
|
/// Shows error message for installation failures
|
||||||
private func showError(_ message: String) {
|
private func showError(_ message: String) {
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue