package pkg_manager import ( "fmt" "io" "os" "os/exec" "strings" "time" "github.com/briandowns/spinner" ) // PackageManager interface defines common operations type PackageManager interface { Install(packages ...string) error Remove(packages ...string) error Search(term string) error Update() error Upgrade() error ListInstalled() error ShowInfo(pkg string) error AddRepository(repo string) error RemoveRepository(repo string) error ListRepositories() error CleanCache() error AutoRemove() error CheckDependencies(pkg string) error ShowDependencies(pkg string) error HandleSource(appName string) error } // Manager represents a concrete package manager type Manager struct { command string installCmd []string removeCmd []string searchCmd []string updateCmd []string upgradeCmd []string listCmd []string infoCmd []string addRepoCmd []string removeRepoCmd []string listRepoCmd []string cleanCacheCmd []string autoRemoveCmd []string checkDependsCmd []string showDependsCmd []string needsSudo bool versionSep string cacheDir string } // UniversalManager represents universal package managers like snap/flatpak type UniversalManager struct { Manager isAvailable bool } // PackageVersion represents a package with optional version type PackageVersion struct { Name string Version string } var universalManagers = map[string]*UniversalManager{ "snap": { Manager: Manager{ command: "snap", installCmd: []string{"install"}, removeCmd: []string{"remove"}, searchCmd: []string{"find"}, updateCmd: []string{"refresh"}, upgradeCmd: []string{"refresh"}, needsSudo: true, }, }, "flatpak": { Manager: Manager{ command: "flatpak", installCmd: []string{"install", "-y"}, removeCmd: []string{"uninstall", "-y"}, searchCmd: []string{"search"}, updateCmd: []string{"update", "-y"}, upgradeCmd: []string{"update", "-y"}, needsSudo: true, }, }, } var managers = map[string]Manager{ "apt": { command: "apt", installCmd: []string{"install", "-y"}, removeCmd: []string{"remove", "-y"}, searchCmd: []string{"search"}, updateCmd: []string{"update"}, upgradeCmd: []string{"upgrade", "-y"}, listCmd: []string{"list", "--installed"}, infoCmd: []string{"show"}, addRepoCmd: []string{"add-apt-repository", "-y"}, removeRepoCmd: []string{"add-apt-repository", "-r", "-y"}, listRepoCmd: []string{"sources"}, cleanCacheCmd: []string{"clean"}, autoRemoveCmd: []string{"autoremove", "-y"}, checkDependsCmd: []string{"check"}, showDependsCmd: []string{"depends"}, needsSudo: true, versionSep: "=", cacheDir: "/var/cache/apt", }, "dnf": { command: "dnf", installCmd: []string{"install", "-y"}, removeCmd: []string{"remove", "-y"}, searchCmd: []string{"search"}, updateCmd: []string{"check-update"}, upgradeCmd: []string{"upgrade", "-y"}, listCmd: []string{"list", "installed"}, infoCmd: []string{"info"}, addRepoCmd: []string{"config-manager", "--add-repo"}, removeRepoCmd: []string{"config-manager", "--disable"}, listRepoCmd: []string{"repolist"}, cleanCacheCmd: []string{"clean", "all"}, autoRemoveCmd: []string{"autoremove", "-y"}, checkDependsCmd: []string{"check-dependencies"}, showDependsCmd: []string{"repoquery", "--requires"}, needsSudo: true, versionSep: "-", cacheDir: "/var/cache/dnf", }, "yum": { command: "yum", installCmd: []string{"install", "-y"}, removeCmd: []string{"remove", "-y"}, searchCmd: []string{"search"}, updateCmd: []string{"check-update"}, upgradeCmd: []string{"upgrade", "-y"}, needsSudo: true, versionSep: "-", cacheDir: "/var/cache/yum", }, "pacman": { command: "pacman", installCmd: []string{"-S", "--noconfirm"}, removeCmd: []string{"-R", "--noconfirm"}, searchCmd: []string{"-Ss"}, updateCmd: []string{"-Sy"}, upgradeCmd: []string{"-Syu", "--noconfirm"}, needsSudo: true, versionSep: "=", cacheDir: "/var/cache/pacman", }, "zypper": { command: "zypper", installCmd: []string{"install", "-y"}, removeCmd: []string{"remove", "-y"}, searchCmd: []string{"search"}, updateCmd: []string{"refresh"}, upgradeCmd: []string{"update", "-y"}, needsSudo: true, versionSep: "=", cacheDir: "/var/cache/zypp", }, "apk": { command: "apk", installCmd: []string{"add"}, removeCmd: []string{"del"}, searchCmd: []string{"search"}, updateCmd: []string{"update"}, upgradeCmd: []string{"upgrade"}, needsSudo: true, versionSep: "=", cacheDir: "/var/cache/apk", }, "xbps": { command: "xbps-install", installCmd: []string{"-y"}, removeCmd: []string{"-R"}, searchCmd: []string{"-Rs"}, updateCmd: []string{"-S"}, upgradeCmd: []string{"-Su"}, listCmd: []string{"-l"}, infoCmd: []string{"-S"}, addRepoCmd: []string{"-R", "add"}, removeRepoCmd: []string{"-R", "remove"}, listRepoCmd: []string{"-R", "list"}, cleanCacheCmd: []string{"-Op"}, autoRemoveCmd: []string{"-Oo"}, checkDependsCmd: []string{"-C"}, showDependsCmd: []string{"-x"}, needsSudo: true, versionSep: "-", cacheDir: "/var/cache/xbps", }, "emerge": { command: "emerge", installCmd: []string{"--ask", "n"}, removeCmd: []string{"--unmerge", "--ask", "n"}, searchCmd: []string{"--search"}, updateCmd: []string{"--sync"}, upgradeCmd: []string{"--update", "--deep", "--ask", "n", "@world"}, listCmd: []string{"--list-sets"}, infoCmd: []string{"--info"}, addRepoCmd: []string{"--repos-conf", "add"}, removeRepoCmd: []string{"--repos-conf", "remove"}, listRepoCmd: []string{"--repos-conf", "list"}, cleanCacheCmd: []string{"--depclean"}, autoRemoveCmd: []string{"--depclean"}, checkDependsCmd: []string{"--depclean", "-p"}, showDependsCmd: []string{"--deps"}, needsSudo: true, versionSep: "-", cacheDir: "/var/cache/portage", }, } // ParsePackageVersion parses package name and version func ParsePackageVersion(pkg string) PackageVersion { parts := strings.Split(pkg, "@") if len(parts) == 2 { return PackageVersion{ Name: parts[0], Version: parts[1], } } return PackageVersion{Name: pkg} } // formatPackageWithVersion formats package with version using manager's separator func (m *Manager) formatPackageWithVersion(pv PackageVersion) string { if pv.Version == "" { return pv.Name } return fmt.Sprintf("%s%s%s", pv.Name, m.versionSep, pv.Version) } // CheckPackageExists verifies if a package exists in the repositories func (m *Manager) CheckPackageExists(pkg string) bool { cmd := exec.Command(m.command, append(m.searchCmd, pkg)...) output, err := cmd.Output() if err != nil { return false } // Different package managers have different output formats // This is a simple check that should work for most return len(output) > 0 } // Install packages with version support func (m *Manager) Install(packages ...string) error { if len(packages) == 0 { return fmt.Errorf("no packages specified") } // Validate packages first for _, pkg := range packages { pv := ParsePackageVersion(pkg) if !m.CheckPackageExists(pv.Name) { return fmt.Errorf("package not found: %s", pv.Name) } } formattedPkgs := make([]string, len(packages)) for i, pkg := range packages { pv := ParsePackageVersion(pkg) formattedPkgs[i] = m.formatPackageWithVersion(pv) } return m.execCommand(m.installCmd, formattedPkgs...) } // InitializeUniversalManagers checks which universal package managers are available func InitializeUniversalManagers() { for _, mgr := range universalManagers { _, err := exec.LookPath(mgr.command) mgr.isAvailable = err == nil } } // GetUniversalManager returns a universal package manager if available func GetUniversalManager(name string) (*UniversalManager, bool) { if mgr, exists := universalManagers[name]; exists && mgr.isAvailable { return mgr, true } return nil, false } type OutputMode int const ( OutputQuiet OutputMode = iota OutputVerbose ) var outputMode = OutputQuiet // SetOutputMode sets the output verbosity func SetOutputMode(mode OutputMode) { outputMode = mode } // execCommandWithSudo executes a command with sudo func (m *Manager) execCommandWithSudo(baseCmd []string, args ...string) error { cmdArgs := append(baseCmd, args...) cmdArgs = append([]string{m.command}, cmdArgs...) cmd := exec.Command("sudo", cmdArgs...) // Always show sudo password prompt cmd.Stdin = os.Stdin if outputMode == OutputVerbose { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr } else { cmd.Stdout = io.Discard cmd.Stderr = io.Discard } return cmd.Run() } func (m *Manager) execCommand(baseCmd []string, args ...string) error { cmdArgs := append(baseCmd, args...) var cmd *exec.Cmd if m.needsSudo && os.Geteuid() != 0 { return m.execCommandWithSudo(baseCmd, args...) } else { cmd = exec.Command(m.command, cmdArgs...) if outputMode == OutputVerbose { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr } else { cmd.Stdout = io.Discard cmd.Stderr = io.Discard } cmd.Stdin = os.Stdin return cmd.Run() } } // Remove packages func (m *Manager) Remove(packages ...string) error { if len(packages) == 0 { return fmt.Errorf("no packages specified") } return m.execCommand(m.removeCmd, packages...) } // Search for packages func (m *Manager) Search(term string) error { if term == "" { return fmt.Errorf("no search term specified") } return m.execCommand(m.searchCmd, term) } // Update package lists func (m *Manager) Update() error { return m.execCommand(m.updateCmd) } // Upgrade installed packages func (m *Manager) Upgrade() error { return m.execCommand(m.upgradeCmd) } // ListInstalled lists all installed packages func (m *Manager) ListInstalled() error { return m.execCommand(m.listCmd) } // ShowInfo shows detailed information about a package func (m *Manager) ShowInfo(pkg string) error { return m.execCommand(m.infoCmd, pkg) } // AddRepository adds a new repository func (m *Manager) AddRepository(repo string) error { return m.execCommand(m.addRepoCmd, repo) } // RemoveRepository removes a repository func (m *Manager) RemoveRepository(repo string) error { return m.execCommand(m.removeRepoCmd, repo) } // ListRepositories lists all configured repositories func (m *Manager) ListRepositories() error { return m.execCommand(m.listRepoCmd) } // CleanCache cleans the package manager cache func (m *Manager) CleanCache() error { return m.execCommand(m.cleanCacheCmd) } // AutoRemove removes unused dependencies func (m *Manager) AutoRemove() error { return m.execCommand(m.autoRemoveCmd) } // CheckDependencies checks for dependency issues func (m *Manager) CheckDependencies(pkg string) error { if pkg == "" { return m.execCommand(m.checkDependsCmd) } return m.execCommand(m.checkDependsCmd, pkg) } // ShowDependencies shows dependencies for a package func (m *Manager) ShowDependencies(pkg string) error { return m.execCommand(m.showDependsCmd, pkg) } // DetectPackageManager identifies the system's package manager func DetectPackageManager() (*Manager, error) { for _, mgr := range managers { if _, err := exec.LookPath(mgr.command); err == nil { return &mgr, nil } } return nil, fmt.Errorf("no supported package manager found") } // NeedsSudo returns whether this package manager needs sudo func (m *Manager) NeedsSudo() bool { return m.needsSudo && os.Geteuid() != 0 } func (m *Manager) HandleSource(appName string) error { fmt.Printf("Searching source code for %s...\n", appName) s := spinner.New(spinner.CharSets[9], 100*time.Millisecond) s.Start() repos, err := SearchGitHubSource(appName) s.Stop() if err != nil { return fmt.Errorf("error searching GitHub: %w", err) } if len(repos) == 0 { fmt.Printf("No source code repositories found for '%s'\n", appName) return nil } fmt.Printf("\nFound %d source code repositories:\n\n", len(repos)) for i, repo := range repos { fmt.Printf("%d. %s\n", i+1, repo.FullName) if repo.Description != "" { fmt.Printf(" Description: %s\n", repo.Description) } fmt.Printf(" Language: %s\n", repo.Language) fmt.Printf(" Stars: %d\n", repo.Stars) fmt.Printf(" URL: %s\n\n", repo.HTMLURL) } return nil }