mirror of
https://github.com/chenasraf/sofmani.git
synced 2026-05-17 17:28:04 +00:00
feat: pacman/yay installers
This commit is contained in:
@@ -229,6 +229,11 @@ For a full list with all the supported options, see [the docs](./docs/installer-
|
||||
- Installs packages using apt/apk install.
|
||||
- Use `type: apt` for `apt install`, and `type: apk` for `apk add`.
|
||||
|
||||
- **`pacman`/`yay`**
|
||||
|
||||
- Installs packages using pacman or yay (Arch Linux).
|
||||
- Use `type: pacman` for official repository packages, and `type: yay` for AUR packages.
|
||||
|
||||
- **`pipx`**
|
||||
|
||||
- Installs packages using pipx.
|
||||
|
||||
@@ -65,6 +65,8 @@ const (
|
||||
InstallerTypeYarn InstallerType = "yarn" // InstallerTypeYarn represents a yarn package installer.
|
||||
InstallerTypePipx InstallerType = "pipx" // InstallerTypePipx represents a pipx package installer.
|
||||
InstallerTypeManifest InstallerType = "manifest" // InstallerTypeManifest represents a manifest file installer.
|
||||
InstallerTypePacman InstallerType = "pacman" // InstallerTypePacman represents a pacman package installer.
|
||||
InstallerTypeYay InstallerType = "yay" // InstallerTypeYay represents a yay (AUR helper) package installer.
|
||||
)
|
||||
|
||||
// Environ returns the combined environment variables for the installer as a slice of strings.
|
||||
|
||||
@@ -212,6 +212,15 @@ These fields are shared by all installer types. Some fields may vary in behavior
|
||||
- **Description**: Installs packages using apt install or apt add.
|
||||
- Use `type: apt` for `apt install`, and `type: apk` for `apk add`.
|
||||
|
||||
- **`pacman`/`yay`**
|
||||
|
||||
- **Description**: Installs packages using pacman or yay (Arch Linux).
|
||||
- Use `type: pacman` for official Arch repository packages.
|
||||
- Use `type: yay` for AUR (Arch User Repository) packages.
|
||||
- Both use `--noconfirm` for non-interactive installation.
|
||||
- **Options**:
|
||||
- `opts.needed`: Skip reinstalling up-to-date packages (`--needed` flag).
|
||||
|
||||
- **`pipx`**
|
||||
|
||||
- **Description**: Installs packages using pipx.
|
||||
@@ -380,6 +389,23 @@ install:
|
||||
only: ['linux']
|
||||
```
|
||||
|
||||
### pacman/yay
|
||||
|
||||
```yaml
|
||||
install:
|
||||
# Install from official Arch repositories
|
||||
- name: neovim
|
||||
type: pacman
|
||||
bin_name: nvim
|
||||
opts:
|
||||
needed: true # Skip if already up-to-date
|
||||
|
||||
# Install from AUR using yay
|
||||
- name: visual-studio-code-bin
|
||||
type: yay
|
||||
bin_name: code
|
||||
```
|
||||
|
||||
### docker
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
# Portable installer for sofmani (no Bashisms)
|
||||
# Env vars you can override: INSTALL_DIR, REPO
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ func GetInstaller(config *appconfig.AppConfig, data *appconfig.InstallerData) (I
|
||||
return NewNpmInstaller(config, data), nil
|
||||
case appconfig.InstallerTypeApt, appconfig.InstallerTypeApk:
|
||||
return NewAptInstaller(config, data), nil
|
||||
case appconfig.InstallerTypePacman, appconfig.InstallerTypeYay:
|
||||
return NewPacmanInstaller(config, data), nil
|
||||
case appconfig.InstallerTypePipx:
|
||||
return NewPipxInstaller(config, data), nil
|
||||
case appconfig.InstallerTypeGitHubRelease:
|
||||
|
||||
@@ -127,5 +127,9 @@ func FillDefaults(data *appconfig.InstallerData) {
|
||||
data.Platforms = &platform.Platforms{
|
||||
Only: &[]platform.Platform{platform.PlatformLinux},
|
||||
}
|
||||
case appconfig.InstallerTypePacman, appconfig.InstallerTypeYay:
|
||||
data.Platforms = &platform.Platforms{
|
||||
Only: &[]platform.Platform{platform.PlatformLinux},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
130
installer/pacman_installer.go
Normal file
130
installer/pacman_installer.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package installer
|
||||
|
||||
import (
|
||||
"github.com/chenasraf/sofmani/appconfig"
|
||||
)
|
||||
|
||||
// PacmanInstaller is an installer for pacman and yay packages.
|
||||
type PacmanInstaller struct {
|
||||
InstallerBase
|
||||
// Config is the application configuration.
|
||||
Config *appconfig.AppConfig
|
||||
// Info is the installer data.
|
||||
Info *appconfig.InstallerData
|
||||
// PackageManager is the package manager to use (pacman or yay).
|
||||
PackageManager PacmanPackageManager
|
||||
}
|
||||
|
||||
// PacmanOpts represents options for the PacmanInstaller.
|
||||
type PacmanOpts struct {
|
||||
// Needed skips reinstalling up-to-date packages (--needed flag).
|
||||
Needed *bool
|
||||
}
|
||||
|
||||
// PacmanPackageManager represents an Arch Linux package manager type.
|
||||
type PacmanPackageManager string
|
||||
|
||||
// Constants for supported Arch Linux package managers.
|
||||
const (
|
||||
PackageManagerPacman PacmanPackageManager = "pacman" // PackageManagerPacman represents the pacman package manager.
|
||||
PackageManagerYay PacmanPackageManager = "yay" // PackageManagerYay represents the yay AUR helper.
|
||||
)
|
||||
|
||||
// Validate validates the installer configuration.
|
||||
func (i *PacmanInstaller) Validate() []ValidationError {
|
||||
errors := i.BaseValidate()
|
||||
return errors
|
||||
}
|
||||
|
||||
// Install implements IInstaller.
|
||||
func (i *PacmanInstaller) Install() error {
|
||||
name := *i.Info.Name
|
||||
args := []string{"-S", "--noconfirm"}
|
||||
if i.GetOpts().Needed != nil && *i.GetOpts().Needed {
|
||||
args = append(args, "--needed")
|
||||
}
|
||||
args = append(args, name)
|
||||
return i.RunCmdPassThrough(string(i.PackageManager), args...)
|
||||
}
|
||||
|
||||
// Update implements IInstaller.
|
||||
func (i *PacmanInstaller) Update() error {
|
||||
name := *i.Info.Name
|
||||
args := []string{"-S", "--noconfirm"}
|
||||
if i.GetOpts().Needed != nil && *i.GetOpts().Needed {
|
||||
args = append(args, "--needed")
|
||||
}
|
||||
args = append(args, name)
|
||||
return i.RunCmdPassThrough(string(i.PackageManager), args...)
|
||||
}
|
||||
|
||||
// CheckNeedsUpdate implements IInstaller.
|
||||
func (i *PacmanInstaller) CheckNeedsUpdate() (bool, error) {
|
||||
if i.HasCustomUpdateCheck() {
|
||||
return i.RunCustomUpdateCheck()
|
||||
}
|
||||
// -Qu lists packages that have updates available
|
||||
// If the package has an update, it will be in the output
|
||||
output, err := i.RunCmdGetOutput(string(i.PackageManager), "-Qu", *i.Info.Name)
|
||||
if err != nil {
|
||||
// No output or error means no updates needed
|
||||
return false, nil
|
||||
}
|
||||
// If we got output, there are updates available
|
||||
return len(output) > 0, nil
|
||||
}
|
||||
|
||||
// CheckIsInstalled implements IInstaller.
|
||||
func (i *PacmanInstaller) CheckIsInstalled() (bool, error) {
|
||||
if i.HasCustomInstallCheck() {
|
||||
return i.RunCustomInstallCheck()
|
||||
}
|
||||
// Use pacman -Q to check if package is installed (works for all packages including fonts/libraries)
|
||||
return i.RunCmdGetSuccess(string(i.PackageManager), "-Q", *i.Info.Name)
|
||||
}
|
||||
|
||||
// GetData implements IInstaller.
|
||||
func (i *PacmanInstaller) GetData() *appconfig.InstallerData {
|
||||
return i.Info
|
||||
}
|
||||
|
||||
// GetOpts returns the parsed options for the PacmanInstaller.
|
||||
func (i *PacmanInstaller) GetOpts() *PacmanOpts {
|
||||
opts := &PacmanOpts{}
|
||||
info := i.Info
|
||||
if info.Opts != nil {
|
||||
if needed, ok := (*info.Opts)["needed"].(bool); ok {
|
||||
opts.Needed = &needed
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// GetBinName returns the binary name for the installer.
|
||||
// It uses the BinName from the installer data if provided, otherwise it uses the installer name.
|
||||
func (i *PacmanInstaller) GetBinName() string {
|
||||
info := i.GetData()
|
||||
if info.BinName != nil && len(*info.BinName) > 0 {
|
||||
return *info.BinName
|
||||
}
|
||||
return *info.Name
|
||||
}
|
||||
|
||||
// NewPacmanInstaller creates a new PacmanInstaller.
|
||||
func NewPacmanInstaller(cfg *appconfig.AppConfig, installer *appconfig.InstallerData) *PacmanInstaller {
|
||||
var packageManager PacmanPackageManager
|
||||
switch installer.Type {
|
||||
case appconfig.InstallerTypePacman:
|
||||
packageManager = PackageManagerPacman
|
||||
case appconfig.InstallerTypeYay:
|
||||
packageManager = PackageManagerYay
|
||||
}
|
||||
i := &PacmanInstaller{
|
||||
InstallerBase: InstallerBase{Data: installer},
|
||||
Config: cfg,
|
||||
Info: installer,
|
||||
PackageManager: packageManager,
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
124
installer/pacman_installer_test.go
Normal file
124
installer/pacman_installer_test.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package installer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/chenasraf/sofmani/appconfig"
|
||||
"github.com/chenasraf/sofmani/logger"
|
||||
)
|
||||
|
||||
func newTestPacmanInstaller(data *appconfig.InstallerData) *PacmanInstaller {
|
||||
return &PacmanInstaller{
|
||||
InstallerBase: InstallerBase{
|
||||
Data: data,
|
||||
},
|
||||
Config: nil,
|
||||
PackageManager: PackageManagerPacman,
|
||||
Info: data,
|
||||
}
|
||||
}
|
||||
|
||||
func newTestYayInstaller(data *appconfig.InstallerData) *PacmanInstaller {
|
||||
return &PacmanInstaller{
|
||||
InstallerBase: InstallerBase{
|
||||
Data: data,
|
||||
},
|
||||
Config: nil,
|
||||
PackageManager: PackageManagerYay,
|
||||
Info: data,
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacmanValidation(t *testing.T) {
|
||||
logger.InitLogger(false)
|
||||
|
||||
// Valid pacman installer
|
||||
validPacmanData := &appconfig.InstallerData{
|
||||
Name: strPtr("vim"),
|
||||
Type: appconfig.InstallerTypePacman,
|
||||
}
|
||||
assertNoValidationErrors(t, newTestPacmanInstaller(validPacmanData).Validate())
|
||||
|
||||
// Valid yay installer
|
||||
validYayData := &appconfig.InstallerData{
|
||||
Name: strPtr("visual-studio-code-bin"),
|
||||
Type: appconfig.InstallerTypeYay,
|
||||
}
|
||||
assertNoValidationErrors(t, newTestYayInstaller(validYayData).Validate())
|
||||
|
||||
// Invalid: nil name
|
||||
nilNameData := &appconfig.InstallerData{
|
||||
Name: nil,
|
||||
Type: appconfig.InstallerTypePacman,
|
||||
}
|
||||
assertValidationError(t, newTestPacmanInstaller(nilNameData).Validate(), "name")
|
||||
}
|
||||
|
||||
func TestPacmanGetBinName(t *testing.T) {
|
||||
logger.InitLogger(false)
|
||||
|
||||
// Test default bin name (uses package name)
|
||||
defaultBinData := &appconfig.InstallerData{
|
||||
Name: strPtr("neovim"),
|
||||
Type: appconfig.InstallerTypePacman,
|
||||
}
|
||||
installer := newTestPacmanInstaller(defaultBinData)
|
||||
if installer.GetBinName() != "neovim" {
|
||||
t.Errorf("expected bin name 'neovim', got '%s'", installer.GetBinName())
|
||||
}
|
||||
|
||||
// Test custom bin name
|
||||
customBinName := "nvim"
|
||||
customBinData := &appconfig.InstallerData{
|
||||
Name: strPtr("neovim"),
|
||||
Type: appconfig.InstallerTypePacman,
|
||||
BinName: &customBinName,
|
||||
}
|
||||
installerWithCustomBin := newTestPacmanInstaller(customBinData)
|
||||
if installerWithCustomBin.GetBinName() != "nvim" {
|
||||
t.Errorf("expected bin name 'nvim', got '%s'", installerWithCustomBin.GetBinName())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacmanGetOpts(t *testing.T) {
|
||||
logger.InitLogger(false)
|
||||
|
||||
// Test default opts (no options set)
|
||||
defaultData := &appconfig.InstallerData{
|
||||
Name: strPtr("vim"),
|
||||
Type: appconfig.InstallerTypePacman,
|
||||
}
|
||||
installer := newTestPacmanInstaller(defaultData)
|
||||
opts := installer.GetOpts()
|
||||
if opts.Needed != nil {
|
||||
t.Errorf("expected Needed to be nil, got %v", *opts.Needed)
|
||||
}
|
||||
|
||||
// Test with needed option set to true
|
||||
neededData := &appconfig.InstallerData{
|
||||
Name: strPtr("vim"),
|
||||
Type: appconfig.InstallerTypePacman,
|
||||
Opts: &map[string]any{
|
||||
"needed": true,
|
||||
},
|
||||
}
|
||||
installerWithNeeded := newTestPacmanInstaller(neededData)
|
||||
optsWithNeeded := installerWithNeeded.GetOpts()
|
||||
if optsWithNeeded.Needed == nil || !*optsWithNeeded.Needed {
|
||||
t.Errorf("expected Needed to be true")
|
||||
}
|
||||
|
||||
// Test with needed option set to false
|
||||
notNeededData := &appconfig.InstallerData{
|
||||
Name: strPtr("vim"),
|
||||
Type: appconfig.InstallerTypePacman,
|
||||
Opts: &map[string]any{
|
||||
"needed": false,
|
||||
},
|
||||
}
|
||||
installerNotNeeded := newTestPacmanInstaller(notNeededData)
|
||||
optsNotNeeded := installerNotNeeded.GetOpts()
|
||||
if optsNotNeeded.Needed == nil || *optsNotNeeded.Needed {
|
||||
t.Errorf("expected Needed to be false")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user