From afb1ad625615a1110f7ef5e33c5d0b32919b317c Mon Sep 17 00:00:00 2001 From: Hau Nguyen Date: Fri, 22 Jul 2022 21:09:34 +0700 Subject: [PATCH] feat: update go.mod --- .deps | 2 + .gitignore | 17 ++++++ .golangci.yml | 74 +++++++++++++++++++++++ README.md | 5 ++ cmd/update-go-mod/main.go | 8 +++ go.mod | 18 ++++++ go.sum | 21 +++++++ internal/cli/action.go | 33 ++++++++++ internal/cli/action_run.go | 121 +++++++++++++++++++++++++++++++++++++ internal/cli/app.go | 75 +++++++++++++++++++++++ 10 files changed, 374 insertions(+) create mode 100644 .deps create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 README.md create mode 100644 cmd/update-go-mod/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/cli/action.go create mode 100644 internal/cli/action_run.go create mode 100644 internal/cli/app.go diff --git a/.deps b/.deps new file mode 100644 index 0000000..6478bbe --- /dev/null +++ b/.deps @@ -0,0 +1,2 @@ +github.com/make-go-great/color-go +github.com/urfave/cli/v2 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..14a808d --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# macOS +.DS_Store + +# Window +*.exe + +# IntelliJ +.idea + +# VSCode +.vscode + +# Go +coverage.out + +# GoReleaser +dist diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..6f791d2 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,74 @@ +run: + tests: false + skip-dirs: + - ".*test.*" + - ".*mock.*" + - ".*generated.*" + - ".*example.*" + skip-files: + - ".*Mock.*" + - ".*_mock.*" + - ".*_generated.*" + go: "1.18" + +output: + sort-results: true + +linters: + disable-all: true + enable: + - deadcode + - errcheck + - gosimple + - govet + - staticcheck + - typecheck + - unused + - varcheck + - errname + - errorlint + - execinquery + - goerr113 + - gofumpt + - gosec + - ifshort + - importas + - makezero + - nilnil + - prealloc + - unconvert + fast: true + +linters-settings: + gosec: + excludes: + - G402 + - G501 + - G505 + exclude-generated: true + govet: + check-shadowing: false + disable-all: true + enable: + - assign + - atomic + - bools + - buildtag + - composites + - copylocks + - fieldalignment + - httpresponse + - loopclosure + - lostcancel + - nilfunc + - printf + - unmarshal + - unreachable + - unusedresult + staticcheck: + go: "1.18" + checks: ["all", "-SA1019"] + +issues: + new: true + fix: true diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2483aa --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# update-go-mod + +## Thanks + +- [Managing dependencies](https://go.dev/doc/modules/managing-dependencies) diff --git a/cmd/update-go-mod/main.go b/cmd/update-go-mod/main.go new file mode 100644 index 0000000..92df1ec --- /dev/null +++ b/cmd/update-go-mod/main.go @@ -0,0 +1,8 @@ +package main + +import "github.com/haunt98/update-go-mod/internal/cli" + +func main() { + app := cli.NewApp() + app.Run() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1cb26d5 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module github.com/haunt98/update-go-mod + +go 1.18 + +require ( + github.com/make-go-great/color-go v0.4.1 + github.com/urfave/cli/v2 v2.11.1 +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ac9d28e --- /dev/null +++ b/go.sum @@ -0,0 +1,21 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/make-go-great/color-go v0.4.1 h1:4HIat5r1oQneu5BSDUjHKN/9x4Ay2qEEkswdOgk/VRY= +github.com/make-go-great/color-go v0.4.1/go.mod h1:G5G8IJ3PUo+vSQ+UvnfaswF8O/piVIhFJKv1mQjBGpI= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/urfave/cli/v2 v2.11.1 h1:UKK6SP7fV3eKOefbS87iT9YHefv7iB/53ih6e+GNAsE= +github.com/urfave/cli/v2 v2.11.1/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/cli/action.go b/internal/cli/action.go new file mode 100644 index 0000000..a424f2a --- /dev/null +++ b/internal/cli/action.go @@ -0,0 +1,33 @@ +package cli + +import ( + "log" + + "github.com/urfave/cli/v2" +) + +type action struct { + flags struct { + depsFile string + verbose bool + dryRun bool + } +} + +func (a *action) RunHelp(c *cli.Context) error { + return cli.ShowAppHelp(c) +} + +func (a *action) getFlags(c *cli.Context) { + a.flags.verbose = c.Bool(flagVerbose) + a.flags.depsFile = c.String(flagDepsFile) + a.flags.dryRun = c.Bool(flagDryRun) + + a.log("flags %+v", a.flags) +} + +func (a *action) log(format string, v ...interface{}) { + if a.flags.verbose { + log.Printf(format, v...) + } +} diff --git a/internal/cli/action_run.go b/internal/cli/action_run.go new file mode 100644 index 0000000..dbcfbed --- /dev/null +++ b/internal/cli/action_run.go @@ -0,0 +1,121 @@ +package cli + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/make-go-great/color-go" + "github.com/urfave/cli/v2" +) + +var ErrInvalidModuleVersion = errors.New("invalid module version") + +// See https://pkg.go.dev/cmd/go#hdr-List_packages_or_modules +type Module struct { + Update *Module + Path string + Version string +} + +func (a *action) Run(c *cli.Context) error { + a.getFlags(c) + + // Get all imported modules + goListAllArgs := []string{"list", "-m", "-json", "all"} + goOutput, err := exec.CommandContext(c.Context, "go", goListAllArgs...).CombinedOutput() + if err != nil { + return fmt.Errorf("failed to run go %+v: %w", strings.Join(goListAllArgs, " "), err) + } + + // goAllOutput is like {...}\n{...}\n{...} + // Missing [] and , for json + goOutputStr := strings.ReplaceAll(strings.TrimSpace(string(goOutput)), "\n", "") + goOutputStr = strings.ReplaceAll(goOutputStr, "}{", "},{") + goOutputStr = "[" + goOutputStr + "]" + + importedModules := make([]Module, 0, 100) + if err := json.Unmarshal([]byte(goOutputStr), &importedModules); err != nil { + return fmt.Errorf("failed to json unmarshal: %w", err) + } + a.log("go output: %s", string(goOutput)) + + mapImportedModules := make(map[string]struct{}) + for _, importedModule := range importedModules { + mapImportedModules[importedModule.Path] = struct{}{} + } + a.log("imported modules: %+v\n", importedModules) + + // Read pkg from file + depsFileBytes, err := os.ReadFile(a.flags.depsFile) + if err != nil { + if os.IsNotExist(err) { + color.PrintAppWarning(name, fmt.Sprintf("deps file [%s] not found", a.flags.depsFile)) + return nil + } + + return fmt.Errorf("failed to read file %s: %w", a.flags.depsFile, err) + } + + depsFileStr := strings.TrimSpace(string(depsFileBytes)) + lines := strings.Split(depsFileStr, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + a.log("line: %s", line) + + // Check if line is imported module, otherwise skip + if _, ok := mapImportedModules[line]; !ok { + a.log("%s is not imported module", line) + continue + } + + // Get module latest version + goListArgs := []string{"list", "-m", "-u", "-json", line} + goOutput, err = exec.CommandContext(c.Context, "go", goListArgs...).CombinedOutput() + if err != nil { + return fmt.Errorf("failed to run go %+v: %w", strings.Join(goListArgs, " "), err) + } + a.log("go output: %s", string(goOutput)) + + module := Module{} + if err := json.Unmarshal(goOutput, &module); err != nil { + return fmt.Errorf("failed to json unmarshal: %w", err) + } + a.log("module: %+v", module) + + if module.Update == nil { + color.PrintAppOK(name, fmt.Sprintf("You already have latest [%s] version [%s]", module.Path, module.Version)) + continue + } + + // Upgrade module + if a.flags.dryRun { + color.PrintAppOK(name, fmt.Sprintf("Will upgrade [%s] version [%s] to [%s]", module.Path, module.Version, module.Update.Version)) + continue + } + + goGetArgs := []string{"get", "-d", line + "@" + module.Update.Version} + goOutput, err = exec.CommandContext(c.Context, "go", goGetArgs...).CombinedOutput() + if err != nil { + return fmt.Errorf("failed to run go %+v: %w", strings.Join(goGetArgs, " "), err) + } + a.log("go output: %s", string(goOutput)) + + color.PrintAppOK(name, fmt.Sprintf("Upgraded [%s] version [%s] to [%s] success", module.Path, module.Version, module.Update.Version)) + } + + goModArgs := []string{"mod", "tidy"} + goOutput, err = exec.CommandContext(c.Context, "go", goModArgs...).CombinedOutput() + if err != nil { + return fmt.Errorf("failed to run go %+v: %w", strings.Join(goModArgs, " "), err) + } + a.log("go output: %s", string(goOutput)) + + return nil +} diff --git a/internal/cli/app.go b/internal/cli/app.go new file mode 100644 index 0000000..ad8b960 --- /dev/null +++ b/internal/cli/app.go @@ -0,0 +1,75 @@ +package cli + +import ( + "os" + + "github.com/make-go-great/color-go" + "github.com/urfave/cli/v2" +) + +const ( + name = "update-go-mod" + usage = "excatly like the name says" + + flagVerbose = "verbose" + flagDepsFile = "deps-file" + flagDryRun = "dry-run" + + commandRun = "run" + + usageCommandRun = "run the program" + usageFlagVerbose = "show what is going on" + usageDepsFile = "show what deps need to upgrade" + usageDryRun = "demo what would be done" + + defaultDepsFile = ".deps" +) + +var aliasFlagVerbose = []string{"v"} + +type App struct { + cliApp *cli.App +} + +func NewApp() *App { + a := &action{} + + cliApp := &cli.App{ + Name: name, + Usage: usage, + Commands: []*cli.Command{ + { + Name: commandRun, + Usage: usageCommandRun, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: flagVerbose, + Aliases: aliasFlagVerbose, + Usage: usageFlagVerbose, + }, + &cli.StringFlag{ + Name: flagDepsFile, + Usage: usageDepsFile, + Value: defaultDepsFile, + }, + &cli.BoolFlag{ + Name: flagDryRun, + Usage: usageDryRun, + }, + }, + Action: a.Run, + }, + }, + Action: a.RunHelp, + } + + return &App{ + cliApp: cliApp, + } +} + +func (a *App) Run() { + if err := a.cliApp.Run(os.Args); err != nil { + color.PrintAppError(name, err.Error()) + } +}