From e6e12ab5fd18f3267ffef4993c86cc0fef6c0eef Mon Sep 17 00:00:00 2001 From: MDietermann Date: Tue, 14 Apr 2026 21:44:47 +0200 Subject: [PATCH] feat(platform): add Reinstall helpers to Pacman and Yay --- internal/platform/aur.go | 11 +++++++++++ internal/platform/aur_test.go | 26 ++++++++++++++++++++++++++ internal/platform/pacman.go | 11 +++++++++++ internal/platform/pacman_test.go | 21 +++++++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/internal/platform/aur.go b/internal/platform/aur.go index fc337ad..903c535 100644 --- a/internal/platform/aur.go +++ b/internal/platform/aur.go @@ -52,6 +52,17 @@ func (y *Yay) Install(ctx context.Context, pkgs []string) error { return y.Runner.Run(ctx, "yay", args...) } +// Reinstall runs `yay -S --noconfirm ` WITHOUT `--needed`, forcing +// yay/pacman to re-fetch and reinstall the packages even if already present. +// Used by the `--force` flag to bypass idempotency. +func (y *Yay) Reinstall(ctx context.Context, pkgs []string) error { + if len(pkgs) == 0 { + return nil + } + args := append([]string{"-S", "--noconfirm"}, pkgs...) + return y.Runner.Run(ctx, "yay", args...) +} + func shellQuote(s string) string { return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" } diff --git a/internal/platform/aur_test.go b/internal/platform/aur_test.go index 0726211..09b5f84 100644 --- a/internal/platform/aur_test.go +++ b/internal/platform/aur_test.go @@ -70,6 +70,32 @@ func TestAUREnsureInstalledBootstrapsYay(t *testing.T) { } } +func TestYayReinstall(t *testing.T) { + r := &aurFakeRunner{yayInstalled: true} + y := NewYay(r) + if err := y.Reinstall(context.Background(), []string{"git", "curl"}); err != nil { + t.Fatal(err) + } + if len(r.runCalls) == 0 { + t.Fatal("expected yay reinstall call") + } + c := r.runCalls[len(r.runCalls)-1] + want := []string{"yay", "-S", "--noconfirm", "git", "curl"} + if len(c) != len(want) { + t.Fatalf("call = %v, want %v", c, want) + } + for i, w := range want { + if c[i] != w { + t.Fatalf("call = %v, want %v", c, want) + } + } + for _, a := range c { + if a == "--needed" { + t.Fatalf("Reinstall must not pass --needed; got %v", c) + } + } +} + func TestAURInstall(t *testing.T) { r := &aurFakeRunner{yayInstalled: true} y := NewYay(r) diff --git a/internal/platform/pacman.go b/internal/platform/pacman.go index d720120..d9d249c 100644 --- a/internal/platform/pacman.go +++ b/internal/platform/pacman.go @@ -76,6 +76,17 @@ func (p Pacman) Install(ctx context.Context, pkgs []string) error { return p.Runner.Run(ctx, "sudo", args...) } +// Reinstall runs `sudo pacman -S --noconfirm ` WITHOUT `--needed`, +// so pacman physically re-fetches and reinstalls the packages even if they +// are already present. Used by the `--force` flag to bypass idempotency. +func (p Pacman) Reinstall(ctx context.Context, pkgs []string) error { + if len(pkgs) == 0 { + return nil + } + args := append([]string{"pacman", "-S", "--noconfirm"}, pkgs...) + return p.Runner.Run(ctx, "sudo", args...) +} + // Update runs `sudo pacman -Syu --noconfirm` (full sync + system upgrade). // On Arch, a bare `pacman -Sy` followed by an install is the partial-upgrade // anti-pattern that can break the system when a dep's ABI changes, so we diff --git a/internal/platform/pacman_test.go b/internal/platform/pacman_test.go index 4d76bc5..2b6a3dc 100644 --- a/internal/platform/pacman_test.go +++ b/internal/platform/pacman_test.go @@ -39,6 +39,27 @@ func TestPacmanInstallShellsOutCorrectly(t *testing.T) { } } +func TestPacmanReinstall(t *testing.T) { + r := &fakeRunner{} + p := NewPacman(r) + if err := p.Reinstall(context.Background(), []string{"git", "curl"}); err != nil { + t.Fatal(err) + } + if len(r.runCalls) != 1 { + t.Fatalf("run calls = %d", len(r.runCalls)) + } + got := r.runCalls[0] + want := []string{"sudo", "pacman", "-S", "--noconfirm", "git", "curl"} + if !reflect.DeepEqual(got, want) { + t.Fatalf("args = %v, want %v", got, want) + } + for _, a := range got { + if a == "--needed" { + t.Fatalf("Reinstall must not pass --needed; got %v", got) + } + } +} + func TestPacmanInstallEmptyIsNoop(t *testing.T) { r := &fakeRunner{} p := NewPacman(r)