From 2223f1736be05c16df31ae15ce7805261b6bec49 Mon Sep 17 00:00:00 2001 From: VERSE Date: Fri, 3 Jul 2026 13:04:54 +0700 Subject: [PATCH] feat: iris uninstall command (#21) New uninstall command option ```bash IRIS uninstall ``` Fix install and uninstall script --- root/uninstall.go | 147 +++++++++++++++++++++++++++++++++++++++++++ scripts/install.sh | 14 +++-- scripts/uninstall.sh | 68 +++++++++++--------- 3 files changed, 195 insertions(+), 34 deletions(-) create mode 100644 root/uninstall.go diff --git a/root/uninstall.go b/root/uninstall.go new file mode 100644 index 0000000..6539317 --- /dev/null +++ b/root/uninstall.go @@ -0,0 +1,147 @@ +package root + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/versenilvis/iris/config" +) + +func init() { + rootCmd.AddCommand(uninstallCmd) +} + +var uninstallCmd = &cobra.Command{ + Use: "uninstall", + Short: "Uninstall Iris and remove shell integrations", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Uninstalling Iris...") + home, err := os.UserHomeDir() + if err != nil { + fmt.Printf("! could not determine home directory: %v\n", err) + return + } + + configFiles := []string{ + filepath.Join(home, ".zshrc"), + filepath.Join(home, ".bashrc"), + filepath.Join(home, ".config", "fish", "config.fish"), + } + + for _, file := range configFiles { + if cleanShellConfig(file) { + fmt.Printf("✓ Removed integration from %s\n", file) + } + } + + // Remove config, state, and cache directories + if cfgPath, err := config.ConfigPath(); err == nil { + if cfgDir := filepath.Dir(cfgPath); os.RemoveAll(cfgDir) == nil { + fmt.Printf("✓ Removed config directory: %s\n", cfgDir) + } + } + if statePath, err := config.StatePath(); err == nil { + if stateDir := filepath.Dir(statePath); os.RemoveAll(stateDir) == nil { + fmt.Printf("✓ Removed state directory: %s\n", stateDir) + } + } + if cachePath, err := config.CachePath(); err == nil { + if os.RemoveAll(cachePath) == nil { + fmt.Printf("✓ Removed cache directory: %s\n", cachePath) + } + } + + binLocations := []string{ + filepath.Join(home, ".local", "bin", "iris"), + "/usr/local/bin/iris", + } + if exe, err := os.Executable(); err == nil && exe != "" { + binLocations = append(binLocations, exe) + } + + anyFound := false + for _, loc := range binLocations { + if _, err := os.Stat(loc); err == nil { + anyFound = true + if errRemove := os.Remove(loc); errRemove == nil { + fmt.Printf("✓ Removed binary: %s\n", loc) + } else { + fmt.Printf("! Could not remove binary at %s (try with sudo): %v\n", loc, errRemove) + } + } + } + + if !anyFound { + fmt.Println("✓ No leftover binary files found") + } + + _ = os.Remove("iris.log") + + fmt.Println("\n✓ Iris has been successfully uninstalled") + if os.Getenv("IRIS_PID") != "" { + fmt.Println("\n⚠️ You are currently inside an active Iris session.") + fmt.Println("Iris runs as the parent process of this terminal - do NOT run 'pkill iris'") + fmt.Println("as it will immediately close this terminal window.") + fmt.Println("\nTo fully exit, simply close this terminal window and open a new one.") + fmt.Println("Iris will not start again since the shell config has been cleaned up.") + } else { + fmt.Println("Please close and reopen your terminal to complete the uninstall.") + } + }, +} + +func cleanShellConfig(filePath string) bool { + content, err := os.ReadFile(filePath) + if err != nil { + return false + } + + var lines []string + scanner := bufio.NewScanner(strings.NewReader(string(content))) + modified := false + skipNext := false + + for scanner.Scan() { + line := scanner.Text() + if skipNext { + skipNext = false + modified = true + continue + } + lowerLine := strings.ToLower(line) + if strings.Contains(lowerLine, "# iris autocomplete") || strings.Contains(lowerLine, "# iris autostart") { + modified = true + skipNext = true + continue + } + if strings.Contains(lowerLine, "iris init") { + modified = true + continue + } + lines = append(lines, line) + } + + if scanErr := scanner.Err(); scanErr != nil { + return false + } + + if !modified { + return false + } + + for len(lines) > 0 && strings.TrimSpace(lines[len(lines)-1]) == "" { + lines = lines[:len(lines)-1] + } + + output := strings.Join(lines, "\n") + if len(lines) > 0 { + output += "\n" + } + + err = os.WriteFile(filePath, []byte(output), 0644) + return err == nil +} diff --git a/scripts/install.sh b/scripts/install.sh index 333e4f1..9656a64 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,9 +1,6 @@ #!/bin/sh set -e -# Iris installer -# Usage: curl -sS https://raw.githubusercontent.com/versenilvis/iris/main/scripts/install.sh | sudo sh - REPO="versenilvis/iris" BIN_DIR="${BIN_DIR:-/usr/local/bin}" # allow overriding the GitHub API base URL for local testing @@ -62,8 +59,15 @@ main() { fi fi - if "${BIN_DIR}/iris" version >/dev/null 2>&1; then - echo "Installation verified." + if [ "${has_write_permission}" -eq 1 ]; then + mkdir -p "${BIN_DIR}" + cp "$bin" "${BIN_DIR}/iris" + chmod +x "${BIN_DIR}/iris" + if "${BIN_DIR}/iris" version >/dev/null 2>&1; then + echo "Installation verified." + else + echo "Warning: could not verify installed binary at ${BIN_DIR}/iris" + fi else chmod +x "$bin" if "$bin" version >/dev/null 2>&1; then diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 8731a9b..a25049f 100755 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -1,43 +1,53 @@ -#!/bin/bash +#!/bin/sh echo "Uninstalling Iris..." +if command -v iris >/dev/null 2>&1; then + if iris uninstall --help >/dev/null 2>&1; then + iris uninstall + exit 0 + fi +fi -BIN_LOCATIONS=( - "$HOME/.local/bin/iris" - "/usr/local/bin/iris" -) - -for loc in "${BIN_LOCATIONS[@]}"; do - if [ -f "$loc" ]; then - echo "Removing binary: $loc" - if [ -w "$(dirname "$loc")" ]; then - rm -f "$loc" +for loc in "${HOME}/.local/bin/iris" "/usr/local/bin/iris"; do + if [ -f "${loc}" ]; then + echo "Removing binary: ${loc}" + if [ -w "$(dirname "${loc}")" ]; then + /bin/rm -f "${loc}" 2>/dev/null || rm -f "${loc}" else - sudo rm -f "$loc" + sudo /bin/rm -f "${loc}" 2>/dev/null || sudo rm -f "${loc}" fi fi done - -CONFIG_FILES=( - "$HOME/.zshrc" - "$HOME/.bashrc" - "$HOME/.config/fish/config.fish" -) - -for file in "${CONFIG_FILES[@]}"; do - if [ -f "$file" ]; then - echo "Removing integration from $file..." - sed -i '/# Iris Autocomplete/d' "$file" - sed -i '/iris init/d' "$file" - sed -i '/^$/N;/^\n$/D' "$file" +for file in "${HOME}/.zshrc" "${HOME}/.bashrc" "${HOME}/.config/fish/config.fish"; do + if [ -f "${file}" ]; then + echo "Removing integration from ${file}..." + tmp_file=$(mktemp) + grep -v -i -E "(# iris autocomplete|# iris autostart|iris init)" "${file}" > "${tmp_file}" 2>/dev/null + status=$? + if [ "${status}" -eq 0 ] || [ "${status}" -eq 1 ]; then + mv "${tmp_file}" "${file}" + else + /bin/rm -f "${tmp_file}" 2>/dev/null || rm -f "${tmp_file}" + fi fi done -if [ -f "iris.log" ]; then - rm -f "iris.log" -fi +/bin/rm -rf "${HOME}/.config/iris" 2>/dev/null || rm -rf "${HOME}/.config/iris" +/bin/rm -rf "${HOME}/.local/share/iris" 2>/dev/null || rm -rf "${HOME}/.local/share/iris" +/bin/rm -rf "${HOME}/.cache/iris" 2>/dev/null || rm -rf "${HOME}/.cache/iris" +/bin/rm -f "iris.log" 2>/dev/null || rm -f "iris.log" echo "✓ Iris has been successfully uninstalled" -echo "Please restart your terminal or source your config file to clear the environment" +if [ -n "${IRIS_PID}" ]; then + echo "" + echo "⚠️ You are currently inside an active Iris session." + echo "Iris runs as the parent process of this terminal - do NOT run 'pkill iris'" + echo "as it will immediately close this terminal window." + echo "" + echo "To fully exit, simply close this terminal window and open a new one." + echo "Iris will not start again since the shell config has been cleaned up." +else + echo "Please close and reopen your terminal to complete the uninstall." +fi