diff --git a/cmd/browse.go b/cmd/browse.go index fcb1bf4..a068554 100644 --- a/cmd/browse.go +++ b/cmd/browse.go @@ -56,16 +56,9 @@ func Browse(cliInput string) { downloadURL := githubProject.Releases[tagIndex].Assets[assetIndex].DownloadURL downloadPath := filepath.Join(stewPkgPath, asset) - downloadPathExists, err := stew.PathExists(downloadPath) + err = stew.DownloadFile(downloadPath, downloadURL) stew.CatchAndExit(err) - - if downloadPathExists { - stew.CatchAndExit(stew.AssetAlreadyDownloadedError{Asset: asset}) - } else { - err = stew.DownloadFile(downloadPath, downloadURL) - stew.CatchAndExit(err) - fmt.Printf("✅ Downloaded %v to %v\n", constants.GreenColor(asset), constants.GreenColor(stewPkgPath)) - } + fmt.Printf("✅ Downloaded %v to %v\n", constants.GreenColor(asset), constants.GreenColor(stewPkgPath)) binaryName, err := stew.InstallBinary(downloadPath, repo, systemInfo, &lockFile, false) if err != nil { diff --git a/cmd/install.go b/cmd/install.go index 98b669f..4bbcaf5 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -107,16 +107,9 @@ func Install(cliInputs []string) { } downloadPath := filepath.Join(stewPkgPath, asset) - downloadPathExists, err := stew.PathExists(downloadPath) + err = stew.DownloadFile(downloadPath, downloadURL) stew.CatchAndExit(err) - if downloadPathExists { - fmt.Println(stew.AssetAlreadyDownloadedError{Asset: asset}) - continue - } else { - err = stew.DownloadFile(downloadPath, downloadURL) - stew.CatchAndExit(err) - fmt.Printf("✅ Downloaded %v to %v\n", constants.GreenColor(asset), constants.GreenColor(stewPkgPath)) - } + fmt.Printf("✅ Downloaded %v to %v\n", constants.GreenColor(asset), constants.GreenColor(stewPkgPath)) binaryName, err := stew.InstallBinary(downloadPath, repo, systemInfo, &lockFile, false) if err != nil { diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 6c4e76e..a11f9a3 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -10,21 +10,18 @@ import ( ) // Upgrade is executed when you run `stew upgrade` -func Upgrade(cliFlag bool, binaryName string) { +func Upgrade(upgradeAllCliFlag bool, binaryName string) { userOS, userArch, _, systemInfo, err := stew.Initialize() stew.CatchAndExit(err) - if cliFlag && binaryName != "" { + if upgradeAllCliFlag && binaryName != "" { stew.CatchAndExit(stew.CLIFlagAndInputError{}) - } else if !cliFlag { + } else if !upgradeAllCliFlag { err := stew.ValidateCLIInput(binaryName) stew.CatchAndExit(err) } - sp := constants.LoadingSpinner - - stewPkgPath := systemInfo.StewPkgPath stewTmpPath := systemInfo.StewTmpPath stewLockFilePath := systemInfo.StewLockFilePath @@ -40,82 +37,95 @@ func Upgrade(cliFlag bool, binaryName string) { stew.CatchAndExit(stew.NoBinariesInstalledError{}) } - var binaryFound bool - for index, pkg := range lockFile.Packages { + if upgradeAllCliFlag { + upgradeAll(userOS, userArch, lockFile, systemInfo) + } else { + err := upgradeOne(binaryName, userOS, userArch, lockFile, systemInfo) + stew.CatchAndExit(err) + } +} - var upgradeCondition bool - if cliFlag || pkg.Binary == binaryName { - upgradeCondition = true - } +func upgradeOne(binaryName, userOS, userArch string, lockFile stew.LockFile, systemInfo stew.SystemInfo) error { + sp := constants.LoadingSpinner + stewPkgPath := systemInfo.StewPkgPath + stewLockFilePath := systemInfo.StewLockFilePath + + indexInLockFile, binaryFoundInLockFile := stew.FindBinaryInLockFile(lockFile, binaryName) + if !binaryFoundInLockFile { + return stew.BinaryNotInstalledError{Binary: binaryName} + } + + pkg := lockFile.Packages[indexInLockFile] + fmt.Println(constants.GreenColor(pkg.Binary)) + if pkg.Source == "other" { + return stew.InstalledFromURLError{Binary: pkg.Binary} + } + owner := pkg.Owner + repo := pkg.Repo + + sp.Start() + githubProject, err := stew.NewGithubProject(owner, repo) + sp.Stop() + if err != nil { + return err + } - if upgradeCondition { - fmt.Println(constants.GreenColor(pkg.Binary)) - binaryFound = true - - if pkg.Source == "other" { - fmt.Println(stew.InstalledFromURLError{Binary: pkg.Binary}) - continue - } - owner := pkg.Owner - repo := pkg.Repo - - sp.Start() - githubProject, err := stew.NewGithubProject(owner, repo) - sp.Stop() - stew.CatchAndExit(err) - - // This will make sure that there are any tags at all - _, err = stew.GetGithubReleasesTags(githubProject) - stew.CatchAndExit(err) - - // Get the latest tag - tagIndex := 0 - tag := githubProject.Releases[tagIndex].TagName - - if pkg.Tag == tag { - fmt.Println(stew.AlreadyInstalledLatestTagError{Tag: tag}) - continue - } - - // Make sure there are any assets at all - releaseAssets, err := stew.GetGithubReleasesAssets(githubProject, tag) - stew.CatchAndExit(err) - - asset, err := stew.DetectAsset(userOS, userArch, releaseAssets) - stew.CatchAndExit(err) - assetIndex, _ := stew.Contains(releaseAssets, asset) - - downloadURL := githubProject.Releases[tagIndex].Assets[assetIndex].DownloadURL - - downloadPath := filepath.Join(stewPkgPath, asset) - downloadPathExists, err := stew.PathExists(downloadPath) - stew.CatchAndExit(err) - if downloadPathExists { - stew.CatchAndExit(stew.AssetAlreadyDownloadedError{Asset: asset}) - } else { - err = stew.DownloadFile(downloadPath, downloadURL) - stew.CatchAndExit(err) - fmt.Printf("✅ Downloaded %v to %v\n", constants.GreenColor(asset), constants.GreenColor(stewPkgPath)) - } - - _, err = stew.InstallBinary(downloadPath, repo, systemInfo, &lockFile, true) - if err != nil { - os.RemoveAll(downloadPath) - stew.CatchAndExit(err) - } - - lockFile.Packages[index].Tag = tag - lockFile.Packages[index].Asset = asset - lockFile.Packages[index].URL = downloadURL - - err = stew.WriteLockFileJSON(lockFile, stewLockFilePath) - stew.CatchAndExit(err) - - fmt.Printf("✨ Successfully upgraded the %v binary from %v to %v\n", constants.GreenColor(pkg.Binary), constants.GreenColor(pkg.Tag), constants.GreenColor(tag)) + // This will make sure that there are any tags at all + _, err = stew.GetGithubReleasesTags(githubProject) + if err != nil { + return err + } + + // Get the latest tag + tagIndex := 0 + tag := githubProject.Releases[tagIndex].TagName + + if pkg.Tag == tag { + return stew.AlreadyInstalledLatestTagError{Tag: tag} + } + // Make sure there are any assets at all + releaseAssets, err := stew.GetGithubReleasesAssets(githubProject, tag) + if err != nil { + return err + } + + asset, err := stew.DetectAsset(userOS, userArch, releaseAssets) + if err != nil { + return err + } + assetIndex, _ := stew.Contains(releaseAssets, asset) + downloadURL := githubProject.Releases[tagIndex].Assets[assetIndex].DownloadURL + downloadPath := filepath.Join(stewPkgPath, asset) + err = stew.DownloadFile(downloadPath, downloadURL) + if err != nil { + return err + } + fmt.Printf("✅ Downloaded %v to %v\n", constants.GreenColor(asset), constants.GreenColor(stewPkgPath)) + + _, err = stew.InstallBinary(downloadPath, repo, systemInfo, &lockFile, true) + if err != nil { + if err := os.RemoveAll(downloadPath); err != nil { + return err } } - if !cliFlag && !binaryFound { - stew.CatchAndExit(stew.BinaryNotInstalledError{Binary: binaryName}) + + lockFile.Packages[indexInLockFile].Tag = tag + lockFile.Packages[indexInLockFile].Asset = asset + lockFile.Packages[indexInLockFile].URL = downloadURL + if err := stew.WriteLockFileJSON(lockFile, stewLockFilePath); err != nil { + return err + } + + fmt.Printf("✨ Successfully upgraded the %v binary from %v to %v\n", constants.GreenColor(pkg.Binary), constants.GreenColor(pkg.Tag), constants.GreenColor(tag)) + return nil +} + +func upgradeAll(userOS, userArch string, lockFile stew.LockFile, systemInfo stew.SystemInfo) { + for _, pkg := range lockFile.Packages { + if err := upgradeOne(pkg.Binary, userOS, userArch, lockFile, systemInfo); err != nil { + fmt.Fprintln(os.Stderr, err) + continue + } } } diff --git a/lib/errors.go b/lib/errors.go index 6ba0d86..ba5ebe3 100644 --- a/lib/errors.go +++ b/lib/errors.go @@ -93,15 +93,6 @@ func (e CLIFlagAndInputError) Error() string { return fmt.Sprintf("%v Cannot use the --all flag with a positional argument", constants.RedColor("Error:")) } -// AssetAlreadyDownloadedError occurs if the requested asset has already been downloaded -type AssetAlreadyDownloadedError struct { - Asset string -} - -func (e AssetAlreadyDownloadedError) Error() string { - return fmt.Sprintf("%v The %v asset has already been downloaded and installed", constants.RedColor("Error:"), constants.RedColor(e.Asset)) -} - // AbortBinaryOverwriteError occurs if the overwrite of a binary is aborted type AbortBinaryOverwriteError struct { Binary string diff --git a/lib/errors_test.go b/lib/errors_test.go index a300c58..4f1a657 100644 --- a/lib/errors_test.go +++ b/lib/errors_test.go @@ -265,35 +265,6 @@ func TestCLIFlagAndInputError_Error(t *testing.T) { } } -func TestAssetAlreadyDownloadedError_Error(t *testing.T) { - type fields struct { - Asset string - } - tests := []struct { - name string - fields fields - want string - }{ - { - name: "test1", - fields: fields{ - Asset: "testAsset", - }, - want: fmt.Sprintf("%v The %v asset has already been downloaded and installed", constants.RedColor("Error:"), constants.RedColor("testAsset")), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := AssetAlreadyDownloadedError{ - Asset: tt.fields.Asset, - } - if got := e.Error(); got != tt.want { - t.Errorf("AssetAlreadyDownloadedError.Error() = %v, want %v", got, tt.want) - } - }) - } -} - func TestAbortBinaryOverwriteError_Error(t *testing.T) { type fields struct { Binary string diff --git a/lib/util.go b/lib/util.go index a71256f..5cd96d3 100644 --- a/lib/util.go +++ b/lib/util.go @@ -27,17 +27,15 @@ func isExecutableFile(filePath string) (bool, error) { if err != nil { return false, err } - filePerm := fileInfo.Mode() isExecutable := filePerm&0111 != 0 - return isExecutable, nil } // CatchAndExit will catch errors and immediately exit func CatchAndExit(err error) { if err != nil { - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) os.Exit(1) } } @@ -45,14 +43,12 @@ func CatchAndExit(err error) { // PathExists checks if a given path exists func PathExists(path string) (bool, error) { _, err := os.Stat(path) - if err != nil { if os.IsNotExist(err) { return false, nil } return false, err } - return true, nil } @@ -129,12 +125,7 @@ func copyFile(srcFile, destFile string) error { return err } - err = os.Chmod(destFile, 0755) - if err != nil { - return err - } - - return nil + return os.Chmod(destFile, 0755) } func walkDir(rootDir string) ([]string, error) { @@ -197,7 +188,6 @@ func ValidateCLIInput(cliInput string) error { if cliInput == "" { return EmptyCLIInputError{} } - return nil } @@ -271,7 +261,7 @@ func parseURLInput(cliInput string) (CLIInput, error) { } // Contains checks if a string slice contains a given target -func Contains(slice []string, target string) (int, bool) { +func Contains[T comparable](slice []T, target T) (int, bool) { for index, element := range slice { if target == element { return index, true @@ -280,6 +270,15 @@ func Contains(slice []string, target string) (int, bool) { return -1, false } +func FindBinaryInLockFile(lockFile LockFile, binaryName string) (int, bool) { + for index, pkg := range lockFile.Packages { + if pkg.Binary == binaryName { + return index, true + } + } + return -1, false +} + func extractBinary(downloadedFilePath, tmpExtractionPath string) error { isArchive := isArchiveFile(downloadedFilePath) if isArchive { @@ -287,31 +286,20 @@ func extractBinary(downloadedFilePath, tmpExtractionPath string) error { if err != nil { return err } - - } else { - originalBinaryName := filepath.Base(downloadedFilePath) - - renamedBinaryName, err := PromptRenameBinary(originalBinaryName) - if err != nil { - return err - } - err = copyFile(downloadedFilePath, filepath.Join(tmpExtractionPath, renamedBinaryName)) - if err != nil { - return err - } + return nil } - return nil + originalBinaryName := filepath.Base(downloadedFilePath) + renamedBinaryName, err := PromptRenameBinary(originalBinaryName) + if err != nil { + return err + } + return copyFile(downloadedFilePath, filepath.Join(tmpExtractionPath, renamedBinaryName)) } // InstallBinary will extract the binary and copy it to the ~/.stew/bin path func InstallBinary(downloadedFilePath string, repo string, systemInfo SystemInfo, lockFile *LockFile, overwriteFromUpgrade bool) (string, error) { - - tmpExtractionPath := systemInfo.StewTmpPath - assetDownloadPath := systemInfo.StewPkgPath - binaryInstallPath := systemInfo.StewBinPath - - err := extractBinary(downloadedFilePath, tmpExtractionPath) - if err != nil { + tmpExtractionPath, stewPkgPath, binaryInstallPath := systemInfo.StewTmpPath, systemInfo.StewPkgPath, systemInfo.StewBinPath + if err := extractBinary(downloadedFilePath, tmpExtractionPath); err != nil { return "", err } @@ -320,57 +308,16 @@ func InstallBinary(downloadedFilePath string, repo string, systemInfo SystemInfo return "", err } - binaryFile, binaryName, err := getBinary(allFilePaths, repo) + binaryFileInTmpExtractionPath, binaryName, err := getBinary(allFilePaths, repo) if err != nil { return "", err } - // Check if the binary already exists - for index, pkg := range lockFile.Packages { - previousAssetPath := filepath.Join(assetDownloadPath, pkg.Asset) - newAssetPath := downloadedFilePath - var overwrite bool - if pkg.Binary == binaryName { - if !overwriteFromUpgrade { - overwrite, err = WarningPromptConfirm(fmt.Sprintf("The binary %v version: %v is already installed, would you like to overwrite it?", constants.YellowColor(binaryName), constants.YellowColor(pkg.Tag))) - if err != nil { - os.RemoveAll(newAssetPath) - return "", err - } - } else { - overwrite = true - } - - if overwrite { - err := os.RemoveAll(previousAssetPath) - if err != nil { - return "", err - } - - if !overwriteFromUpgrade { - lockFile.Packages, err = RemovePackage(lockFile.Packages, index) - if err != nil { - return "", err - } - } - - } else { - err = os.RemoveAll(newAssetPath) - if err != nil { - return "", err - } - - err = os.RemoveAll(tmpExtractionPath) - if err != nil { - return "", err - } - - return "", AbortBinaryOverwriteError{Binary: pkg.Binary} - } - } + if err = handleExistingBinary(lockFile, binaryName, downloadedFilePath, stewPkgPath, overwriteFromUpgrade); err != nil { + return "", err } - err = copyFile(binaryFile, filepath.Join(binaryInstallPath, binaryName)) + err = copyFile(binaryFileInTmpExtractionPath, filepath.Join(binaryInstallPath, binaryName)) if err != nil { return "", err } @@ -383,6 +330,51 @@ func InstallBinary(downloadedFilePath string, repo string, systemInfo SystemInfo return binaryName, nil } +func handleExistingBinary(lockFile *LockFile, binaryName, newlyDownloadedAssetPath, stewPkgPath string, overwriteFromUpgrade bool) error { + indexInLockFile, binaryFoundInLockFile := FindBinaryInLockFile(*lockFile, binaryName) + if !binaryFoundInLockFile { + return nil + } + pkg := lockFile.Packages[indexInLockFile] + if !overwriteFromUpgrade { + userChoosingToOverwrite, err := WarningPromptConfirm(fmt.Sprintf("The binary %v version: %v is already installed, would you like to overwrite it?", constants.YellowColor(binaryName), constants.YellowColor(pkg.Tag))) + if err != nil { + if err := os.RemoveAll(newlyDownloadedAssetPath); err != nil { + return err + } + return err + } + if !userChoosingToOverwrite { + if err := os.RemoveAll(newlyDownloadedAssetPath); err != nil { + return err + } + return AbortBinaryOverwriteError{Binary: binaryName} + } + } + return overwriteBinary(lockFile, indexInLockFile, newlyDownloadedAssetPath, stewPkgPath, overwriteFromUpgrade) +} + +func overwriteBinary(lockFile *LockFile, indexInLockFile int, newlyDownloadedAssetPath, stewPkgPath string, overwriteFromUpgrade bool) error { + pkg := lockFile.Packages[indexInLockFile] + previousAssetPath := filepath.Join(stewPkgPath, pkg.Asset) + if previousAssetPath != newlyDownloadedAssetPath { + if err := os.RemoveAll(previousAssetPath); err != nil { + return err + } + } + // If not overwriting as part of an upgrade, remove the package entry from the lock file + // This is because the upgrade command will update the package entry in place + // but the install command will add a new package entry + if !overwriteFromUpgrade { + var err error + lockFile.Packages, err = RemovePackage(lockFile.Packages, indexInLockFile) + if err != nil { + return err + } + } + return nil +} + // PromptRenameBinary takes in the original name of the binary and will return the new name of the binary. func PromptRenameBinary(originalBinaryName string) (string, error) { renamedBinaryName, err := warningPromptInput("Rename the binary?", originalBinaryName)