feat(platform): add Reinstall helpers to Pacman and Yay

This commit is contained in:
MDietermann
2026-04-14 21:44:47 +02:00
parent 1cd3886d56
commit e6e12ab5fd
4 changed files with 69 additions and 0 deletions

View File

@@ -52,6 +52,17 @@ func (y *Yay) Install(ctx context.Context, pkgs []string) error {
return y.Runner.Run(ctx, "yay", args...) return y.Runner.Run(ctx, "yay", args...)
} }
// Reinstall runs `yay -S --noconfirm <pkgs...>` 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 { func shellQuote(s string) string {
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
} }

View File

@@ -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) { func TestAURInstall(t *testing.T) {
r := &aurFakeRunner{yayInstalled: true} r := &aurFakeRunner{yayInstalled: true}
y := NewYay(r) y := NewYay(r)

View File

@@ -76,6 +76,17 @@ func (p Pacman) Install(ctx context.Context, pkgs []string) error {
return p.Runner.Run(ctx, "sudo", args...) return p.Runner.Run(ctx, "sudo", args...)
} }
// Reinstall runs `sudo pacman -S --noconfirm <pkgs...>` 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). // Update runs `sudo pacman -Syu --noconfirm` (full sync + system upgrade).
// On Arch, a bare `pacman -Sy` followed by an install is the partial-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 // anti-pattern that can break the system when a dep's ABI changes, so we

View File

@@ -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) { func TestPacmanInstallEmptyIsNoop(t *testing.T) {
r := &fakeRunner{} r := &fakeRunner{}
p := NewPacman(r) p := NewPacman(r)