@@ -135,6 +135,8 @@ enum Commands {
135135 } ,
136136
137137 /// Git commands with compact output
138+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
139+ #[ command( disable_help_flag = true ) ]
138140 Git {
139141 /// Change to directory before executing (like git -C <path>, can be repeated)
140142 #[ arg( short = 'C' , action = clap:: ArgAction :: Append ) ]
@@ -214,6 +216,8 @@ enum Commands {
214216 } ,
215217
216218 /// pnpm commands with ultra-compact output
219+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
220+ #[ command( disable_help_flag = true ) ]
217221 Pnpm {
218222 /// pnpm filter arguments (can be repeated: --filter @app1 --filter @app2)
219223 #[ arg( long, short = 'F' ) ]
@@ -285,24 +289,32 @@ enum Commands {
285289 } ,
286290
287291 /// .NET commands with compact output (build/test/restore/format)
292+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
293+ #[ command( disable_help_flag = true ) ]
288294 Dotnet {
289295 #[ command( subcommand) ]
290296 command : DotnetCommands ,
291297 } ,
292298
293299 /// Docker commands with compact output
300+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
301+ #[ command( disable_help_flag = true ) ]
294302 Docker {
295303 #[ command( subcommand) ]
296304 command : DockerCommands ,
297305 } ,
298306
299307 /// Kubectl commands with compact output
308+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
309+ #[ command( disable_help_flag = true ) ]
300310 Kubectl {
301311 #[ command( subcommand) ]
302312 command : KubectlCommands ,
303313 } ,
304314
305315 /// OpenShift CLI (oc) commands with compact output
316+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
317+ #[ command( disable_help_flag = true ) ]
306318 Oc {
307319 #[ command( subcommand) ]
308320 command : OcCommands ,
@@ -576,6 +588,8 @@ enum Commands {
576588 } ,
577589
578590 /// Cargo commands with compact output
591+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
592+ #[ command( disable_help_flag = true ) ]
579593 Cargo {
580594 #[ command( subcommand) ]
581595 command : CargoCommands ,
@@ -596,6 +610,8 @@ enum Commands {
596610 } ,
597611
598612 /// Bun runtime commands with compact output
613+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
614+ #[ command( disable_help_flag = true ) ]
599615 Bun {
600616 #[ command( subcommand) ]
601617 command : BunCommands ,
@@ -839,24 +855,32 @@ enum Commands {
839855 } ,
840856
841857 /// Deno runtime commands with compact output
858+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
859+ #[ command( disable_help_flag = true ) ]
842860 Deno {
843861 #[ command( subcommand) ]
844862 command : DenoCommands ,
845863 } ,
846864
847865 /// Go commands with compact output
866+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
867+ #[ command( disable_help_flag = true ) ]
848868 Go {
849869 #[ command( subcommand) ]
850870 command : GoCommands ,
851871 } ,
852872
853873 /// SBT (Scala Build Tool) commands with compact output
874+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
875+ #[ command( disable_help_flag = true ) ]
854876 Sbt {
855877 #[ command( subcommand) ]
856878 command : SbtCommands ,
857879 } ,
858880
859881 /// Graphite (gt) stacked PR commands with compact output
882+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
883+ #[ command( disable_help_flag = true ) ]
860884 Gt {
861885 #[ command( subcommand) ]
862886 command : GtCommands ,
@@ -1081,6 +1105,8 @@ enum DockerCommands {
10811105 /// Show container logs (deduplicated)
10821106 Logs { container : String } ,
10831107 /// Docker Compose commands with compact output
1108+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
1109+ #[ command( disable_help_flag = true ) ]
10841110 Compose {
10851111 #[ command( subcommand) ]
10861112 command : ComposeCommands ,
@@ -1384,6 +1410,8 @@ enum BunCommands {
13841410 args : Vec < String > ,
13851411 } ,
13861412 /// Package manager commands (pm ls, etc.)
1413+ // `--help` belongs to the tool: see forward_help_to_wrapped_tools.
1414+ #[ command( disable_help_flag = true ) ]
13871415 Pm {
13881416 #[ command( subcommand) ]
13891417 command : BunPmCommands ,
@@ -1808,24 +1836,38 @@ fn is_native_test_expression(command: &[String]) -> bool {
18081836 }
18091837}
18101838
1811- /// Hand `--help`/`-h` to the wrapped tool on every subcommand that forwards
1812- /// trailing hyphen arguments, recursively.
1839+ /// Hand `--help`/`-h` to the wrapped tool instead of clap, recursively.
1840+ ///
1841+ /// A subcommand that carries a trailing-var-arg positional (`rtk tsc <args>`)
1842+ /// forwards every other flag to the tool; clap's auto help was the one it
1843+ /// kept, so `rtk tsc --help` printed rtk's one-line stub instead of running
1844+ /// tsc, and through the hook that is what `tsc --help` came back as.
1845+ ///
1846+ /// The exact exemption is `RTK_META_COMMANDS`: rtk's own entry points keep
1847+ /// clap's help even when they take a trailing command (`proxy`, `run`,
1848+ /// `rewrite`), because there is no tool to hand it to. Subcommands with
1849+ /// nothing to forward (`gain`, `init`, …) are untouched by construction.
18131850///
1814- /// Those subcommands pass every other flag through to the tool, and clap's
1815- /// auto help was the one it kept for itself: `rtk tsc --help` printed rtk's
1816- /// one-line stub instead of running tsc, and through the hook that is what
1817- /// `tsc --help` came back as. Subcommands with nothing to forward — rtk's own
1818- /// `gain`, `init`, `rewrite`, … — keep clap's help. `Psql` and `Ctest` opted
1819- /// out per-variant before this; the rule covers them and the ~100 siblings
1820- /// that never did.
1851+ /// Parents whose passthrough is an `external_subcommand` arm (`rtk git <any>`)
1852+ /// cannot be told apart here: clap records that arm only while building, after
1853+ /// this pass. Those parents carry `#[command(disable_help_flag = true)]` on
1854+ /// the variant, and `test_every_wrapped_tool_subcommand_forwards_help` builds
1855+ /// the command to hold both groups to the same contract.
18211856fn forward_help_to_wrapped_tools ( cmd : clap:: Command ) -> clap:: Command {
1822- let forwards = cmd. get_positionals ( ) . any ( |a| a. is_trailing_var_arg_set ( ) ) ;
1857+ forward_help_below ( cmd, 0 )
1858+ }
1859+
1860+ /// `depth` 1 is a direct child of `rtk`: the meta-command names are only
1861+ /// rtk's own there (`rtk run` is, `rtk bun run` is bun's).
1862+ fn forward_help_below ( cmd : clap:: Command , depth : usize ) -> clap:: Command {
1863+ let meta = depth == 1 && core:: constants:: RTK_META_COMMANDS . contains ( & cmd. get_name ( ) ) ;
1864+ let forwards = cmd. get_positionals ( ) . any ( |a| a. is_trailing_var_arg_set ( ) ) && !meta;
18231865 let cmd = if forwards {
18241866 cmd. disable_help_flag ( true )
18251867 } else {
18261868 cmd
18271869 } ;
1828- cmd. mut_subcommands ( forward_help_to_wrapped_tools )
1870+ cmd. mut_subcommands ( |sub| forward_help_below ( sub , depth + 1 ) )
18291871}
18301872
18311873/// The clap command `main` parses with: the derived `Cli` after
@@ -1834,8 +1876,10 @@ fn cli_command() -> clap::Command {
18341876 forward_help_to_wrapped_tools ( <Cli as clap:: CommandFactory >:: command ( ) )
18351877}
18361878
1837- /// Parse argv the way `main` does. Tests reach for this rather than
1838- /// `Cli::try_parse_from` when the help-flag routing matters.
1879+ /// Parse argv the way `main` does. `Cli::try_parse_from` is the derived
1880+ /// parser *before* [`forward_help_to_wrapped_tools`], so a test about
1881+ /// `--help`/`-h` on a wrapped tool must go through this or it tests a parser
1882+ /// `main` never runs.
18391883fn parse_cli < I , T > ( args : I ) -> Result < Cli , clap:: Error >
18401884where
18411885 I : IntoIterator < Item = T > ,
@@ -3166,10 +3210,49 @@ mod tests {
31663210 }
31673211 }
31683212
3213+ #[ test]
3214+ fn test_parse_cli_forwards_help_past_an_external_subcommand_parent ( ) {
3215+ // `rtk git --help`: the parent has no trailing positional, only an
3216+ // external-subcommand arm. clap must not answer with rtk's stub: the
3217+ // flag either lands in the external arm or fails the parse, and a
3218+ // parse error routes `run_fallback` to the raw `git --help`.
3219+ match parse_cli ( [ "rtk" , "git" , "--help" ] ) {
3220+ Err ( e) => assert_ne ! ( e. kind( ) , ErrorKind :: DisplayHelp ) ,
3221+ Ok ( Cli {
3222+ command :
3223+ Commands :: Git {
3224+ command : GitCommands :: Other ( args) ,
3225+ ..
3226+ } ,
3227+ ..
3228+ } ) => assert_eq ! ( args, vec![ OsString :: from( "--help" ) ] ) ,
3229+ Ok ( _) => panic ! ( "`--help` parsed as something other than git's external arm" ) ,
3230+ }
3231+
3232+ let cli = parse_cli ( [ "rtk" , "git" , "bisect" , "--help" ] ) . unwrap ( ) ;
3233+ match cli. command {
3234+ Commands :: Git {
3235+ command : GitCommands :: Other ( args) ,
3236+ ..
3237+ } => assert_eq ! (
3238+ args,
3239+ vec![ OsString :: from( "bisect" ) , OsString :: from( "--help" ) ]
3240+ ) ,
3241+ _ => panic ! ( "expected git's external subcommand arm" ) ,
3242+ }
3243+ }
3244+
31693245 #[ test]
31703246 fn test_parse_cli_keeps_help_on_meta_commands ( ) {
3171- // Nothing to forward to: rtk's own help stays.
3172- for argv in [ vec ! [ "rtk" , "--help" ] , vec ! [ "rtk" , "gain" , "--help" ] ] {
3247+ // Nothing to forward to: rtk's own help stays, including on the meta
3248+ // commands that take a trailing command of their own.
3249+ for argv in [
3250+ vec ! [ "rtk" , "--help" ] ,
3251+ vec ! [ "rtk" , "gain" , "--help" ] ,
3252+ vec ! [ "rtk" , "proxy" , "--help" ] ,
3253+ vec ! [ "rtk" , "run" , "--help" ] ,
3254+ vec ! [ "rtk" , "rewrite" , "--help" ] ,
3255+ ] {
31733256 let err = match parse_cli ( argv. clone ( ) ) {
31743257 Err ( e) => e,
31753258 Ok ( _) => panic ! ( "{argv:?}: help must be clap's" ) ,
@@ -3180,23 +3263,34 @@ mod tests {
31803263
31813264 #[ test]
31823265 fn test_every_wrapped_tool_subcommand_forwards_help ( ) {
3183- // Total contract: a subcommand that forwards trailing hyphen args must
3184- // not let clap claim `--help`; one that forwards nothing must keep it.
3185- fn walk ( cmd : & clap:: Command , path : & str , seen : & mut usize ) {
3186- let forwards = cmd. get_positionals ( ) . any ( |a| a. is_trailing_var_arg_set ( ) ) ;
3187- if forwards {
3266+ // Total contract: a subcommand that forwards to a tool (trailing
3267+ // hyphen args or an external subcommand) must not let clap claim
3268+ // `--help`; a meta command or one that forwards nothing must keep it.
3269+ fn walk ( cmd : & clap:: Command , path : & str , depth : usize , seen : & mut usize ) {
3270+ let forwards = cmd. get_positionals ( ) . any ( |a| a. is_trailing_var_arg_set ( ) )
3271+ || cmd. is_allow_external_subcommands_set ( ) ;
3272+ let meta = depth == 1 && core:: constants:: RTK_META_COMMANDS . contains ( & cmd. get_name ( ) ) ;
3273+ if forwards && !meta {
31883274 * seen += 1 ;
31893275 assert ! (
31903276 cmd. is_disable_help_flag_set( ) ,
31913277 "{path} forwards args but clap still owns --help"
31923278 ) ;
3279+ } else if meta {
3280+ assert ! (
3281+ !cmd. is_disable_help_flag_set( ) ,
3282+ "{path} is rtk's own; clap must keep --help"
3283+ ) ;
31933284 }
31943285 for sub in cmd. get_subcommands ( ) {
3195- walk ( sub, & format ! ( "{path} {}" , sub. get_name( ) ) , seen) ;
3286+ walk ( sub, & format ! ( "{path} {}" , sub. get_name( ) ) , depth + 1 , seen) ;
31963287 }
31973288 }
31983289 let mut seen = 0 ;
3199- walk ( & cli_command ( ) , "rtk" , & mut seen) ;
3290+ // Built, so the external-subcommand arms are visible to the walk.
3291+ let mut cmd = cli_command ( ) ;
3292+ cmd. build ( ) ;
3293+ walk ( & cmd, "rtk" , 0 , & mut seen) ;
32003294 assert ! ( seen >= 100 , "expected the wrapped-tool surface, saw {seen}" ) ;
32013295 }
32023296
@@ -3735,7 +3829,7 @@ mod tests {
37353829 #[ test]
37363830 fn test_ctest_help_and_version_passthrough_args ( ) {
37373831 for flag in [ "--help" , "--version" ] {
3738- let cli = Cli :: try_parse_from ( [ "rtk" , "ctest" , flag] ) . unwrap ( ) ;
3832+ let cli = parse_cli ( [ "rtk" , "ctest" , flag] ) . unwrap ( ) ;
37393833 match cli. command {
37403834 Commands :: Ctest { args } => assert_eq ! ( args, vec![ flag] ) ,
37413835 _ => panic ! ( "Expected Ctest command" ) ,
@@ -3926,7 +4020,7 @@ mod tests {
39264020 fn test_rewrite_clap_double_dash_blocks_flag_injection ( ) {
39274021 let injection_inputs = [ "--help" , "-h" , "--version" , "-V" ] ;
39284022 for injected in injection_inputs {
3929- let result = Cli :: try_parse_from ( [ "rtk" , "rewrite" , "--" , injected] ) ;
4023+ let result = parse_cli ( [ "rtk" , "rewrite" , "--" , injected] ) ;
39304024 assert ! (
39314025 result. is_ok( ) ,
39324026 "`rtk rewrite -- {injected:?}` must parse without triggering clap"
0 commit comments