mirror of
https://github.com/gogrlx/snack.git
synced 2026-04-02 05:08:42 -07:00
- Add brew package for Homebrew support on macOS and Linux - Implement NameNormalizer interface (NormalizeName, ParseArch) for all providers - Add darwin platform detection with Homebrew as default - Consolidate capabilities by removing separate *_linux.go/*_other.go files - Update tests for new capability expectations - Add comprehensive tests for AUR and brew providers - Update README with capability matrix and modern Target API usage 💘 Generated with Crush Assisted-by: AWS Claude Opus 4.5 via Crush <crush@charm.land>
133 lines
3.6 KiB
Go
133 lines
3.6 KiB
Go
//go:build linux
|
|
|
|
package apk
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/gogrlx/snack"
|
|
)
|
|
|
|
func latestVersion(ctx context.Context, pkg string) (string, error) {
|
|
// Use `apk search -e` for exact match, output is "pkg-version"
|
|
c := exec.CommandContext(ctx, "apk", "search", "-e", pkg)
|
|
out, err := c.CombinedOutput()
|
|
if err != nil {
|
|
return "", fmt.Errorf("apk latestVersion %s: %w", pkg, snack.ErrNotFound)
|
|
}
|
|
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
|
if len(lines) == 0 || lines[0] == "" {
|
|
return "", fmt.Errorf("apk latestVersion %s: %w", pkg, snack.ErrNotFound)
|
|
}
|
|
// Output: "pkg-1.2.3-r0"
|
|
_, ver := splitNameVersion(lines[0])
|
|
if ver == "" {
|
|
return "", fmt.Errorf("apk latestVersion %s: %w", pkg, snack.ErrNotFound)
|
|
}
|
|
return ver, nil
|
|
}
|
|
|
|
func listUpgrades(ctx context.Context) ([]snack.Package, error) {
|
|
c := exec.CommandContext(ctx, "apk", "upgrade", "--simulate")
|
|
out, err := c.CombinedOutput()
|
|
if err != nil {
|
|
// If simulate fails, it could mean no upgrades or an error
|
|
outStr := strings.TrimSpace(string(out))
|
|
if outStr == "" || strings.Contains(outStr, "OK") {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("apk listUpgrades: %w", err)
|
|
}
|
|
return parseUpgradeSimulation(string(out)), nil
|
|
}
|
|
|
|
func upgradeAvailable(ctx context.Context, pkg string) (bool, error) {
|
|
upgrades, err := listUpgrades(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, u := range upgrades {
|
|
if u.Name == pkg {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func versionCmp(ctx context.Context, ver1, ver2 string) (int, error) {
|
|
c := exec.CommandContext(ctx, "apk", "version", "-t", ver1, ver2)
|
|
out, err := c.CombinedOutput()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("apk versionCmp: %w", err)
|
|
}
|
|
result := strings.TrimSpace(string(out))
|
|
switch result {
|
|
case "<":
|
|
return -1, nil
|
|
case ">":
|
|
return 1, nil
|
|
case "=":
|
|
return 0, nil
|
|
default:
|
|
return 0, fmt.Errorf("apk versionCmp: unexpected output %q", result)
|
|
}
|
|
}
|
|
|
|
// autoremove is a no-op for apk. Alpine's apk does not have a direct
|
|
// autoremove equivalent. `apk fix` repairs packages but does not remove
|
|
// unused dependencies.
|
|
func autoremove(_ context.Context, _ ...snack.Option) error {
|
|
return nil
|
|
}
|
|
|
|
func clean(ctx context.Context) error {
|
|
c := exec.CommandContext(ctx, "apk", "cache", "clean")
|
|
out, err := c.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("apk clean: %s: %w", strings.TrimSpace(string(out)), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func fileList(ctx context.Context, pkg string) ([]string, error) {
|
|
c := exec.CommandContext(ctx, "apk", "info", "-L", pkg)
|
|
out, err := c.CombinedOutput()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("apk fileList %s: %w", pkg, snack.ErrNotInstalled)
|
|
}
|
|
var files []string
|
|
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
// First line is "pkg-version contains:" — skip it
|
|
if strings.Contains(line, " contains:") {
|
|
continue
|
|
}
|
|
files = append(files, line)
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
func owner(ctx context.Context, path string) (string, error) {
|
|
c := exec.CommandContext(ctx, "apk", "info", "--who-owns", path)
|
|
out, err := c.CombinedOutput()
|
|
if err != nil {
|
|
return "", fmt.Errorf("apk owner %s: %w", path, snack.ErrNotFound)
|
|
}
|
|
// Output: "/path is owned by pkg-version"
|
|
outStr := strings.TrimSpace(string(out))
|
|
if idx := strings.Index(outStr, "is owned by "); idx != -1 {
|
|
nameVer := strings.TrimSpace(outStr[idx+len("is owned by "):])
|
|
name, _ := splitNameVersion(nameVer)
|
|
if name != "" {
|
|
return name, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("apk owner %s: unexpected output %q", path, outStr)
|
|
}
|