From b8cb33334e0f6c42e520d3b58839c3b0f24d315c Mon Sep 17 00:00:00 2001 From: Evan Phoenix Date: Thu, 9 Jul 2026 16:12:51 -0700 Subject: [PATCH] Show section subcommand help when a global flag precedes the section `miren -C auth help` (and `miren -C auth`) printed top-level help instead of the auth section's subcommands. The mflags interspersed-flag resolver rejected the section match because the section's empty flagset doesn't declare -C; that fix lands in mflags. On this side, sections now tolerate unknown flags so the no-help form parses and renders its subcommands via ErrShowHelp instead of erroring. - cli/commands/help.go: Section flagsets set AllowUnknownFlags(true). - cli/commands/section_help_test.go: regression test via RegisterAll. - go.mod: bump miren.dev/mflags to the resolver fix. Fixes MIR-1309. --- cli/commands/help.go | 8 +++- cli/commands/section_help_test.go | 79 +++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 cli/commands/section_help_test.go diff --git a/cli/commands/help.go b/cli/commands/help.go index 0477d229c..67a9e2bf0 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 000000000..d8f87d5f1 --- /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 420a90d7e..0691b5891 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 40aa27531..87717b36a 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=