Skip to content

Commit 827fb9e

Browse files
Restrict global version flag handling to root args (#437)
Updates how we detect if we should run `printVersionWithUpdateCheck`. - Before: If `--version` or `-v` appears anywhere in the command, we print version - After: Only print version if `--version` or `-v` appears before any subcommand GitOrigin-RevId: e34ff077c6ff0e8205308ac756d97948de198b40
1 parent f8abc8e commit 827fb9e

2 files changed

Lines changed: 73 additions & 6 deletions

File tree

cmd/root.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import (
44
"errors"
55
"fmt"
66
"os"
7+
"strings"
78

89
tea "github.com/charmbracelet/bubbletea"
910
"github.com/charmbracelet/lipgloss"
1011
"github.com/spf13/cobra"
12+
"github.com/spf13/pflag"
1113

1214
"github.com/render-oss/cli/pkg/cfg"
1315
"github.com/render-oss/cli/pkg/client"
@@ -234,15 +236,51 @@ func printVersionWithUpdateCheck() {
234236
}
235237
}
236238

239+
// isRootVersionRequest reports whether --version / -v appears among the
240+
// root-level arguments, before the first subcommand token. It uses rootFlags to
241+
// skip values consumed by global flags such as `-o text`.
242+
func isRootVersionRequest(args []string, rootFlags *pflag.FlagSet) bool {
243+
for i := 0; i < len(args); i++ {
244+
arg := args[i]
245+
if arg == "--version" || arg == "-v" || strings.HasPrefix(arg, "--version=") {
246+
return true
247+
}
248+
if !strings.HasPrefix(arg, "-") || arg == "--" {
249+
return false
250+
}
251+
if strings.Contains(arg, "=") {
252+
continue
253+
}
254+
255+
var flag *pflag.Flag
256+
if strings.HasPrefix(arg, "--") {
257+
flag = rootFlags.Lookup(strings.TrimPrefix(arg, "--"))
258+
} else {
259+
short := strings.TrimPrefix(arg, "-")
260+
if len(short) != 1 {
261+
return false
262+
}
263+
flag = rootFlags.ShorthandLookup(short)
264+
}
265+
if flag == nil {
266+
return false
267+
}
268+
if flag.Value.Type() != "bool" && flag.NoOptDefVal == "" {
269+
i++
270+
}
271+
}
272+
return false
273+
}
274+
237275
// Execute adds all child commands to the root command and sets flags appropriately.
238276
// This is called by main.main(). It only needs to happen once to the rootCmd.
239277
func Execute() {
240-
// Check if version flag is explicitly requested before Cobra handles it
241-
for _, arg := range os.Args[1:] {
242-
if arg == "--version" || arg == "-v" {
243-
printVersionWithUpdateCheck()
244-
return
245-
}
278+
// Check if version flag is explicitly requested before Cobra handles it.
279+
// Only treat --version / -v as the CLI's global version flag when it
280+
// appears before a subcommand; otherwise let subcommands own their flags.
281+
if isRootVersionRequest(os.Args[1:], rootCmd.PersistentFlags()) {
282+
printVersionWithUpdateCheck()
283+
return
246284
}
247285

248286
if err := SetupCommands(); err != nil {

cmd/root_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,35 @@ func TestSetAnnotationBestEffort(t *testing.T) {
450450
})
451451
}
452452

453+
func TestIsRootVersionRequest(t *testing.T) {
454+
flags := pflag.NewFlagSet("root", pflag.ContinueOnError)
455+
flags.StringP("output", "o", "interactive", "")
456+
flags.Bool(command.ConfirmFlag, false, "")
457+
458+
tests := []struct {
459+
name string
460+
args []string
461+
want bool
462+
}{
463+
{"empty args", nil, false},
464+
{"no version", []string{"services", "list"}, false},
465+
{"bare --version", []string{"--version"}, true},
466+
{"bare -v", []string{"-v"}, true},
467+
{"version after global -o flag", []string{"-o", "text", "--version"}, true},
468+
{"version after --output= flag", []string{"--output=text", "--version"}, true},
469+
{"version after --confirm bool flag", []string{"--confirm", "--version"}, true},
470+
{"version after subcommand should not match", []string{"ea", "pg", "create", "--version", "17"}, false},
471+
{"output alone", []string{"--output=text"}, false},
472+
{"version flag form via equals", []string{"--version=true"}, true},
473+
{"multi-char single-dash arg falls through to cobra", []string{"-output", "text", "--version"}, false},
474+
}
475+
for _, tc := range tests {
476+
t.Run(tc.name, func(t *testing.T) {
477+
require.Equal(t, tc.want, isRootVersionRequest(tc.args, flags))
478+
})
479+
}
480+
}
481+
453482
func stripANSI(input string) string {
454483
ansiEscapePattern := regexp.MustCompile(`\x1b\[[0-9;]*m`)
455484
return ansiEscapePattern.ReplaceAllString(input, "")

0 commit comments

Comments
 (0)