feat: add install_flags and update_flags to various installers

This commit is contained in:
2025-12-03 21:38:56 +02:00
parent 7de60d48fd
commit 0401be046f
13 changed files with 769 additions and 18 deletions

View File

@@ -1,6 +1,8 @@
package installer
import (
"strings"
"github.com/chenasraf/sofmani/appconfig"
"github.com/chenasraf/sofmani/utils"
)
@@ -18,7 +20,12 @@ type AptInstaller struct {
// AptOpts represents options for the AptInstaller.
type AptOpts struct {
//
// Flags is a string of additional flags to pass to the apt/apk command.
Flags *string
// InstallFlags is a string of additional flags to pass only during install.
InstallFlags *string
// UpdateFlags is a string of additional flags to pass only during update.
UpdateFlags *string
}
// AptPackageManager represents a package manager type.
@@ -39,6 +46,7 @@ func (i *AptInstaller) Validate() []ValidationError {
// Install implements IInstaller.
func (i *AptInstaller) Install() error {
name := *i.Info.Name
opts := i.GetOpts()
err := i.RunCmdPassThrough(string(i.PackageManager), "update")
if err != nil {
return err
@@ -47,7 +55,17 @@ func (i *AptInstaller) Install() error {
if i.PackageManager == PackageManagerApk {
install = "add"
}
return i.RunCmdPassThrough(string(i.PackageManager), install, i.getConfirmArg(), name)
args := []string{install}
if confirm := i.getConfirmArg(); confirm != "" {
args = append(args, confirm)
}
if opts.InstallFlags != nil {
args = append(args, strings.Fields(*opts.InstallFlags)...)
} else if opts.Flags != nil {
args = append(args, strings.Fields(*opts.Flags)...)
}
args = append(args, name)
return i.RunCmdPassThrough(string(i.PackageManager), args...)
}
// getConfirmArg returns the appropriate confirmation argument for the package manager.
@@ -62,7 +80,18 @@ func (i *AptInstaller) getConfirmArg() string {
// Update implements IInstaller.
func (i *AptInstaller) Update() error {
return i.RunCmdPassThrough(string(i.PackageManager), "upgrade", i.getConfirmArg(), *i.Info.Name)
opts := i.GetOpts()
args := []string{"upgrade"}
if confirm := i.getConfirmArg(); confirm != "" {
args = append(args, confirm)
}
if opts.UpdateFlags != nil {
args = append(args, strings.Fields(*opts.UpdateFlags)...)
} else if opts.Flags != nil {
args = append(args, strings.Fields(*opts.Flags)...)
}
args = append(args, *i.Info.Name)
return i.RunCmdPassThrough(string(i.PackageManager), args...)
}
// CheckNeedsUpdate implements IInstaller.
@@ -96,7 +125,20 @@ func (i *AptInstaller) GetData() *appconfig.InstallerData {
// GetOpts returns the parsed options for the AptInstaller.
func (i *AptInstaller) GetOpts() *AptOpts {
return &AptOpts{}
opts := &AptOpts{}
info := i.Info
if info.Opts != nil {
if flags, ok := (*info.Opts)["flags"].(string); ok {
opts.Flags = &flags
}
if installFlags, ok := (*info.Opts)["install_flags"].(string); ok {
opts.InstallFlags = &installFlags
}
if updateFlags, ok := (*info.Opts)["update_flags"].(string); ok {
opts.UpdateFlags = &updateFlags
}
}
return opts
}
// GetBinName returns the binary name for the installer.

View File

@@ -27,3 +27,88 @@ func TestAptValidation(t *testing.T) {
)
assertNoValidationErrors(t, aptInstaller.Validate())
}
func TestAptGetOpts(t *testing.T) {
logger.InitLogger(false)
// Test default opts (no options set)
defaultData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeApt,
}
installer := newAptInstaller(defaultData)
opts := installer.GetOpts()
if opts.Flags != nil {
t.Errorf("expected Flags to be nil")
}
if opts.InstallFlags != nil {
t.Errorf("expected InstallFlags to be nil")
}
if opts.UpdateFlags != nil {
t.Errorf("expected UpdateFlags to be nil")
}
// Test with flags option
flagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeApt,
Opts: &map[string]any{
"flags": "-y --no-install-recommends",
},
}
installerWithFlags := newAptInstaller(flagsData)
optsWithFlags := installerWithFlags.GetOpts()
if optsWithFlags.Flags == nil || *optsWithFlags.Flags != "-y --no-install-recommends" {
t.Errorf("expected Flags to be '-y --no-install-recommends'")
}
// Test with install_flags option
installFlagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeApt,
Opts: &map[string]any{
"install_flags": "--no-install-recommends",
},
}
installerWithInstallFlags := newAptInstaller(installFlagsData)
optsWithInstallFlags := installerWithInstallFlags.GetOpts()
if optsWithInstallFlags.InstallFlags == nil || *optsWithInstallFlags.InstallFlags != "--no-install-recommends" {
t.Errorf("expected InstallFlags to be '--no-install-recommends'")
}
// Test with update_flags option
updateFlagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeApt,
Opts: &map[string]any{
"update_flags": "--only-upgrade",
},
}
installerWithUpdateFlags := newAptInstaller(updateFlagsData)
optsWithUpdateFlags := installerWithUpdateFlags.GetOpts()
if optsWithUpdateFlags.UpdateFlags == nil || *optsWithUpdateFlags.UpdateFlags != "--only-upgrade" {
t.Errorf("expected UpdateFlags to be '--only-upgrade'")
}
// Test with all flags options combined
allFlagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeApt,
Opts: &map[string]any{
"flags": "--common",
"install_flags": "--install-specific",
"update_flags": "--update-specific",
},
}
installerWithAllFlags := newAptInstaller(allFlagsData)
optsWithAllFlags := installerWithAllFlags.GetOpts()
if optsWithAllFlags.Flags == nil || *optsWithAllFlags.Flags != "--common" {
t.Errorf("expected Flags to be '--common'")
}
if optsWithAllFlags.InstallFlags == nil || *optsWithAllFlags.InstallFlags != "--install-specific" {
t.Errorf("expected InstallFlags to be '--install-specific'")
}
if optsWithAllFlags.UpdateFlags == nil || *optsWithAllFlags.UpdateFlags != "--update-specific" {
t.Errorf("expected UpdateFlags to be '--update-specific'")
}
}

View File

@@ -30,6 +30,12 @@ type BrewOpts struct {
Tap *string
// Cask installs the formula as a cask instead of a regular package.
Cask *bool
// Flags is a string of additional flags to pass to the brew command.
Flags *string
// InstallFlags is a string of additional flags to pass only during install.
InstallFlags *string
// UpdateFlags is a string of additional flags to pass only during update.
UpdateFlags *string
}
// Validate validates the installer configuration.
@@ -48,20 +54,32 @@ func (i *BrewInstaller) Validate() []ValidationError {
// Install implements IInstaller.
func (i *BrewInstaller) Install() error {
name := i.GetFullName()
opts := i.GetOpts()
cmd := "brew install"
if i.IsCask() {
cmd += " --cask"
}
if opts.InstallFlags != nil {
cmd += " " + *opts.InstallFlags
} else if opts.Flags != nil {
cmd += " " + *opts.Flags
}
return i.RunCmdAsFile(fmt.Sprintf("%s %s", cmd, name))
}
// Update implements IInstaller.
func (i *BrewInstaller) Update() error {
name := i.GetFullName()
opts := i.GetOpts()
cmd := "brew upgrade"
if i.IsCask() {
cmd += " --cask"
}
if opts.UpdateFlags != nil {
cmd += " " + *opts.UpdateFlags
} else if opts.Flags != nil {
cmd += " " + *opts.Flags
}
return i.RunCmdAsFile(fmt.Sprintf("%s %s", cmd, name))
}
@@ -189,6 +207,15 @@ func (i *BrewInstaller) GetOpts() *BrewOpts {
if caskVal, ok := (*info.Opts)["cask"].(bool); ok {
opts.Cask = &caskVal
}
if flags, ok := (*info.Opts)["flags"].(string); ok {
opts.Flags = &flags
}
if installFlags, ok := (*info.Opts)["install_flags"].(string); ok {
opts.InstallFlags = &installFlags
}
if updateFlags, ok := (*info.Opts)["update_flags"].(string); ok {
opts.UpdateFlags = &updateFlags
}
}
return opts
}

View File

@@ -79,6 +79,105 @@ func simulateBrewCheck(input string, exitCode int) (logs string, updateNeeded bo
return logBuf.String(), needsUpdate, nil
}
func TestBrewGetOpts(t *testing.T) {
logger.InitLogger(false)
// Test default opts (no options set)
defaultData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
}
installer := newTestBrewInstaller(defaultData)
opts := installer.GetOpts()
if opts.Tap != nil {
t.Errorf("expected Tap to be nil")
}
if opts.Cask != nil {
t.Errorf("expected Cask to be nil")
}
if opts.Flags != nil {
t.Errorf("expected Flags to be nil")
}
if opts.InstallFlags != nil {
t.Errorf("expected InstallFlags to be nil")
}
if opts.UpdateFlags != nil {
t.Errorf("expected UpdateFlags to be nil")
}
// Test with flags option
flagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
Opts: &map[string]any{
"flags": "--verbose --debug",
},
}
installerWithFlags := newTestBrewInstaller(flagsData)
optsWithFlags := installerWithFlags.GetOpts()
if optsWithFlags.Flags == nil || *optsWithFlags.Flags != "--verbose --debug" {
t.Errorf("expected Flags to be '--verbose --debug'")
}
// Test with install_flags option
installFlagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
Opts: &map[string]any{
"install_flags": "--force",
},
}
installerWithInstallFlags := newTestBrewInstaller(installFlagsData)
optsWithInstallFlags := installerWithInstallFlags.GetOpts()
if optsWithInstallFlags.InstallFlags == nil || *optsWithInstallFlags.InstallFlags != "--force" {
t.Errorf("expected InstallFlags to be '--force'")
}
// Test with update_flags option
updateFlagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
Opts: &map[string]any{
"update_flags": "--dry-run",
},
}
installerWithUpdateFlags := newTestBrewInstaller(updateFlagsData)
optsWithUpdateFlags := installerWithUpdateFlags.GetOpts()
if optsWithUpdateFlags.UpdateFlags == nil || *optsWithUpdateFlags.UpdateFlags != "--dry-run" {
t.Errorf("expected UpdateFlags to be '--dry-run'")
}
// Test with all flags options combined
allFlagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
Opts: &map[string]any{
"tap": "homebrew/core",
"cask": true,
"flags": "--common",
"install_flags": "--install-specific",
"update_flags": "--update-specific",
},
}
installerWithAllFlags := newTestBrewInstaller(allFlagsData)
optsWithAllFlags := installerWithAllFlags.GetOpts()
if optsWithAllFlags.Tap == nil || *optsWithAllFlags.Tap != "homebrew/core" {
t.Errorf("expected Tap to be 'homebrew/core'")
}
if optsWithAllFlags.Cask == nil || !*optsWithAllFlags.Cask {
t.Errorf("expected Cask to be true")
}
if optsWithAllFlags.Flags == nil || *optsWithAllFlags.Flags != "--common" {
t.Errorf("expected Flags to be '--common'")
}
if optsWithAllFlags.InstallFlags == nil || *optsWithAllFlags.InstallFlags != "--install-specific" {
t.Errorf("expected InstallFlags to be '--install-specific'")
}
if optsWithAllFlags.UpdateFlags == nil || *optsWithAllFlags.UpdateFlags != "--update-specific" {
t.Errorf("expected UpdateFlags to be '--update-specific'")
}
}
func TestBrewNeedsUpdateWithExitCode(t *testing.T) {
tests := []struct {
name string

View File

@@ -25,6 +25,12 @@ type GitOpts struct {
Destination *string
// Ref is the Git reference (branch, tag, or commit) to checkout.
Ref *string
// Flags is a string of additional flags to pass to git commands.
Flags *string
// InstallFlags is a string of additional flags to pass only to git clone.
InstallFlags *string
// UpdateFlags is a string of additional flags to pass only to git pull.
UpdateFlags *string
}
// Validate validates the installer configuration.
@@ -43,20 +49,34 @@ func (i *GitInstaller) Validate() []ValidationError {
// Install implements IInstaller.
func (i *GitInstaller) Install() error {
args := []string{"clone", i.GetRepositoryUrl(), i.GetInstallDir()}
opts := i.GetOpts()
args := []string{"clone"}
if opts.InstallFlags != nil {
args = append(args, strings.Fields(*opts.InstallFlags)...)
} else if opts.Flags != nil {
args = append(args, strings.Fields(*opts.Flags)...)
}
args = append(args, i.GetRepositoryUrl(), i.GetInstallDir())
err := i.RunCmdPassThrough("git", args...)
if err != nil {
return err
}
if i.GetOpts().Ref != nil {
return i.RunCmdPassThrough("git", "-C", i.GetInstallDir(), "checkout", *i.GetOpts().Ref)
if opts.Ref != nil {
return i.RunCmdPassThrough("git", "-C", i.GetInstallDir(), "checkout", *opts.Ref)
}
return nil
}
// Update implements IInstaller.
func (i *GitInstaller) Update() error {
return i.RunCmdPassThrough("git", "-C", i.GetInstallDir(), "pull")
opts := i.GetOpts()
args := []string{"-C", i.GetInstallDir(), "pull"}
if opts.UpdateFlags != nil {
args = append(args, strings.Fields(*opts.UpdateFlags)...)
} else if opts.Flags != nil {
args = append(args, strings.Fields(*opts.Flags)...)
}
return i.RunCmdPassThrough("git", args...)
}
// CheckNeedsUpdate implements IInstaller.
@@ -103,6 +123,15 @@ func (i *GitInstaller) GetOpts() *GitOpts {
if ref, ok := (*info.Opts)["ref"].(string); ok {
opts.Ref = &ref
}
if flags, ok := (*info.Opts)["flags"].(string); ok {
opts.Flags = &flags
}
if installFlags, ok := (*info.Opts)["install_flags"].(string); ok {
opts.InstallFlags = &installFlags
}
if updateFlags, ok := (*info.Opts)["update_flags"].(string); ok {
opts.UpdateFlags = &updateFlags
}
}
return opts
}

View File

@@ -50,3 +50,108 @@ func TestGitValidation(t *testing.T) {
}
assertValidationError(t, newTestGitInstaller(missingDestData).Validate(), "destination")
}
func TestGitGetOpts(t *testing.T) {
logger.InitLogger(false)
// Test default opts (only destination set)
defaultData := &appconfig.InstallerData{
Name: strPtr("owner/repo"),
Type: appconfig.InstallerTypeGit,
Opts: &map[string]any{
"destination": "/some/path",
},
}
installer := newTestGitInstaller(defaultData)
opts := installer.GetOpts()
if opts.Destination == nil || *opts.Destination != "/some/path" {
t.Errorf("expected Destination to be '/some/path'")
}
if opts.Ref != nil {
t.Errorf("expected Ref to be nil")
}
if opts.Flags != nil {
t.Errorf("expected Flags to be nil")
}
if opts.InstallFlags != nil {
t.Errorf("expected InstallFlags to be nil")
}
if opts.UpdateFlags != nil {
t.Errorf("expected UpdateFlags to be nil")
}
// Test with flags option
flagsData := &appconfig.InstallerData{
Name: strPtr("owner/repo"),
Type: appconfig.InstallerTypeGit,
Opts: &map[string]any{
"destination": "/some/path",
"flags": "--depth 1",
},
}
installerWithFlags := newTestGitInstaller(flagsData)
optsWithFlags := installerWithFlags.GetOpts()
if optsWithFlags.Flags == nil || *optsWithFlags.Flags != "--depth 1" {
t.Errorf("expected Flags to be '--depth 1'")
}
// Test with install_flags option (for git clone)
installFlagsData := &appconfig.InstallerData{
Name: strPtr("owner/repo"),
Type: appconfig.InstallerTypeGit,
Opts: &map[string]any{
"destination": "/some/path",
"install_flags": "--depth 1 --single-branch",
},
}
installerWithInstallFlags := newTestGitInstaller(installFlagsData)
optsWithInstallFlags := installerWithInstallFlags.GetOpts()
if optsWithInstallFlags.InstallFlags == nil || *optsWithInstallFlags.InstallFlags != "--depth 1 --single-branch" {
t.Errorf("expected InstallFlags to be '--depth 1 --single-branch'")
}
// Test with update_flags option (for git pull)
updateFlagsData := &appconfig.InstallerData{
Name: strPtr("owner/repo"),
Type: appconfig.InstallerTypeGit,
Opts: &map[string]any{
"destination": "/some/path",
"update_flags": "--rebase",
},
}
installerWithUpdateFlags := newTestGitInstaller(updateFlagsData)
optsWithUpdateFlags := installerWithUpdateFlags.GetOpts()
if optsWithUpdateFlags.UpdateFlags == nil || *optsWithUpdateFlags.UpdateFlags != "--rebase" {
t.Errorf("expected UpdateFlags to be '--rebase'")
}
// Test with all options combined
allOptsData := &appconfig.InstallerData{
Name: strPtr("owner/repo"),
Type: appconfig.InstallerTypeGit,
Opts: &map[string]any{
"destination": "/some/path",
"ref": "develop",
"flags": "--common",
"install_flags": "--depth 1",
"update_flags": "--rebase",
},
}
installerWithAllOpts := newTestGitInstaller(allOptsData)
optsWithAllOpts := installerWithAllOpts.GetOpts()
if optsWithAllOpts.Destination == nil || *optsWithAllOpts.Destination != "/some/path" {
t.Errorf("expected Destination to be '/some/path'")
}
if optsWithAllOpts.Ref == nil || *optsWithAllOpts.Ref != "develop" {
t.Errorf("expected Ref to be 'develop'")
}
if optsWithAllOpts.Flags == nil || *optsWithAllOpts.Flags != "--common" {
t.Errorf("expected Flags to be '--common'")
}
if optsWithAllOpts.InstallFlags == nil || *optsWithAllOpts.InstallFlags != "--depth 1" {
t.Errorf("expected InstallFlags to be '--depth 1'")
}
if optsWithAllOpts.UpdateFlags == nil || *optsWithAllOpts.UpdateFlags != "--rebase" {
t.Errorf("expected UpdateFlags to be '--rebase'")
}
}

View File

@@ -1,6 +1,8 @@
package installer
import (
"strings"
"github.com/chenasraf/sofmani/appconfig"
"github.com/chenasraf/sofmani/utils"
)
@@ -18,7 +20,12 @@ type NpmInstaller struct {
// NpmOpts represents options for the NpmInstaller.
type NpmOpts struct {
//
// Flags is a string of additional flags to pass to the npm/pnpm/yarn command.
Flags *string
// InstallFlags is a string of additional flags to pass only during install.
InstallFlags *string
// UpdateFlags is a string of additional flags to pass only during update.
UpdateFlags *string
}
// NpmPackageManager represents a Node.js package manager type.
@@ -40,12 +47,28 @@ func (i *NpmInstaller) Validate() []ValidationError {
// Install implements IInstaller.
func (i *NpmInstaller) Install() error {
return i.RunCmdPassThrough(string(i.PackageManager), "install", "--global", *i.Info.Name)
opts := i.GetOpts()
args := []string{"install", "--global"}
if opts.InstallFlags != nil {
args = append(args, strings.Fields(*opts.InstallFlags)...)
} else if opts.Flags != nil {
args = append(args, strings.Fields(*opts.Flags)...)
}
args = append(args, *i.Info.Name)
return i.RunCmdPassThrough(string(i.PackageManager), args...)
}
// Update implements IInstaller.
func (i *NpmInstaller) Update() error {
return i.RunCmdPassThrough(string(i.PackageManager), "install", "--global", *i.Info.Name+"@latest")
opts := i.GetOpts()
args := []string{"install", "--global"}
if opts.UpdateFlags != nil {
args = append(args, strings.Fields(*opts.UpdateFlags)...)
} else if opts.Flags != nil {
args = append(args, strings.Fields(*opts.Flags)...)
}
args = append(args, *i.Info.Name+"@latest")
return i.RunCmdPassThrough(string(i.PackageManager), args...)
}
// CheckNeedsUpdate implements IInstaller.
@@ -76,7 +99,18 @@ func (i *NpmInstaller) GetData() *appconfig.InstallerData {
// GetOpts returns the parsed options for the NpmInstaller.
func (i *NpmInstaller) GetOpts() *NpmOpts {
opts := &NpmOpts{}
// info := i.Info
info := i.Info
if info.Opts != nil {
if flags, ok := (*info.Opts)["flags"].(string); ok {
opts.Flags = &flags
}
if installFlags, ok := (*info.Opts)["install_flags"].(string); ok {
opts.InstallFlags = &installFlags
}
if updateFlags, ok := (*info.Opts)["update_flags"].(string); ok {
opts.UpdateFlags = &updateFlags
}
}
return opts
}

View File

@@ -35,3 +35,88 @@ func TestNpmValidation(t *testing.T) {
}
assertValidationError(t, newTestNpmInstaller(nilNameData).Validate(), "name")
}
func TestNpmGetOpts(t *testing.T) {
logger.InitLogger(false)
// Test default opts (no options set)
defaultData := &appconfig.InstallerData{
Name: strPtr("prettier"),
Type: appconfig.InstallerTypeNpm,
}
installer := newTestNpmInstaller(defaultData)
opts := installer.GetOpts()
if opts.Flags != nil {
t.Errorf("expected Flags to be nil")
}
if opts.InstallFlags != nil {
t.Errorf("expected InstallFlags to be nil")
}
if opts.UpdateFlags != nil {
t.Errorf("expected UpdateFlags to be nil")
}
// Test with flags option
flagsData := &appconfig.InstallerData{
Name: strPtr("prettier"),
Type: appconfig.InstallerTypeNpm,
Opts: &map[string]any{
"flags": "--legacy-peer-deps",
},
}
installerWithFlags := newTestNpmInstaller(flagsData)
optsWithFlags := installerWithFlags.GetOpts()
if optsWithFlags.Flags == nil || *optsWithFlags.Flags != "--legacy-peer-deps" {
t.Errorf("expected Flags to be '--legacy-peer-deps'")
}
// Test with install_flags option
installFlagsData := &appconfig.InstallerData{
Name: strPtr("prettier"),
Type: appconfig.InstallerTypeNpm,
Opts: &map[string]any{
"install_flags": "--save-exact",
},
}
installerWithInstallFlags := newTestNpmInstaller(installFlagsData)
optsWithInstallFlags := installerWithInstallFlags.GetOpts()
if optsWithInstallFlags.InstallFlags == nil || *optsWithInstallFlags.InstallFlags != "--save-exact" {
t.Errorf("expected InstallFlags to be '--save-exact'")
}
// Test with update_flags option
updateFlagsData := &appconfig.InstallerData{
Name: strPtr("prettier"),
Type: appconfig.InstallerTypeNpm,
Opts: &map[string]any{
"update_flags": "--force",
},
}
installerWithUpdateFlags := newTestNpmInstaller(updateFlagsData)
optsWithUpdateFlags := installerWithUpdateFlags.GetOpts()
if optsWithUpdateFlags.UpdateFlags == nil || *optsWithUpdateFlags.UpdateFlags != "--force" {
t.Errorf("expected UpdateFlags to be '--force'")
}
// Test with all flags options combined
allFlagsData := &appconfig.InstallerData{
Name: strPtr("prettier"),
Type: appconfig.InstallerTypeNpm,
Opts: &map[string]any{
"flags": "--common",
"install_flags": "--install-specific",
"update_flags": "--update-specific",
},
}
installerWithAllFlags := newTestNpmInstaller(allFlagsData)
optsWithAllFlags := installerWithAllFlags.GetOpts()
if optsWithAllFlags.Flags == nil || *optsWithAllFlags.Flags != "--common" {
t.Errorf("expected Flags to be '--common'")
}
if optsWithAllFlags.InstallFlags == nil || *optsWithAllFlags.InstallFlags != "--install-specific" {
t.Errorf("expected InstallFlags to be '--install-specific'")
}
if optsWithAllFlags.UpdateFlags == nil || *optsWithAllFlags.UpdateFlags != "--update-specific" {
t.Errorf("expected UpdateFlags to be '--update-specific'")
}
}

View File

@@ -1,6 +1,8 @@
package installer
import (
"strings"
"github.com/chenasraf/sofmani/appconfig"
)
@@ -19,6 +21,12 @@ type PacmanInstaller struct {
type PacmanOpts struct {
// Needed skips reinstalling up-to-date packages (--needed flag).
Needed *bool
// Flags is a string of additional flags to pass to the pacman/yay command.
Flags *string
// InstallFlags is a string of additional flags to pass only during install.
InstallFlags *string
// UpdateFlags is a string of additional flags to pass only during update.
UpdateFlags *string
}
// PacmanPackageManager represents an Arch Linux package manager type.
@@ -39,10 +47,16 @@ func (i *PacmanInstaller) Validate() []ValidationError {
// Install implements IInstaller.
func (i *PacmanInstaller) Install() error {
name := *i.Info.Name
opts := i.GetOpts()
args := []string{"-S", "--noconfirm"}
if i.GetOpts().Needed != nil && *i.GetOpts().Needed {
if opts.Needed != nil && *opts.Needed {
args = append(args, "--needed")
}
if opts.InstallFlags != nil {
args = append(args, strings.Fields(*opts.InstallFlags)...)
} else if opts.Flags != nil {
args = append(args, strings.Fields(*opts.Flags)...)
}
args = append(args, name)
return i.RunCmdPassThrough(string(i.PackageManager), args...)
}
@@ -50,10 +64,16 @@ func (i *PacmanInstaller) Install() error {
// Update implements IInstaller.
func (i *PacmanInstaller) Update() error {
name := *i.Info.Name
opts := i.GetOpts()
args := []string{"-S", "--noconfirm"}
if i.GetOpts().Needed != nil && *i.GetOpts().Needed {
if opts.Needed != nil && *opts.Needed {
args = append(args, "--needed")
}
if opts.UpdateFlags != nil {
args = append(args, strings.Fields(*opts.UpdateFlags)...)
} else if opts.Flags != nil {
args = append(args, strings.Fields(*opts.Flags)...)
}
args = append(args, name)
return i.RunCmdPassThrough(string(i.PackageManager), args...)
}
@@ -96,6 +116,15 @@ func (i *PacmanInstaller) GetOpts() *PacmanOpts {
if needed, ok := (*info.Opts)["needed"].(bool); ok {
opts.Needed = &needed
}
if flags, ok := (*info.Opts)["flags"].(string); ok {
opts.Flags = &flags
}
if installFlags, ok := (*info.Opts)["install_flags"].(string); ok {
opts.InstallFlags = &installFlags
}
if updateFlags, ok := (*info.Opts)["update_flags"].(string); ok {
opts.UpdateFlags = &updateFlags
}
}
return opts
}

View File

@@ -93,6 +93,15 @@ func TestPacmanGetOpts(t *testing.T) {
if opts.Needed != nil {
t.Errorf("expected Needed to be nil, got %v", *opts.Needed)
}
if opts.Flags != nil {
t.Errorf("expected Flags to be nil")
}
if opts.InstallFlags != nil {
t.Errorf("expected InstallFlags to be nil")
}
if opts.UpdateFlags != nil {
t.Errorf("expected UpdateFlags to be nil")
}
// Test with needed option set to true
neededData := &appconfig.InstallerData{
@@ -121,4 +130,68 @@ func TestPacmanGetOpts(t *testing.T) {
if optsNotNeeded.Needed == nil || *optsNotNeeded.Needed {
t.Errorf("expected Needed to be false")
}
// Test with flags option
flagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypePacman,
Opts: &map[string]any{
"flags": "--asdeps --overwrite '*'",
},
}
installerWithFlags := newTestPacmanInstaller(flagsData)
optsWithFlags := installerWithFlags.GetOpts()
if optsWithFlags.Flags == nil || *optsWithFlags.Flags != "--asdeps --overwrite '*'" {
t.Errorf("expected Flags to be '--asdeps --overwrite '*''")
}
// Test with install_flags option
installFlagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypePacman,
Opts: &map[string]any{
"install_flags": "--asdeps",
},
}
installerWithInstallFlags := newTestPacmanInstaller(installFlagsData)
optsWithInstallFlags := installerWithInstallFlags.GetOpts()
if optsWithInstallFlags.InstallFlags == nil || *optsWithInstallFlags.InstallFlags != "--asdeps" {
t.Errorf("expected InstallFlags to be '--asdeps'")
}
// Test with update_flags option
updateFlagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypePacman,
Opts: &map[string]any{
"update_flags": "--ignore vim",
},
}
installerWithUpdateFlags := newTestPacmanInstaller(updateFlagsData)
optsWithUpdateFlags := installerWithUpdateFlags.GetOpts()
if optsWithUpdateFlags.UpdateFlags == nil || *optsWithUpdateFlags.UpdateFlags != "--ignore vim" {
t.Errorf("expected UpdateFlags to be '--ignore vim'")
}
// Test with all flags options combined
allFlagsData := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypePacman,
Opts: &map[string]any{
"flags": "--common",
"install_flags": "--install-specific",
"update_flags": "--update-specific",
},
}
installerWithAllFlags := newTestPacmanInstaller(allFlagsData)
optsWithAllFlags := installerWithAllFlags.GetOpts()
if optsWithAllFlags.Flags == nil || *optsWithAllFlags.Flags != "--common" {
t.Errorf("expected Flags to be '--common'")
}
if optsWithAllFlags.InstallFlags == nil || *optsWithAllFlags.InstallFlags != "--install-specific" {
t.Errorf("expected InstallFlags to be '--install-specific'")
}
if optsWithAllFlags.UpdateFlags == nil || *optsWithAllFlags.UpdateFlags != "--update-specific" {
t.Errorf("expected UpdateFlags to be '--update-specific'")
}
}

View File

@@ -1,6 +1,8 @@
package installer
import (
"strings"
"github.com/chenasraf/sofmani/appconfig"
"github.com/chenasraf/sofmani/utils"
)
@@ -16,7 +18,12 @@ type PipxInstaller struct {
// PipxOpts represents options for the PipxInstaller.
type PipxOpts struct {
//
// Flags is a string of additional flags to pass to the pipx command.
Flags *string
// InstallFlags is a string of additional flags to pass only during install.
InstallFlags *string
// UpdateFlags is a string of additional flags to pass only during update.
UpdateFlags *string
}
// Validate validates the installer configuration.
@@ -28,12 +35,28 @@ func (i *PipxInstaller) Validate() []ValidationError {
// Install implements IInstaller.
func (i *PipxInstaller) Install() error {
name := *i.Info.Name
return i.RunCmdPassThrough("pipx", "install", name)
opts := i.GetOpts()
args := []string{"install"}
if opts.InstallFlags != nil {
args = append(args, strings.Fields(*opts.InstallFlags)...)
} else if opts.Flags != nil {
args = append(args, strings.Fields(*opts.Flags)...)
}
args = append(args, name)
return i.RunCmdPassThrough("pipx", args...)
}
// Update implements IInstaller.
func (i *PipxInstaller) Update() error {
return i.RunCmdPassThrough("pipx", "upgrade", *i.Info.Name)
opts := i.GetOpts()
args := []string{"upgrade"}
if opts.UpdateFlags != nil {
args = append(args, strings.Fields(*opts.UpdateFlags)...)
} else if opts.Flags != nil {
args = append(args, strings.Fields(*opts.Flags)...)
}
args = append(args, *i.Info.Name)
return i.RunCmdPassThrough("pipx", args...)
}
// CheckNeedsUpdate implements IInstaller.
@@ -63,7 +86,20 @@ func (i *PipxInstaller) GetData() *appconfig.InstallerData {
// GetOpts returns the parsed options for the PipxInstaller.
func (i *PipxInstaller) GetOpts() *PipxOpts {
return &PipxOpts{}
opts := &PipxOpts{}
info := i.Info
if info.Opts != nil {
if flags, ok := (*info.Opts)["flags"].(string); ok {
opts.Flags = &flags
}
if installFlags, ok := (*info.Opts)["install_flags"].(string); ok {
opts.InstallFlags = &installFlags
}
if updateFlags, ok := (*info.Opts)["update_flags"].(string); ok {
opts.UpdateFlags = &updateFlags
}
}
return opts
}
// GetBinName returns the binary name for the installer.

View File

@@ -34,3 +34,88 @@ func TestPipxValidation(t *testing.T) {
}
assertValidationError(t, newTestPipxInstaller(nilNameData).Validate(), "name")
}
func TestPipxGetOpts(t *testing.T) {
logger.InitLogger(false)
// Test default opts (no options set)
defaultData := &appconfig.InstallerData{
Name: strPtr("black"),
Type: appconfig.InstallerTypePipx,
}
installer := newTestPipxInstaller(defaultData)
opts := installer.GetOpts()
if opts.Flags != nil {
t.Errorf("expected Flags to be nil")
}
if opts.InstallFlags != nil {
t.Errorf("expected InstallFlags to be nil")
}
if opts.UpdateFlags != nil {
t.Errorf("expected UpdateFlags to be nil")
}
// Test with flags option
flagsData := &appconfig.InstallerData{
Name: strPtr("black"),
Type: appconfig.InstallerTypePipx,
Opts: &map[string]any{
"flags": "--verbose",
},
}
installerWithFlags := newTestPipxInstaller(flagsData)
optsWithFlags := installerWithFlags.GetOpts()
if optsWithFlags.Flags == nil || *optsWithFlags.Flags != "--verbose" {
t.Errorf("expected Flags to be '--verbose'")
}
// Test with install_flags option
installFlagsData := &appconfig.InstallerData{
Name: strPtr("black"),
Type: appconfig.InstallerTypePipx,
Opts: &map[string]any{
"install_flags": "--python python3.11",
},
}
installerWithInstallFlags := newTestPipxInstaller(installFlagsData)
optsWithInstallFlags := installerWithInstallFlags.GetOpts()
if optsWithInstallFlags.InstallFlags == nil || *optsWithInstallFlags.InstallFlags != "--python python3.11" {
t.Errorf("expected InstallFlags to be '--python python3.11'")
}
// Test with update_flags option
updateFlagsData := &appconfig.InstallerData{
Name: strPtr("black"),
Type: appconfig.InstallerTypePipx,
Opts: &map[string]any{
"update_flags": "--force",
},
}
installerWithUpdateFlags := newTestPipxInstaller(updateFlagsData)
optsWithUpdateFlags := installerWithUpdateFlags.GetOpts()
if optsWithUpdateFlags.UpdateFlags == nil || *optsWithUpdateFlags.UpdateFlags != "--force" {
t.Errorf("expected UpdateFlags to be '--force'")
}
// Test with all flags options combined
allFlagsData := &appconfig.InstallerData{
Name: strPtr("black"),
Type: appconfig.InstallerTypePipx,
Opts: &map[string]any{
"flags": "--common",
"install_flags": "--install-specific",
"update_flags": "--update-specific",
},
}
installerWithAllFlags := newTestPipxInstaller(allFlagsData)
optsWithAllFlags := installerWithAllFlags.GetOpts()
if optsWithAllFlags.Flags == nil || *optsWithAllFlags.Flags != "--common" {
t.Errorf("expected Flags to be '--common'")
}
if optsWithAllFlags.InstallFlags == nil || *optsWithAllFlags.InstallFlags != "--install-specific" {
t.Errorf("expected InstallFlags to be '--install-specific'")
}
if optsWithAllFlags.UpdateFlags == nil || *optsWithAllFlags.UpdateFlags != "--update-specific" {
t.Errorf("expected UpdateFlags to be '--update-specific'")
}
}