diff --git a/cli/commands/help.go b/cli/commands/help.go index 0477d229..67a9e2bf 100644 --- a/cli/commands/help.go +++ b/cli/commands/help.go @@ -44,11 +44,17 @@ func Section(name, desc, help string, opts ...SectionOption) mflags.Command { desc = help } + fs := mflags.NewFlagSet(name) + // Sections declare no flags of their own, but users routinely place global + // flags (e.g. -C/--cluster) before a section name. Tolerate unknown flags so + // the section still renders its sub-command help instead of failing to parse. + fs.AllowUnknownFlags(true) + s := §ion{ name: name, desc: desc, help: help, - fs: mflags.NewFlagSet(name), + fs: fs, } for _, o := range opts { diff --git a/cli/commands/section_help_test.go b/cli/commands/section_help_test.go new file mode 100644 index 00000000..d8f87d5f --- /dev/null +++ b/cli/commands/section_help_test.go @@ -0,0 +1,79 @@ +package commands + +import ( + "bytes" + "io" + "os" + "testing" + + "miren.dev/mflags" + "miren.dev/runtime/pkg/labs" +) + +// TestSectionHelpWithGlobalFlag guards against MIR-1309: a value-taking global +// flag (e.g. -C/--cluster) placed before a section name must still render the +// section's sub-commands rather than falling back to top-level help or a flag +// parse error. +func TestSectionHelpWithGlobalFlag(t *testing.T) { + labs.EnableAll() + + cases := [][]string{ + {"auth"}, + {"auth", "help"}, + {"help", "auth"}, + {"-C", "prod", "auth"}, + {"-C", "prod", "auth", "help"}, + {"-C", "prod", "auth", "--help"}, + } + + for _, args := range cases { + t.Run(argsName(args), func(t *testing.T) { + out := captureDispatch(t, args) + + // The auth section's sub-commands must appear; top-level help would + // instead list unrelated commands like "app". + for _, want := range []string{"generate", "provider", "ci"} { + if !bytes.Contains([]byte(out), []byte(want)) { + t.Errorf("expected auth sub-command %q in output for %v, got:\n%s", want, args, out) + } + } + }) + } +} + +func argsName(args []string) string { + name := "" + for i, a := range args { + if i > 0 { + name += "_" + } + name += a + } + return name +} + +// captureDispatch builds the full dispatcher and runs Execute with the given +// args, returning everything written to stdout. +func captureDispatch(t *testing.T, args []string) string { + t.Helper() + + d := mflags.NewDispatcher("miren") + RegisterAll(d) + + old := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := d.Execute(args) + + w.Close() + os.Stdout = old + + if err != nil { + t.Fatalf("Execute(%v) returned error: %v", args, err) + } + + var buf bytes.Buffer + io.Copy(&buf, r) + return buf.String() +} diff --git a/go.mod b/go.mod index 420a90d7..0691b589 100644 --- a/go.mod +++ b/go.mod @@ -130,7 +130,7 @@ require ( k8s.io/klog/v2 v2.130.1 miren.dev/jsonrpc3/go/jsonrpc3 v0.0.0-20260106052505-c98e2702b093 miren.dev/lbd v0.0.0-20260224020427-8914d8db2233 - miren.dev/mflags v0.0.0-20260518222642-0ca7adc28461 + miren.dev/mflags v0.0.0-20260709231109-a397dcbc98df sigs.k8s.io/knftables v0.0.21 ) diff --git a/go.sum b/go.sum index 40aa2753..87717b36 100644 --- a/go.sum +++ b/go.sum @@ -1989,8 +1989,8 @@ miren.dev/jsonrpc3/go/jsonrpc3 v0.0.0-20260106052505-c98e2702b093 h1:Sd+M5HSUati miren.dev/jsonrpc3/go/jsonrpc3 v0.0.0-20260106052505-c98e2702b093/go.mod h1:B4A3wSzkcSZQw6Y5opU+rk1+1lEuCWIWkAiN7TkltGE= miren.dev/lbd v0.0.0-20260224020427-8914d8db2233 h1:9DxH7Dhnmu7hn1OA2JC5fHpLuVgAqBySws9GLNssLl4= miren.dev/lbd v0.0.0-20260224020427-8914d8db2233/go.mod h1:+x9fy2p45csBnGUJdqxCUmzlUTCipoVDbv6zIapTgDA= -miren.dev/mflags v0.0.0-20260518222642-0ca7adc28461 h1:ETSN/0nBy1Dqpu43INesvPWoipOaC5+pfKzRjngkeO0= -miren.dev/mflags v0.0.0-20260518222642-0ca7adc28461/go.mod h1:G1eQ/upWVdO6BGT6dlh5Yqjt+9ncH5RUAKX6UKi1F9Q= +miren.dev/mflags v0.0.0-20260709231109-a397dcbc98df h1:4fo71OveODliBgU9sv2M/uepiRNAwQPJ7YTPuNffQNc= +miren.dev/mflags v0.0.0-20260709231109-a397dcbc98df/go.mod h1:G1eQ/upWVdO6BGT6dlh5Yqjt+9ncH5RUAKX6UKi1F9Q= modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=