diff --git a/CHANGELOG.md b/CHANGELOG.md index bfea568..c8d6298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log (go-package-manager) +## 0.30.0 + +- feat: `gpm update` now supports update of specific modules as well +- fix: using `TidyUp()` method from `AppContext` instead raw `go mod tidy` +- refactor: `gpm doctor` now checks all dependencies for up-to-dateness +- refactor: code cleanups and improvements + ## 0.29.10 - feat: self-update by executing `gpm update --self` diff --git a/README.md b/README.md index 8b8fe19..d32535c 100644 --- a/README.md +++ b/README.md @@ -585,6 +585,14 @@ gpm update is a short version of `go get -u ./... && go mod tidy` and will update all dependencies. +On the other hand, you are able to run something like + +```bash +gpm update github.com/alecthomas/chroma yaml +``` + +to update specific ones. Each argument can be a module URL or [alias](#add-alias-). + ## Setup AI [] If you would like to use AI feature, like suggestion of branch names, you can setup one of the following APIs: diff --git a/commands/doctor.go b/commands/doctor.go index ad0804e..8c60dc9 100644 --- a/commands/doctor.go +++ b/commands/doctor.go @@ -116,7 +116,6 @@ func Init_Doctor_Command(parentCmd *cobra.Command, app *types.AppContext) { // cleanups and extract items as references allItems := make([]*GoModFileRequireItem, 0) - directItems := make([]*GoModFileRequireItem, 0) for _, item := range goMod.Require { refItem := &item @@ -124,17 +123,14 @@ func Init_Doctor_Command(parentCmd *cobra.Command, app *types.AppContext) { refItem.Version = strings.TrimSpace(refItem.Version) allItems = append(allItems, refItem) - if refItem.Indirect == nil || !*refItem.Indirect { - directItems = append(directItems, refItem) - } } - if len(directItems) > 0 { + if len(allItems) > 0 { fmt.Println("Checking dependencies for up-to-dateness ...") - for i, item := range directItems { + for i, item := range allItems { s := spinner.New(spinner.CharSets[24], 100*time.Millisecond) s.Prefix = "\t[" - s.Suffix = fmt.Sprintf("] Checking '%s' (%v/%v) ...", item.Path, i+1, len(directItems)) + s.Suffix = fmt.Sprintf("] Checking '%s' (%v/%v) ...", item.Path, i+1, len(allItems)) s.Start() thisVersion, err := version.NewVersion(strings.TrimSpace(item.Version)) diff --git a/commands/generate.go b/commands/generate.go index 1d5dae1..dab3a48 100644 --- a/commands/generate.go +++ b/commands/generate.go @@ -418,12 +418,7 @@ require ( } // cleanup project - p = utils.CreateShellCommandByArgs("go", "mod", "tidy") - p.Dir = outDir - p.Stdout = nil - p.Stderr = nil - app.Debug(fmt.Sprintf("Cleanup project '%s' ...", projectUrl)) - utils.RunCommand(p) + app.TidyUp() // output final summary out, _ := glamour.Render(lastResponse.FinalSummary, "dark") diff --git a/commands/update.go b/commands/update.go index 1f3463a..6a8823b 100644 --- a/commands/update.go +++ b/commands/update.go @@ -23,20 +23,10 @@ package commands import ( - "bufio" - "bytes" "fmt" - "io" - "net/http" - "os" - "os/exec" - "runtime" "strings" - browser "github.com/EDDYCJY/fake-useragent" - "github.com/alecthomas/chroma/quick" "github.com/mkloubert/go-package-manager/types" - "github.com/mkloubert/go-package-manager/utils" "github.com/spf13/cobra" ) @@ -51,215 +41,45 @@ func Init_Update_Command(parentCmd *cobra.Command, app *types.AppContext) { var userAgent string var updateCmd = &cobra.Command{ - Use: "update", + Use: "update ", Aliases: []string{"upd"}, Short: "Update dependencies", - Long: `Updates all dependencies in this project.`, + Long: `Updates all or only specific dependencies in this project.`, Run: func(cmd *cobra.Command, args []string) { if selfUpdate { - app.Debug("Will start self-update ...") - - consoleFormatter := utils.GetBestChromaFormatterName() - consoleStyle := utils.GetBestChromaStyleName() - - customUserAgent := strings.TrimSpace(userAgent) - if customUserAgent == "" { - customUserAgent = browser.Chrome() - } - - customPowerShellBin := strings.TrimSpace(powerShellBin) - if customPowerShellBin == "" { - customPowerShellBin = "powershell" - } - - customUpdateScript := strings.TrimSpace(updateScript) - if customUpdateScript == "" { - customUpdateScript = strings.TrimSpace(os.Getenv("GPM_UPDATE_SCRIPT")) - } - - downloadScript := func(url string) ([]byte, error) { - app.Debug(fmt.Sprintf("Download from '%s' ...", url)) - app.Debug(fmt.Sprintf("User agent: %s", customUserAgent)) - - req, err := http.NewRequest("GET", url, bytes.NewBuffer([]byte{})) - if err != nil { - return []byte{}, err - } - - req.Header.Set("User-Agent", customUserAgent) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return []byte{}, err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return []byte{}, fmt.Errorf("unexpected response: %v", resp.StatusCode) - } - - responseData, err := io.ReadAll(resp.Body) - - return responseData, err - } - - showNewVersion := func() { - if noVersionPrint { - return - } + run_self_update_command( + app, + force, noVersionPrint, powerShell, powerShellBin, updateScript, userAgent, + ) + } else { + modulesToUpdate := make([]string, 0) + for _, moduleNameOrUrl := range args { + // maybe alias => module url(s) + moduleUrls := app.GetModuleUrls(moduleNameOrUrl) - app.RunShellCommandByArgs("gpm", "--version") + modulesToUpdate = append(modulesToUpdate, moduleUrls...) } - if powerShell || utils.IsWindows() { - // PowerShell - app.Debug(fmt.Sprintf("Will use PowerShell '%s' ...", powerShellBin)) - - scriptUrl := customUpdateScript - if scriptUrl == "" { - scriptUrl = "https://raw.githubusercontent.com/mkloubert/go-package-manager/main/sh.kloubert.dev/gpm.ps1" - } else { - su, err := utils.ToUrlForOpenHandler(scriptUrl) - utils.CheckForError(err) - - scriptUrl = su - } - - pwshScript, err := downloadScript(scriptUrl) - utils.CheckForError(err) - - executeScript := func() { - p := exec.Command(customPowerShellBin, "-NoProfile", "-Command", "-") - p.Dir = app.Cwd - p.Stderr = os.Stderr - p.Stdout = os.Stdout - - stdinPipe, err := p.StdinPipe() - utils.CheckForError(err) - - err = p.Start() - utils.CheckForError(err) - - go func() { - defer stdinPipe.Close() - stdinPipe.Write([]byte(pwshScript)) - }() + additionalShellArgs := make([]string, 0) + additionalShellArgs = append(additionalShellArgs, modulesToUpdate...) + if len(modulesToUpdate) == 0 { + app.Debug("Will update all modules in project ...") - err = p.Wait() - utils.CheckForError(err) - - showNewVersion() - os.Exit(0) - } - - if force { - executeScript() - } else { - // ask the user first - - err = quick.Highlight(os.Stdout, string(pwshScript), "powershell", consoleFormatter, consoleStyle) - if err != nil { - fmt.Print(string(pwshScript)) - } - - fmt.Println() - fmt.Println() - - reader := bufio.NewReader(os.Stdin) - - for { - fmt.Print("Do you really want to run this PowerShell script (Y/n)? ") - userInput, _ := reader.ReadString('\n') - userInput = strings.TrimSpace(strings.ToLower(userInput)) - - switch userInput { - case "", "y", "yes": - executeScript() - case "n", "no": - os.Exit(0) - } - } - } - } else if utils.IsPOSIXLikeOS() { - // if POSIX-like => sh - app.Debug("Will use UNIX shell ...") - - scriptUrl := customUpdateScript - if scriptUrl == "" { - scriptUrl = "https://raw.githubusercontent.com/mkloubert/go-package-manager/main/sh.kloubert.dev/gpm.sh" - } else { - su, err := utils.ToUrlForOpenHandler(scriptUrl) - utils.CheckForError(err) - - scriptUrl = su - } - - bashScript, err := downloadScript(scriptUrl) - utils.CheckForError(err) - - executeScript := func() { - p := exec.Command("sh") - p.Dir = app.Cwd - p.Stderr = os.Stderr - p.Stdout = os.Stdout - - stdinPipe, err := p.StdinPipe() - utils.CheckForError(err) - - err = p.Start() - utils.CheckForError(err) - - go func() { - defer stdinPipe.Close() - stdinPipe.Write([]byte(bashScript)) - }() - - err = p.Wait() - utils.CheckForError(err) - - showNewVersion() - os.Exit(0) - } - - if force { - executeScript() - } else { - // ask the user first - - err = quick.Highlight(os.Stdout, string(bashScript), "shell", consoleFormatter, consoleStyle) - if err != nil { - fmt.Print(string(bashScript)) - } - - fmt.Println() - fmt.Println() - - reader := bufio.NewReader(os.Stdin) - - for { - fmt.Print("Do you really want to run this bash script (Y/n)? ") - userInput, _ := reader.ReadString('\n') - userInput = strings.TrimSpace(strings.ToLower(userInput)) - - switch userInput { - case "", "y", "yes": - executeScript() - case "n", "no": - os.Exit(0) - } - } - } + // update all in this project instead specific onces + additionalShellArgs = append(additionalShellArgs, "./...") } else { - utils.CheckForError(fmt.Errorf("self-update for %s/%s is not supported yet", runtime.GOOS, runtime.GOARCH)) + // update specific ones + app.Debug(fmt.Sprintf("Will update following modules in project: %s", strings.Join(modulesToUpdate, ","))) } - } else { - app.Debug("Will start project dependencies ...") - app.RunShellCommandByArgs("go", "get", "-u", "./...") + allShellArgs := make([]string, 0) + allShellArgs = append(allShellArgs, "get", "-u") + allShellArgs = append(allShellArgs, additionalShellArgs...) + + app.RunShellCommandByArgs("go", allShellArgs...) if !noCleanup { - app.RunShellCommandByArgs("go", "mod", "tidy") + app.TidyUp() } } }, diff --git a/commands/update.self_update.go b/commands/update.self_update.go new file mode 100644 index 0000000..cf66448 --- /dev/null +++ b/commands/update.self_update.go @@ -0,0 +1,242 @@ +// MIT License +// +// Copyright (c) 2024 Marcel Joachim Kloubert (https://marcel.coffee) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package commands + +import ( + "bufio" + "bytes" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "runtime" + "strings" + + browser "github.com/EDDYCJY/fake-useragent" + "github.com/alecthomas/chroma/quick" + "github.com/mkloubert/go-package-manager/types" + "github.com/mkloubert/go-package-manager/utils" +) + +func run_self_update_command( + app *types.AppContext, + force bool, noVersionPrint bool, powerShell bool, powerShellBin string, updateScript string, userAgent string, +) { + app.Debug("Will start self-update ...") + + consoleFormatter := utils.GetBestChromaFormatterName() + consoleStyle := utils.GetBestChromaStyleName() + + customUserAgent := strings.TrimSpace(userAgent) + if customUserAgent == "" { + customUserAgent = browser.Chrome() + } + + customPowerShellBin := strings.TrimSpace(powerShellBin) + if customPowerShellBin == "" { + customPowerShellBin = "powershell" + } + + customUpdateScript := strings.TrimSpace(updateScript) + if customUpdateScript == "" { + customUpdateScript = strings.TrimSpace(os.Getenv("GPM_UPDATE_SCRIPT")) + } + + downloadScript := func(url string) ([]byte, error) { + app.Debug(fmt.Sprintf("Download from '%s' ...", url)) + app.Debug(fmt.Sprintf("User agent: %s", customUserAgent)) + + req, err := http.NewRequest("GET", url, bytes.NewBuffer([]byte{})) + if err != nil { + return []byte{}, err + } + + req.Header.Set("User-Agent", customUserAgent) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return []byte{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return []byte{}, fmt.Errorf("unexpected response: %v", resp.StatusCode) + } + + responseData, err := io.ReadAll(resp.Body) + + return responseData, err + } + + showNewVersion := func() { + if noVersionPrint { + return + } + + app.RunShellCommandByArgs("gpm", "--version") + } + + if powerShell || utils.IsWindows() { + // PowerShell + app.Debug(fmt.Sprintf("Will use PowerShell '%s' ...", powerShellBin)) + + scriptUrl := customUpdateScript + if scriptUrl == "" { + scriptUrl = "https://raw.githubusercontent.com/mkloubert/go-package-manager/main/sh.kloubert.dev/gpm.ps1" + } else { + su, err := utils.ToUrlForOpenHandler(scriptUrl) + utils.CheckForError(err) + + scriptUrl = su + } + + pwshScript, err := downloadScript(scriptUrl) + utils.CheckForError(err) + + executeScript := func() { + p := exec.Command(customPowerShellBin, "-NoProfile", "-Command", "-") + p.Dir = app.Cwd + p.Stderr = os.Stderr + p.Stdout = os.Stdout + + stdinPipe, err := p.StdinPipe() + utils.CheckForError(err) + + err = p.Start() + utils.CheckForError(err) + + go func() { + defer stdinPipe.Close() + stdinPipe.Write([]byte(pwshScript)) + }() + + err = p.Wait() + utils.CheckForError(err) + + showNewVersion() + os.Exit(0) + } + + if force { + executeScript() + } else { + // ask the user first + + err = quick.Highlight(os.Stdout, string(pwshScript), "powershell", consoleFormatter, consoleStyle) + if err != nil { + fmt.Print(string(pwshScript)) + } + + fmt.Println() + fmt.Println() + + reader := bufio.NewReader(os.Stdin) + + for { + fmt.Print("Do you really want to run this PowerShell script (Y/n)? ") + userInput, _ := reader.ReadString('\n') + userInput = strings.TrimSpace(strings.ToLower(userInput)) + + switch userInput { + case "", "y", "yes": + executeScript() + case "n", "no": + os.Exit(0) + } + } + } + } else if utils.IsPOSIXLikeOS() { + // if POSIX-like => sh + app.Debug("Will use UNIX shell ...") + + scriptUrl := customUpdateScript + if scriptUrl == "" { + scriptUrl = "https://raw.githubusercontent.com/mkloubert/go-package-manager/main/sh.kloubert.dev/gpm.sh" + } else { + su, err := utils.ToUrlForOpenHandler(scriptUrl) + utils.CheckForError(err) + + scriptUrl = su + } + + bashScript, err := downloadScript(scriptUrl) + utils.CheckForError(err) + + executeScript := func() { + p := exec.Command("sh") + p.Dir = app.Cwd + p.Stderr = os.Stderr + p.Stdout = os.Stdout + + stdinPipe, err := p.StdinPipe() + utils.CheckForError(err) + + err = p.Start() + utils.CheckForError(err) + + go func() { + defer stdinPipe.Close() + stdinPipe.Write([]byte(bashScript)) + }() + + err = p.Wait() + utils.CheckForError(err) + + showNewVersion() + os.Exit(0) + } + + if force { + executeScript() + } else { + // ask the user first + + err = quick.Highlight(os.Stdout, string(bashScript), "shell", consoleFormatter, consoleStyle) + if err != nil { + fmt.Print(string(bashScript)) + } + + fmt.Println() + fmt.Println() + + reader := bufio.NewReader(os.Stdin) + + for { + fmt.Print("Do you really want to run this bash script (Y/n)? ") + userInput, _ := reader.ReadString('\n') + userInput = strings.TrimSpace(strings.ToLower(userInput)) + + switch userInput { + case "", "y", "yes": + executeScript() + case "n", "no": + os.Exit(0) + } + } + } + } else { + utils.CheckForError(fmt.Errorf("self-update for %s/%s is not supported yet", runtime.GOOS, runtime.GOARCH)) + } +} diff --git a/go.mod b/go.mod index a0734aa..7bbcf70 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( github.com/yuin/goldmark-emoji v1.0.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 80911b8..7bccc3a 100644 --- a/go.sum +++ b/go.sum @@ -203,8 +203,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/types/app_context.go b/types/app_context.go index a800ee5..61ec8a0 100644 --- a/types/app_context.go +++ b/types/app_context.go @@ -745,7 +745,7 @@ func (app *AppContext) GetGpmFilePath() (string, error) { } // app.GetModuleUrls() - returns the list of module urls based on the -// information from gpm.y(a)ml file +// information from aliases.y(a)ml file if possible func (app *AppContext) GetModuleUrls(moduleNameOrUrl string) []string { moduleNameOrUrl = utils.CleanupModuleName(moduleNameOrUrl)