├── .gitattributes ├── screenshot.png ├── .gitignore ├── Makefile ├── shell.nix ├── .envrc ├── .goreleaser.yml ├── nix ├── sources.json └── sources.nix ├── go.mod ├── License ├── .github └── workflows │ └── go.yaml ├── internal ├── module │ └── module.go └── app │ └── app.go ├── readme.md ├── main.go └── go.sum /.gitattributes: -------------------------------------------------------------------------------- 1 | go.sum binary 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oligot/go-mod-upgrade/HEAD/screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | go-mod-upgrade 3 | 4 | bin/ 5 | dist/ 6 | .idea/ 7 | .direnv/ 8 | vendor 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Lint the source code 2 | lint: 3 | @golangci-lint run 4 | .PHONY: lint 5 | 6 | # Vulnerability checking 7 | vulncheck: 8 | @govulncheck ./... 9 | .PHONY: vulncheck 10 | 11 | # Release a new version 12 | release: 13 | @goreleaser --clean 14 | .PHONY: release 15 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | sources ? import ./nix/sources.nix, 3 | pkgs ? import sources.nixpkgs { }, 4 | }: 5 | 6 | pkgs.mkShell { 7 | 8 | buildInputs = [ 9 | pkgs.niv 10 | pkgs.nil 11 | pkgs.go_1_24 12 | pkgs.golangci-lint 13 | pkgs.gopls 14 | pkgs.goreleaser 15 | pkgs.govulncheck 16 | ]; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | # vi: set ft=bash : 2 | 3 | if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then 4 | echo "has not nix_direnv_verions" 5 | source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" 6 | fi 7 | 8 | if has nix-shell; then 9 | echo "not nix_shell" 10 | use nix 11 | fi 12 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | --- 2 | before: 3 | hooks: 4 | - go mod tidy 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - darwin 10 | - linux 11 | - windows 12 | archives: 13 | - name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 14 | checksum: 15 | name_template: 'checksums.txt' 16 | snapshot: 17 | name_template: "{{ .Tag }}-next" 18 | changelog: 19 | sort: asc 20 | filters: 21 | exclude: 22 | - '^docs:' 23 | - '^test:' 24 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "nixpkgs": { 3 | "branch": "nixos-unstable", 4 | "description": "Nix Packages collection", 5 | "homepage": "", 6 | "owner": "NixOS", 7 | "repo": "nixpkgs", 8 | "rev": "8eaee110344796db060382e15d3af0a9fc396e0e", 9 | "sha256": "1k5zyljqg8fp99hzgjkvkawhmbwlfsnz4viisfcfdjyky9zrc8c8", 10 | "type": "tarball", 11 | "url": "https://github.com/NixOS/nixpkgs/archive/8eaee110344796db060382e15d3af0a9fc396e0e.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oligot/go-mod-upgrade 2 | 3 | go 1.25.1 4 | 5 | require ( 6 | github.com/AlecAivazis/survey/v2 v2.3.7 7 | github.com/Masterminds/semver/v3 v3.4.0 8 | github.com/apex/log v1.9.0 9 | github.com/briandowns/spinner v1.23.2 10 | github.com/fatih/color v1.18.0 11 | github.com/urfave/cli/v2 v2.27.7 12 | golang.org/x/mod v0.26.0 13 | ) 14 | 15 | require ( 16 | github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect 17 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 18 | github.com/mattn/go-colorable v0.1.13 // indirect 19 | github.com/mattn/go-isatty v0.0.20 // indirect 20 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect 21 | github.com/pkg/errors v0.8.1 // indirect 22 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 23 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect 24 | golang.org/x/sys v0.25.0 // indirect 25 | golang.org/x/term v0.1.0 // indirect 26 | golang.org/x/text v0.4.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Olivier Ligot 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /.github/workflows/go.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Go 3 | 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | test: 8 | name: Test 9 | runs-on: ubuntu-24.04 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: cachix/install-nix-action@v30 13 | with: 14 | nix_path: nixpkgs=channel:nixos-unstable 15 | - run: nix-shell --run "go test -race ./..." 16 | build: 17 | name: Build 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | matrix: 21 | os: [ubuntu-24.04, macos-14, macos-15] 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: cachix/install-nix-action@v30 25 | with: 26 | nix_path: nixpkgs=channel:nixos-unstable 27 | - run: nix-shell --run "go build" 28 | lint: 29 | name: Lint 30 | runs-on: ubuntu-24.04 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: cachix/install-nix-action@v30 34 | with: 35 | nix_path: nixpkgs=channel:nixos-unstable 36 | - run: nix-shell --run "make lint" 37 | vulncheck: 38 | name: Vulnerability checking 39 | runs-on: ubuntu-24.04 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: cachix/install-nix-action@v30 43 | with: 44 | nix_path: nixpkgs=channel:nixos-unstable 45 | - run: nix-shell --run "make vulncheck" 46 | release: 47 | name: Release 48 | runs-on: ubuntu-24.04 49 | if: startsWith(github.ref, 'refs/tags/') 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v4 53 | with: 54 | fetch-depth: 0 55 | - uses: cachix/install-nix-action@v30 56 | with: 57 | nix_path: nixpkgs=channel:nixos-unstable 58 | - name: Release 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | run: nix-shell --run "make release" 62 | -------------------------------------------------------------------------------- /internal/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/Masterminds/semver/v3" 9 | "github.com/fatih/color" 10 | ) 11 | 12 | func padRight(str string, length int) string { 13 | if len(str) >= length { 14 | return str 15 | } 16 | return str + strings.Repeat(" ", length-len(str)) 17 | } 18 | 19 | type Module struct { 20 | Name string 21 | From *semver.Version 22 | To *semver.Version 23 | } 24 | 25 | func (mod *Module) FormatName(length int) string { 26 | c := color.New(color.FgWhite).SprintFunc() 27 | from := mod.From 28 | to := mod.To 29 | if from.Major() == 0 { 30 | c = color.New(color.FgRed).SprintFunc() 31 | } else if from.Minor() < to.Minor() { 32 | c = color.New(color.FgYellow).SprintFunc() 33 | } else if from.Patch() < to.Patch() { 34 | c = color.New(color.FgGreen).SprintFunc() 35 | } 36 | return c(padRight(mod.Name, length)) 37 | } 38 | 39 | func (mod *Module) FormatFrom(length int) string { 40 | c := color.New(color.FgBlue).SprintFunc() 41 | return c(padRight(mod.From.String(), length)) 42 | } 43 | 44 | func (mod *Module) FormatTo() string { 45 | green := color.New(color.FgGreen).SprintFunc() 46 | var buf bytes.Buffer 47 | from := mod.From 48 | to := mod.To 49 | same := true 50 | fmt.Fprintf(&buf, "%d.", to.Major()) 51 | if from.Minor() == to.Minor() { 52 | fmt.Fprintf(&buf, "%d.", to.Minor()) 53 | } else { 54 | fmt.Fprintf(&buf, "%s%s", green(to.Minor()), green(".")) 55 | same = false 56 | } 57 | if from.Patch() == to.Patch() && same { 58 | fmt.Fprintf(&buf, "%d", to.Patch()) 59 | } else { 60 | fmt.Fprintf(&buf, "%s", green(to.Patch())) 61 | same = false 62 | } 63 | if to.Prerelease() != "" { 64 | if from.Prerelease() == to.Prerelease() && same { 65 | fmt.Fprintf(&buf, "-%s", to.Prerelease()) 66 | } else { 67 | fmt.Fprintf(&buf, "-%s", green(to.Prerelease())) 68 | } 69 | } 70 | if to.Metadata() != "" { 71 | fmt.Fprintf(&buf, "%s%s", green("+"), green(to.Metadata())) 72 | } 73 | return buf.String() 74 | } 75 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # go-mod-upgrade 2 | 3 | [![Build Status](https://github.com/oligot/go-mod-upgrade/actions/workflows/go.yaml/badge.svg)](https://github.com/oligot/go-mod-upgrade/actions/workflows/go.yaml) 4 | [![License](https://img.shields.io/github/license/oligot/go-mod-upgrade)](/license) 5 | [![Release](https://img.shields.io/github/v/release/oligot/go-mod-upgrade.svg)](https://github.com/oligot/go-mod-upgrade/releases/latest) 6 | 7 | > Update outdated Go dependencies interactively 8 | 9 | ![Screenshot](screenshot.png) 10 | 11 | Note that only patch and minor updates are supported for now. 12 | 13 | ## Why 14 | 15 | The Go wiki has a great section on [How to Upgrade and Downgrade Dependencies](https://go.dev/wiki/Modules#how-to-upgrade-and-downgrade-dependencies). 16 | One can run the command 17 | ```bash 18 | go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}: {{.Version}} -> {{.Update.Version}}{{end}}' -m all 2> /dev/null 19 | ``` 20 | to view available upgrades for direct dependencies. 21 | Unfortunately, the output is not actionable, i.e. we can't easily use it to update multiple dependencies. 22 | 23 | This tool is an attempt to make it easier to update multiple dependencies interactively. 24 | This is similar to [yarn upgrade-interactive](https://legacy.yarnpkg.com/en/docs/cli/upgrade-interactive/), but for Go. 25 | 26 | ## Install 27 | 28 | Pre-compiled binaries for Windows, OS X and Linux are available in the [releases page](https://github.com/oligot/go-mod-upgrade/releases). 29 | 30 | Alternatively, with the Go toolchain, you can do 31 | 32 | ``` 33 | go install github.com/oligot/go-mod-upgrade@latest 34 | ``` 35 | 36 | ## Usage 37 | 38 | In a Go project which uses modules, you can now run 39 | ``` 40 | go-mod-upgrade 41 | ``` 42 | 43 | ### List available upgrades 44 | 45 | To see what upgrades are available without any interactivity, use the `--list` flag: 46 | ``` 47 | go-mod-upgrade --list 48 | ``` 49 | 50 | This will display all available module upgrades using the same color coding as the interactive mode, making it perfect for CI/CD pipelines or when you just want to check what's available. 51 | 52 | Colors in module names help identify the update type: 53 | * yellow for a minor update 54 | * green for a patch update 55 | * red for a prerelease update 56 | 57 | Additional options can be specified via the CLI global options: 58 | 59 | ``` 60 | GLOBAL OPTIONS: 61 | --pagesize value, -p value Specify page size (default: 10) 62 | --force, -f Force update all modules in non-interactive mode (default: false) 63 | --list, -l List available module upgrades without interactivity (default: false) 64 | --verbose, -v Verbose mode (default: false) 65 | --hook value Hook to execute for each updated module 66 | --ignore value, -i value Ignore modules matching the given regular expression 67 | --help, -h show help (default: false) 68 | --version print the version (default: false) 69 | ``` 70 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "runtime/debug" 9 | 10 | "github.com/apex/log" 11 | logcli "github.com/apex/log/handlers/cli" 12 | "github.com/urfave/cli/v2" 13 | 14 | "github.com/oligot/go-mod-upgrade/internal/app" 15 | ) 16 | 17 | var ( 18 | // Variables populated during the compilation phase 19 | version = "dev" 20 | commit = "" 21 | date = "" 22 | builtBy = "" 23 | ) 24 | 25 | func versionPrinter(c *cli.Context) { 26 | version := c.App.Version 27 | if commit != "" { 28 | version = fmt.Sprintf("%s\ncommit: %s", version, commit) 29 | } 30 | if date != "" { 31 | version = fmt.Sprintf("%s\nbuild at: %s", version, date) 32 | } 33 | if builtBy != "" { 34 | version = fmt.Sprintf("%s\nbuilt by: %s", version, builtBy) 35 | } 36 | if info, ok := debug.ReadBuildInfo(); ok { 37 | version = fmt.Sprintf("%s\nmodule version: %s", version, info.Main.Version) 38 | } 39 | fmt.Printf( 40 | "%s version %s\n", 41 | c.App.Name, 42 | version, 43 | ) 44 | } 45 | 46 | func main() { 47 | var ( 48 | app = &app.AppEnv{} 49 | ) 50 | 51 | log.SetHandler(logcli.Default) 52 | 53 | cli.VersionFlag = &cli.BoolFlag{ 54 | Name: "version", 55 | Usage: "print the version", 56 | } 57 | cli.VersionPrinter = versionPrinter 58 | 59 | cliapp := &cli.App{ 60 | Name: "go-mod-upgrade", 61 | Usage: "Update outdated Go dependencies interactively", 62 | Version: version, 63 | Flags: []cli.Flag{ 64 | &cli.IntFlag{ 65 | Name: "pagesize", 66 | Aliases: []string{"p"}, 67 | Value: 10, 68 | Usage: "Specify page size", 69 | Destination: &app.PageSize, 70 | }, 71 | &cli.BoolFlag{ 72 | Name: "force", 73 | Aliases: []string{"f"}, 74 | Value: false, 75 | Usage: "Force update all modules in non-interactive mode", 76 | Destination: &app.Force, 77 | }, 78 | &cli.BoolFlag{ 79 | Name: "list", 80 | Aliases: []string{"l"}, 81 | Value: false, 82 | Usage: "List available module upgrades without interactivity", 83 | Destination: &app.List, 84 | }, 85 | &cli.BoolFlag{ 86 | Name: "verbose", 87 | Aliases: []string{"v"}, 88 | Value: false, 89 | Usage: "Verbose mode", 90 | Destination: &app.Verbose, 91 | }, 92 | &cli.PathFlag{ 93 | Name: "hook", 94 | Usage: "Hook to execute for each updated module", 95 | Destination: &app.Hook, 96 | }, 97 | &cli.StringSliceFlag{ 98 | Name: "ignore", 99 | Aliases: []string{"i"}, 100 | Usage: "Ignore modules matching the given regular expression", 101 | Destination: &app.Ignore, 102 | }, 103 | }, 104 | Action: func(c *cli.Context) error { 105 | return app.Run() 106 | }, 107 | UseShortOptionHandling: true, 108 | EnableBashCompletion: true, 109 | } 110 | 111 | err := cliapp.Run(os.Args) 112 | if err != nil { 113 | logger := log.WithError(err) 114 | var e *exec.ExitError 115 | if errors.As(err, &e) { 116 | logger = logger.WithField("stderr", string(e.Stderr)) 117 | } 118 | logger.Error("upgrade failed") 119 | os.Exit(1) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | # niv: no_update 3 | 4 | let 5 | 6 | # 7 | # The fetchers. fetch_ fetches specs of type . 8 | # 9 | 10 | fetch_file = pkgs: name: spec: 11 | let 12 | name' = sanitizeName name + "-src"; 13 | in 14 | if spec.builtin or true then 15 | builtins_fetchurl { inherit (spec) url sha256; name = name'; } 16 | else 17 | pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 18 | 19 | fetch_tarball = pkgs: name: spec: 20 | let 21 | name' = sanitizeName name + "-src"; 22 | in 23 | if spec.builtin or true then 24 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 25 | else 26 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 27 | 28 | fetch_git = name: spec: 29 | let 30 | ref = 31 | if spec ? ref then spec.ref else 32 | if spec ? branch then "refs/heads/${spec.branch}" else 33 | if spec ? tag then "refs/tags/${spec.tag}" else 34 | abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; 35 | submodules = if spec ? submodules then spec.submodules else false; 36 | submoduleArg = 37 | let 38 | nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; 39 | emptyArgWithWarning = 40 | if submodules == true 41 | then 42 | builtins.trace 43 | ( 44 | "The niv input \"${name}\" uses submodules " 45 | + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " 46 | + "does not support them" 47 | ) 48 | {} 49 | else {}; 50 | in 51 | if nixSupportsSubmodules 52 | then { inherit submodules; } 53 | else emptyArgWithWarning; 54 | in 55 | builtins.fetchGit 56 | ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); 57 | 58 | fetch_local = spec: spec.path; 59 | 60 | fetch_builtin-tarball = name: throw 61 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 62 | $ niv modify ${name} -a type=tarball -a builtin=true''; 63 | 64 | fetch_builtin-url = name: throw 65 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 66 | $ niv modify ${name} -a type=file -a builtin=true''; 67 | 68 | # 69 | # Various helpers 70 | # 71 | 72 | # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 73 | sanitizeName = name: 74 | ( 75 | concatMapStrings (s: if builtins.isList s then "-" else s) 76 | ( 77 | builtins.split "[^[:alnum:]+._?=-]+" 78 | ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 79 | ) 80 | ); 81 | 82 | # The set of packages used when specs are fetched using non-builtins. 83 | mkPkgs = sources: system: 84 | let 85 | sourcesNixpkgs = 86 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 87 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 88 | hasThisAsNixpkgsPath = == ./.; 89 | in 90 | if builtins.hasAttr "nixpkgs" sources 91 | then sourcesNixpkgs 92 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 93 | import {} 94 | else 95 | abort 96 | '' 97 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 98 | add a package called "nixpkgs" to your sources.json. 99 | ''; 100 | 101 | # The actual fetching function. 102 | fetch = pkgs: name: spec: 103 | 104 | if ! builtins.hasAttr "type" spec then 105 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 106 | else if spec.type == "file" then fetch_file pkgs name spec 107 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 108 | else if spec.type == "git" then fetch_git name spec 109 | else if spec.type == "local" then fetch_local spec 110 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 111 | else if spec.type == "builtin-url" then fetch_builtin-url name 112 | else 113 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 114 | 115 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 116 | # the path directly as opposed to the fetched source. 117 | replace = name: drv: 118 | let 119 | saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; 120 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 121 | in 122 | if ersatz == "" then drv else 123 | # this turns the string into an actual Nix path (for both absolute and 124 | # relative paths) 125 | if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; 126 | 127 | # Ports of functions for older nix versions 128 | 129 | # a Nix version of mapAttrs if the built-in doesn't exist 130 | mapAttrs = builtins.mapAttrs or ( 131 | f: set: with builtins; 132 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 133 | ); 134 | 135 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 136 | range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); 137 | 138 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 139 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 140 | 141 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 142 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 143 | concatMapStrings = f: list: concatStrings (map f list); 144 | concatStrings = builtins.concatStringsSep ""; 145 | 146 | # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 147 | optionalAttrs = cond: as: if cond then as else {}; 148 | 149 | # fetchTarball version that is compatible between all the versions of Nix 150 | builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 151 | let 152 | inherit (builtins) lessThan nixVersion fetchTarball; 153 | in 154 | if lessThan nixVersion "1.12" then 155 | fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 156 | else 157 | fetchTarball attrs; 158 | 159 | # fetchurl version that is compatible between all the versions of Nix 160 | builtins_fetchurl = { url, name ? null, sha256 }@attrs: 161 | let 162 | inherit (builtins) lessThan nixVersion fetchurl; 163 | in 164 | if lessThan nixVersion "1.12" then 165 | fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 166 | else 167 | fetchurl attrs; 168 | 169 | # Create the final "sources" from the config 170 | mkSources = config: 171 | mapAttrs ( 172 | name: spec: 173 | if builtins.hasAttr "outPath" spec 174 | then abort 175 | "The values in sources.json should not have an 'outPath' attribute" 176 | else 177 | spec // { outPath = replace name (fetch config.pkgs name spec); } 178 | ) config.sources; 179 | 180 | # The "config" used by the fetchers 181 | mkConfig = 182 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 183 | , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) 184 | , system ? builtins.currentSystem 185 | , pkgs ? mkPkgs sources system 186 | }: rec { 187 | # The sources, i.e. the attribute set of spec name to spec 188 | inherit sources; 189 | 190 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 191 | inherit pkgs; 192 | }; 193 | 194 | in 195 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 196 | -------------------------------------------------------------------------------- /internal/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "regexp" 9 | "strings" 10 | "time" 11 | 12 | "github.com/AlecAivazis/survey/v2" 13 | term "github.com/AlecAivazis/survey/v2/terminal" 14 | "github.com/Masterminds/semver/v3" 15 | "github.com/apex/log" 16 | "github.com/briandowns/spinner" 17 | "github.com/fatih/color" 18 | "github.com/urfave/cli/v2" 19 | "golang.org/x/mod/modfile" 20 | 21 | "github.com/oligot/go-mod-upgrade/internal/module" 22 | ) 23 | 24 | func max(x, y int) int { 25 | if x > y { 26 | return x 27 | } 28 | return y 29 | } 30 | 31 | // MultiSelect that doesn't show the answer 32 | // It just reset the prompt and the answers are shown afterwards 33 | type MultiSelect struct { 34 | survey.MultiSelect 35 | } 36 | 37 | func (m MultiSelect) Cleanup(config *survey.PromptConfig, val interface{}) error { 38 | return m.Render("", nil) 39 | } 40 | 41 | type AppEnv struct { 42 | Verbose bool 43 | Force bool 44 | List bool 45 | PageSize int 46 | Hook string 47 | Ignore cli.StringSlice 48 | } 49 | 50 | func (app *AppEnv) Run() error { 51 | if app.Verbose { 52 | log.SetLevel(log.DebugLevel) 53 | } 54 | var paths []string 55 | gw, err := exec.Command("go", "env", "GOWORK").Output() 56 | if err != nil { 57 | return err 58 | } 59 | gowork := strings.TrimSpace(string(gw)) 60 | if gowork == "" || gowork == "off" { 61 | cwd, err := os.Getwd() 62 | if err != nil { 63 | return err 64 | } 65 | paths = append(paths, cwd) 66 | } else { 67 | log.WithField("gowork", gowork).Info("Workspace mode") 68 | content, err := os.ReadFile(gowork) 69 | if err != nil { 70 | return err 71 | } 72 | work, err := modfile.ParseWork("go.work", content, nil) 73 | if err != nil { 74 | return err 75 | } 76 | for _, use := range work.Use { 77 | if use != nil { 78 | paths = append(paths, use.Path) 79 | } 80 | } 81 | } 82 | 83 | for _, path := range paths { 84 | cwd, err := os.Getwd() 85 | if err != nil { 86 | return err 87 | } 88 | dir := path 89 | if !filepath.IsAbs(path) { 90 | dir = filepath.Join(filepath.Dir(gowork), path) 91 | } 92 | log.WithField("dir", dir).Info("Using directory") 93 | if err := os.Chdir(dir); err != nil { 94 | return err 95 | } 96 | modules, err := discoverModules(app.Ignore.Value()) 97 | if err != nil { 98 | return err 99 | } 100 | supported, err := toolsSupported() 101 | if err != nil { 102 | return err 103 | } 104 | log.WithFields(log.Fields{ 105 | "supported": supported, 106 | }).Debug("Tool support") 107 | if supported { 108 | toolModules, err := discoverTools(app.Ignore.Value()) 109 | if err != nil { 110 | return err 111 | } 112 | modules = append(modules, toolModules...) 113 | } 114 | if len(modules) > 0 { 115 | if app.List { 116 | listModules(modules) 117 | } else if app.Force { 118 | log.Debug("Update all modules in non-interactive mode...") 119 | update(modules, app.Hook) 120 | } else { 121 | modules = choose(modules, app.PageSize) 122 | update(modules, app.Hook) 123 | } 124 | } else { 125 | fmt.Println("All modules are up to date") 126 | } 127 | if err := os.Chdir(cwd); err != nil { 128 | return err 129 | } 130 | } 131 | return nil 132 | } 133 | 134 | func discoverModules(ignoreNames []string) ([]module.Module, error) { 135 | s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) 136 | if err := s.Color("yellow"); err != nil { 137 | return nil, err 138 | } 139 | s.Suffix = " Discovering modules..." 140 | s.Start() 141 | 142 | args := []string{ 143 | "list", 144 | "-u", 145 | "-mod=readonly", 146 | "-f", 147 | "'{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}: {{.Version}} -> {{.Update.Version}}{{end}}'", 148 | "-m", 149 | "all", 150 | } 151 | 152 | cmd := exec.Command("go", args...) 153 | // Disable Go workspace mode, otherwise this can cause trouble 154 | // See issue https://github.com/oligot/go-mod-upgrade/issues/35 155 | cmd.Env = append(os.Environ(), "GOWORK=off") 156 | list, err := cmd.Output() 157 | s.Stop() 158 | 159 | // Clear line 160 | fmt.Printf("\r%s\r", strings.Repeat(" ", len(s.Suffix)+1)) 161 | 162 | if err != nil { 163 | return nil, fmt.Errorf("error running go command to discover modules: %w", err) 164 | } 165 | 166 | split := strings.Split(string(list), "\n") 167 | modules := []module.Module{} 168 | re := regexp.MustCompile(`'(.+): (.+) -> (.+)'`) 169 | for _, x := range split { 170 | if x != "''" && x != "" { 171 | matched := re.FindStringSubmatch(x) 172 | if len(matched) < 4 { 173 | return nil, fmt.Errorf("couldn't parse module %s", x) 174 | } 175 | name, from, to := matched[1], matched[2], matched[3] 176 | log.WithFields(log.Fields{ 177 | "name": name, 178 | "from": from, 179 | "to": to, 180 | }).Debug("Found module") 181 | if shouldIgnore(name, from, to, ignoreNames) { 182 | continue 183 | } 184 | fromversion, err := semver.NewVersion(from) 185 | if err != nil { 186 | return nil, err 187 | } 188 | toversion, err := semver.NewVersion(to) 189 | if err != nil { 190 | return nil, err 191 | } 192 | d := module.Module{ 193 | Name: name, 194 | From: fromversion, 195 | To: toversion, 196 | } 197 | modules = append(modules, d) 198 | } 199 | } 200 | return modules, nil 201 | } 202 | 203 | func discoverTools(ignoreNames []string) ([]module.Module, error) { 204 | 205 | s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) 206 | if err := s.Color("yellow"); err != nil { 207 | return nil, err 208 | } 209 | s.Suffix = " Discovering tool modules..." 210 | s.Start() 211 | 212 | toolsArgs := []string{ 213 | "list", 214 | "-f", 215 | "{{if .Module}}{{.Module.Path}} {{.Module.Version}}{{end}}", 216 | "tool", 217 | } 218 | cmd := exec.Command("go", toolsArgs...) 219 | cmd.Env = append(os.Environ(), "GOWORK=off") 220 | toolsOutput, err := cmd.Output() 221 | 222 | s.Stop() 223 | fmt.Printf("\r%s\r", strings.Repeat(" ", len(s.Suffix)+1)) 224 | 225 | if err != nil { 226 | if strings.Contains(err.Error(), "matched no packages") { 227 | return []module.Module{}, nil 228 | } 229 | log.WithFields(log.Fields{ 230 | "error": err, 231 | "args": cmd.Args, 232 | }).Error("error listing tools") 233 | return nil, fmt.Errorf("error listing tools: %w", err) 234 | } 235 | 236 | var modules []module.Module 237 | tools := strings.Split(strings.TrimSpace(string(toolsOutput)), "\n") 238 | for _, tool := range tools { 239 | if tool == "" { 240 | continue 241 | } 242 | 243 | parts := strings.Fields(tool) 244 | if len(parts) == 1 { 245 | continue // local tool 246 | } 247 | if len(parts) != 2 { 248 | return nil, fmt.Errorf("invalid tool format: %s", tool) 249 | } 250 | toolPath, currentVersion := parts[0], parts[1] 251 | 252 | // Check for updates 253 | updateArgs := []string{ 254 | "list", 255 | "-m", 256 | "-f", 257 | "{{if .Update}}{{.Update.Version}}{{end}}", 258 | "-u", 259 | toolPath, 260 | } 261 | updateCmd := exec.Command("go", updateArgs...) 262 | updateCmd.Env = append(os.Environ(), "GOWORK=off") 263 | if updateOutput, err := updateCmd.Output(); err == nil { 264 | newVersion := strings.TrimSpace(string(updateOutput)) 265 | if newVersion != "" && newVersion != currentVersion { 266 | fromVersion, err := semver.NewVersion(currentVersion) 267 | if err != nil { 268 | return nil, fmt.Errorf("invalid tool version: %s -> %s: %w", toolPath, currentVersion, err) 269 | } 270 | toVersion, err := semver.NewVersion(newVersion) 271 | if err != nil { 272 | return nil, fmt.Errorf("invalid tool update version: %s -> %s: %w", toolPath, newVersion, err) 273 | } 274 | log.WithFields(log.Fields{ 275 | "tool": toolPath, 276 | "from": currentVersion, 277 | "to": newVersion, 278 | }).Debug("Found tool module update available") 279 | if shouldIgnore(toolPath, currentVersion, newVersion, ignoreNames) { 280 | continue 281 | } 282 | modules = append(modules, module.Module{ 283 | Name: toolPath, 284 | From: fromVersion, 285 | To: toVersion, 286 | }) 287 | } 288 | } 289 | } 290 | 291 | return modules, nil 292 | } 293 | 294 | func toolsSupported() (bool, error) { 295 | gv, err := exec.Command("go", "version").Output() 296 | if err != nil { 297 | return false, err 298 | } 299 | 300 | version := strings.TrimSpace(string(gv)) 301 | re := regexp.MustCompile(`go version go([\d\.]+)(rc.+)?`) 302 | matched := re.FindStringSubmatch(version) 303 | if len(matched) < 2 { 304 | return false, fmt.Errorf("couldn't parse go version %s", version) 305 | } 306 | 307 | goversion, err := semver.NewVersion(matched[1]) 308 | if err != nil { 309 | return false, err 310 | } 311 | log.WithFields(log.Fields{ 312 | "major": goversion.Major(), 313 | "minor": goversion.Minor(), 314 | }).Debug("Go version") 315 | if goversion.Major() >= 1 && goversion.Minor() >= 24 { 316 | return true, nil 317 | } 318 | return false, nil 319 | } 320 | 321 | func shouldIgnore(name, from, to string, ignoreNames []string) bool { 322 | for _, ig := range ignoreNames { 323 | if strings.Contains(name, ig) { 324 | c := color.New(color.FgYellow).SprintFunc() 325 | log.WithFields(log.Fields{ 326 | "name": name, 327 | "from": from, 328 | "to": to, 329 | }).Debug(c("Ignore module")) 330 | return true 331 | } 332 | } 333 | return false 334 | } 335 | 336 | func listModules(modules []module.Module) { 337 | maxName := 0 338 | maxFrom := 0 339 | for _, x := range modules { 340 | maxName = max(maxName, len(x.Name)) 341 | maxFrom = max(maxFrom, len(x.From.String())) 342 | } 343 | for _, x := range modules { 344 | from := x.FormatFrom(maxFrom) 345 | _, err := fmt.Fprintf(color.Output, "%s %s -> %s\n", x.FormatName(maxName), from, x.FormatTo()) 346 | if err != nil { 347 | log.WithFields(log.Fields{ 348 | "error": err, 349 | "name": x.Name, 350 | }).Error("Error while listing module") 351 | } 352 | } 353 | } 354 | 355 | func choose(modules []module.Module, pageSize int) []module.Module { 356 | maxName := 0 357 | maxFrom := 0 358 | for _, x := range modules { 359 | maxName = max(maxName, len(x.Name)) 360 | maxFrom = max(maxFrom, len(x.From.String())) 361 | } 362 | options := []string{} 363 | for _, x := range modules { 364 | from := x.FormatFrom(maxFrom) 365 | option := fmt.Sprintf("%s %s -> %s", x.FormatName(maxName), from, x.FormatTo()) 366 | options = append(options, option) 367 | } 368 | prompt := &MultiSelect{ 369 | survey.MultiSelect{ 370 | Message: "Choose which modules to update", 371 | Options: options, 372 | PageSize: pageSize, 373 | }, 374 | } 375 | choice := []int{} 376 | err := survey.AskOne(prompt, &choice) 377 | if err == term.InterruptErr { 378 | log.Info("Bye") 379 | os.Exit(0) 380 | } else if err != nil { 381 | log.WithError(err).Error("Choose failed") 382 | os.Exit(1) 383 | } 384 | updates := []module.Module{} 385 | for _, x := range choice { 386 | updates = append(updates, modules[x]) 387 | } 388 | return updates 389 | } 390 | 391 | func update(modules []module.Module, hook string) { 392 | for _, x := range modules { 393 | _, err := fmt.Fprintf(color.Output, "Updating %s to version %s...\n", x.FormatName(len(x.Name)), x.FormatTo()) 394 | if err != nil { 395 | log.WithFields(log.Fields{ 396 | "error": err, 397 | "name": x.Name, 398 | }).Error("Error while updating module") 399 | } 400 | out, err := exec.Command("go", "get", "-d", x.Name).CombinedOutput() 401 | if err != nil { 402 | log.WithFields(log.Fields{ 403 | "error": err, 404 | "name": x.Name, 405 | "out": string(out), 406 | }).Error("Error while updating module") 407 | } 408 | if hook != "" { 409 | out, err := exec.Command( 410 | hook, 411 | x.Name, 412 | x.From.String(), 413 | x.To.String(), 414 | ).CombinedOutput() 415 | if err != nil { 416 | log.WithFields(log.Fields{ 417 | "error": err, 418 | "hook": hook, 419 | "out": string(out), 420 | }).Error("Error while executing hook") 421 | os.Exit(1) 422 | } 423 | log.Info(string(out)) 424 | } 425 | } 426 | } 427 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= 2 | github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= 3 | github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= 4 | github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 5 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= 6 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= 7 | github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0= 8 | github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA= 9 | github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= 10 | github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= 11 | github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= 12 | github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 13 | github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= 14 | github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w= 15 | github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM= 16 | github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= 17 | github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 18 | github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= 19 | github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 24 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 25 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 26 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 27 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 28 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 29 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 30 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 31 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= 32 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= 33 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 34 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 35 | github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= 36 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 37 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 38 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 39 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 40 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 41 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 42 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 43 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 44 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 45 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 46 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 47 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 48 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 49 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 50 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 51 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= 52 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 53 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 54 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 55 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 56 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 57 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 58 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 59 | github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 60 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 61 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 62 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 63 | github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= 64 | github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= 65 | github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= 66 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 67 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 68 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 69 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 70 | github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= 71 | github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= 72 | github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= 73 | github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj52Uc= 74 | github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= 75 | github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= 76 | github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= 77 | github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= 78 | github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= 79 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= 80 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= 81 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 82 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 83 | golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 84 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 85 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 86 | golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= 87 | golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= 88 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 89 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 90 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 91 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 92 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 93 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 94 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 95 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 96 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 97 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 98 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 99 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 100 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 101 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 102 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 104 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 107 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 108 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 109 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 110 | golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= 111 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 112 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 113 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 114 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 115 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 116 | golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= 117 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 118 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 119 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 120 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 121 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 122 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 123 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 124 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 125 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 126 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 127 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 128 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 129 | gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 130 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 131 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 132 | --------------------------------------------------------------------------------