Skip to content

Commit

Permalink
feat: support excluding packages from upgrade --all (#48)
Browse files Browse the repository at this point in the history
* feat: support excluding packages from upgrade --all

* use binary names instead of package names

* simplify stew config command

* cleanup

---------

Co-authored-by: Marwan Hawari <[email protected]>
  • Loading branch information
tim-coutinho and marwanhawari authored Jan 21, 2025
1 parent 6358677 commit 1be909e
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 24 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ stew config # Automatically updates the stew.config.json
```

# Configuration

`stew` can be configured with a `stew.config.json` file. The location of this file will also depend on your OS:
|Linux/macOS | Windows |
| ------------ | ---------- |
Expand All @@ -166,8 +165,9 @@ stew config # Automatically updates the stew.config.json
You can configure 2 aspects of `stew`:
1. The `stewPath`: this is where `stew` data is stored.
2. The `stewBinPath`: this is where `stew` installs binaries
3. `excludeFromUpgradeAll`: this is the list of binaries that you don't want to be upgraded during `stew upgrade --all`, perhaps because they have their own built in upgrade feature or because you want to pin a specific version.

The default locations for these are:
The default locations for the `stewPath` and `stewBinPath` are:
| | Linux/macOS | Windows |
| ------------ | ------------ | ---------- |
| `stewPath` | `$XDG_DATA_HOME/stew` or `~/.local/share/stew` | `~/AppData/Local/stew` |
Expand All @@ -176,7 +176,7 @@ The default locations for these are:
There are multiple ways to configure these:
* When you first run `stew`, it will look for a `stew.config.json` file. If it cannot find one, then you will be prompted to set the configuration values.
* After `stew` is installed, you can use the `stew config` command to set the configuration values.
* At any time, you can manually create or edit the `stew.config.json` file. It should have values for `stewPath` and `stewBinPath`.
* At any time, you can manually create or edit the `stew.config.json` file. It should have values for `stewPath`, `stewBinPath`, and `excludeFromUpgradeAll`.

Make sure that the installation path is in your `PATH` environment variable. Otherwise, you won't be able to use any of the binaries installed by `stew`.

Expand Down
7 changes: 5 additions & 2 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ func Config() {

config, err := stew.ReadStewConfigJSON(stewConfigFilePath)
stew.CatchAndExit(err)
newStewPath, newStewBinPath, err := stew.PromptConfig(config.StewPath, config.StewBinPath)
systemInfo := stew.NewSystemInfo(config)
installedPackages, err := stew.ReadStewLockFileContents(systemInfo.StewLockFilePath)
stew.CatchAndExit(err)
newStewPath, newStewBinPath, newExcludedFromUpgradeAll, err := stew.PromptConfig(config.StewPath, config.StewBinPath, installedPackages, config.ExcludedFromUpgradeAll)
stew.CatchAndExit(err)

newStewConfig := stew.StewConfig{StewPath: newStewPath, StewBinPath: newStewBinPath}
newStewConfig := stew.StewConfig{StewPath: newStewPath, StewBinPath: newStewBinPath, ExcludedFromUpgradeAll: newExcludedFromUpgradeAll}
err = stew.WriteStewConfigJSON(newStewConfig, stewConfigFilePath)
stew.CatchAndExit(err)

Expand Down
10 changes: 7 additions & 3 deletions cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
// Upgrade is executed when you run `stew upgrade`
func Upgrade(upgradeAllCliFlag bool, binaryName string) {

userOS, userArch, _, systemInfo, err := stew.Initialize()
userOS, userArch, stewConfig, systemInfo, err := stew.Initialize()
stew.CatchAndExit(err)

if upgradeAllCliFlag && binaryName != "" {
Expand All @@ -38,7 +38,7 @@ func Upgrade(upgradeAllCliFlag bool, binaryName string) {
}

if upgradeAllCliFlag {
upgradeAll(userOS, userArch, lockFile, systemInfo)
upgradeAll(userOS, userArch, lockFile, systemInfo, stewConfig)
} else {
err := upgradeOne(binaryName, userOS, userArch, lockFile, systemInfo)
stew.CatchAndExit(err)
Expand Down Expand Up @@ -128,8 +128,12 @@ func upgradeOne(binaryName, userOS, userArch string, lockFile stew.LockFile, sys
return nil
}

func upgradeAll(userOS, userArch string, lockFile stew.LockFile, systemInfo stew.SystemInfo) {
func upgradeAll(userOS, userArch string, lockFile stew.LockFile, systemInfo stew.SystemInfo, stewConfig stew.StewConfig) {
for _, pkg := range lockFile.Packages {
if _, packageIsExcluded := stew.Contains(stewConfig.ExcludedFromUpgradeAll, pkg.Binary); packageIsExcluded {
fmt.Printf("%v (Excluded)\n", constants.YellowColor(pkg.Binary))
continue
}
if err := upgradeOne(pkg.Binary, userOS, userArch, lockFile, systemInfo); err != nil {
fmt.Fprintln(os.Stderr, err)
continue
Expand Down
39 changes: 29 additions & 10 deletions lib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ func GetStewConfigFilePath(userOS string) (string, error) {

// StewConfig contains all the stew configuration data
type StewConfig struct {
StewPath string `json:"stewPath"`
StewBinPath string `json:"stewBinPath"`
StewPath string `json:"stewPath"`
StewBinPath string `json:"stewBinPath"`
ExcludedFromUpgradeAll []string `json:"excludedFromUpgradeAll"`
}

func ReadStewConfigJSON(stewConfigFilePath string) (StewConfig, error) {
Expand Down Expand Up @@ -134,6 +135,7 @@ func NewStewConfig(userOS string) (StewConfig, error) {
if err != nil {
return StewConfig{}, err
}
defaultExcludedFromUpgradeAll := []string{}

configExists, err := PathExists(stewConfigFilePath)
if err != nil {
Expand All @@ -152,13 +154,19 @@ func NewStewConfig(userOS string) (StewConfig, error) {
if stewConfig.StewBinPath == "" {
stewConfig.StewBinPath = defaultStewBinPath
}

if len(stewConfig.ExcludedFromUpgradeAll) == 0 {
stewConfig.ExcludedFromUpgradeAll = defaultExcludedFromUpgradeAll
}
} else {
selectedStewPath, selectedStewBinPath, err := PromptConfig(defaultStewPath, defaultStewBinPath)
defaultInstalledPackages := []PackageData{}
selectedStewPath, selectedStewBinPath, excludedFromUpgradeAll, err := PromptConfig(defaultStewPath, defaultStewBinPath, defaultInstalledPackages, defaultExcludedFromUpgradeAll)
if err != nil {
return StewConfig{}, err
}
stewConfig.StewPath = selectedStewPath
stewConfig.StewBinPath = selectedStewBinPath
stewConfig.ExcludedFromUpgradeAll = excludedFromUpgradeAll
fmt.Printf("📄 Updated %v\n", constants.GreenColor(stewConfigFilePath))
}

Expand Down Expand Up @@ -235,27 +243,38 @@ func Initialize() (string, string, StewConfig, SystemInfo, error) {
return userOS, userArch, stewConfig, systemInfo, nil
}

// PromptConfig launches an interactive UI for setting the stew config values. It returns the resolved stewPath and stewBinPath.
func PromptConfig(suggestedStewPath, suggestedStewBinPath string) (string, string, error) {
// PromptConfig launches an interactive UI for setting the stew config values. It returns the resolved stewPath, stewBinPath, and excludedFromUpgradeAll values.
func PromptConfig(suggestedStewPath string, suggestedStewBinPath string, installedPackages []PackageData, excludedPackages []string) (string, string, []string, error) {
inputStewPath, err := PromptInput("Set the stewPath. This will contain all stew data other than the binaries.", suggestedStewPath)
if err != nil {
return "", "", err
return "", "", []string{}, err
}
inputStewBinPath, err := PromptInput("Set the stewBinPath. This is where the binaries will be installed by stew.", suggestedStewBinPath)
if err != nil {
return "", "", err
return "", "", []string{}, err
}
excludedFromUpgradeAll := []string{}
if len(installedPackages) != 0 {
installedBinaryNames := []string{}
for _, pkg := range installedPackages {
installedBinaryNames = append(installedBinaryNames, pkg.Binary)
}
excludedFromUpgradeAll, err = PromptMultiSelect("Select any packages that you do not wish to be upgraded during stew upgrade --all.", installedBinaryNames, excludedPackages)
if err != nil {
return "", "", []string{}, err
}
}

fullStewPath, err := ResolvePath(inputStewPath)
if err != nil {
return "", "", err
return "", "", []string{}, err
}
fullStewBinPath, err := ResolvePath(inputStewBinPath)
if err != nil {
return "", "", err
return "", "", []string{}, err
}

return fullStewPath, fullStewBinPath, nil
return fullStewPath, fullStewBinPath, excludedFromUpgradeAll, nil
}

func ValidateStewBinPath(stewBinPath, pathVariable string) bool {
Expand Down
6 changes: 3 additions & 3 deletions lib/stewfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type PackageData struct {
BinaryHash string `json:"binaryHash"`
}

func readLockFileJSON(lockFilePath string) (LockFile, error) {
func ReadLockFileJSON(lockFilePath string) (LockFile, error) {

lockFileBytes, err := os.ReadFile(lockFilePath)
if err != nil {
Expand Down Expand Up @@ -108,7 +108,7 @@ func ReadStewfileContents(stewfilePath string) ([]PackageData, error) {

// ReadStewLockFileContents will read the contents of the Stewfile.lock.json
func ReadStewLockFileContents(lockFilePath string) ([]PackageData, error) {
lockFile, err := readLockFileJSON(lockFilePath)
lockFile, err := ReadLockFileJSON(lockFilePath)
if err != nil {
return []PackageData{}, err
}
Expand All @@ -125,7 +125,7 @@ func NewLockFile(stewLockFilePath, userOS, userArch string) (LockFile, error) {
if !lockFileExists {
lockFile = LockFile{Os: userOS, Arch: userArch, Packages: []PackageData{}}
} else {
lockFile, err = readLockFileJSON(stewLockFilePath)
lockFile, err = ReadLockFileJSON(stewLockFilePath)
if err != nil {
return LockFile{}, err
}
Expand Down
4 changes: 2 additions & 2 deletions lib/stewfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func Test_readLockFileJSON(t *testing.T) {
lockFilePath := filepath.Join(tempDir, "Stewfile.lock.json")
WriteLockFileJSON(testLockfile, lockFilePath)

got, err := readLockFileJSON(lockFilePath)
got, err := ReadLockFileJSON(lockFilePath)
if (err != nil) != tt.wantErr {
t.Errorf("readLockFileJSON() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down Expand Up @@ -160,7 +160,7 @@ func TestWriteLockFileJSON(t *testing.T) {
t.Errorf("WriteLockFileJSON() error = %v, wantErr %v", err, tt.wantErr)
}

got, _ := readLockFileJSON(lockFilePath)
got, _ := ReadLockFileJSON(lockFilePath)

if !reflect.DeepEqual(got, testLockfile) {
t.Errorf("WriteLockFileJSON() = %v, want %v", got, testLockfile)
Expand Down
18 changes: 18 additions & 0 deletions lib/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ func PromptSelect(message string, options []string) (string, error) {
return result, nil
}

// PromptMultiSelect launches the multiple selection UI
func PromptMultiSelect(message string, options []string, defaultSelections []string) ([]string, error) {
result := []string{}
prompt := &survey.MultiSelect{
Message: message,
Options: options,
Default: defaultSelections,
}
err := survey.AskOne(prompt, &result, survey.WithIcons(func(icons *survey.IconSet) {
icons.Question.Text = "*"
}))
if err != nil {
return []string{}, ExitUserSelectionError{Err: err}
}

return result, nil
}

// PromptInput launches the input UI
func PromptInput(message string, defaultInput string) (string, error) {
result := ""
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func main() {
},
{
Name: "config",
Usage: "Configure the stew file paths using an interactive UI. [Ex: stew config]",
Usage: "Configure stew using an interactive UI. [Ex: stew config]",
Action: func(c *cli.Context) error {
cmd.Config()
return nil
Expand Down

0 comments on commit 1be909e

Please sign in to comment.