From this discussion: https://www.reddit.com/r/zsh/comments/1v8rq7s/comment/p09b05o/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button --- Problem: Iris hardcodes config paths to `~/.zshrc` and `~/.config/fish/config.fish`. This ignores users' custom `ZDOTDIR` or `XDG_CONFIG_HOME` setups, polluting their `$HOME` directory during init and failing to scan their actual aliases. Also, ZDOTDIR is often set in `~/.zshenv` without export, making it invisible to Go's `os.Getenv` Solution: - Bash: default `$HOME/.bashrc` so dont mind about it - For Zsh, Iris now probes zsh directly (`zsh -c "echo $ZDOTDIR"`) with a timeout to accurately resolve custom paths even if they aint exported - For Fish, Iris now natively respects the `XDG_CONFIG_HOME` environment variable - Shared this robust resolution logic across init, uninstall, and alias scanning - Fixed alias scanning to process files in the correct zsh startup order (`.zshenv` -> `.zprofile` -> `.zshrc`) and correctly merge them
151 lines
3.8 KiB
Go
151 lines
3.8 KiB
Go
package root
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/versenilvis/iris/integration/shell"
|
|
"github.com/versenilvis/iris/internal/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
|
|
}
|
|
|
|
zshrcPath := filepath.Join(shell.GetZshConfigDir(), ".zshrc")
|
|
|
|
configFiles := []string{
|
|
zshrcPath,
|
|
filepath.Join(home, ".bashrc"),
|
|
filepath.Join(shell.GetFishConfigDir(), "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
|
|
}
|