From 1476137e794e95f43d36a7531d59a082d640ca8f Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Tue, 15 Sep 2026 17:29:49 +0530 Subject: [PATCH 1/3] Show help for a bare ts dev proxy invocation Signed-off-by: dhruv8sh --- .../src/commands/dev/mod.rs | 2 +- .../src/commands/dev/proxy/config.rs | 11 ++-------- .../src/commands/dev/proxy/mod.rs | 1 + crates/trusted-server-cli/src/run.rs | 21 +++++++++++++++++++ 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/crates/trusted-server-cli/src/commands/dev/mod.rs b/crates/trusted-server-cli/src/commands/dev/mod.rs index 7a61d769b..822d99fd1 100644 --- a/crates/trusted-server-cli/src/commands/dev/mod.rs +++ b/crates/trusted-server-cli/src/commands/dev/mod.rs @@ -31,6 +31,6 @@ pub enum DevCommand { pub fn run(command: DevCommand) -> Result<(), String> { match command { #[cfg(target_os = "macos")] - DevCommand::Proxy(args) => proxy::run(&args).map_err(|report| format!("{report:?}")), + DevCommand::Proxy(args) => proxy::run(&args).map_err(|report| format!("{report:#}")), } } diff --git a/crates/trusted-server-cli/src/commands/dev/proxy/config.rs b/crates/trusted-server-cli/src/commands/dev/proxy/config.rs index d831735c2..9d0ef2380 100644 --- a/crates/trusted-server-cli/src/commands/dev/proxy/config.rs +++ b/crates/trusted-server-cli/src/commands/dev/proxy/config.rs @@ -359,14 +359,7 @@ mod tests { }; fn base_args() -> crate::commands::dev::proxy::ProxyArgs { - // Construct via clap so defaults match the real surface. - use clap::Parser; - #[derive(clap::Parser)] - struct W { - #[command(flatten)] - a: crate::commands::dev::proxy::ProxyArgs, - } - W::parse_from(["ts"]).a + parse_args(&["ts", "--listen", "127.0.0.1:18080"]) } fn parse_args(argv: &[&str]) -> crate::commands::dev::proxy::ProxyArgs { @@ -382,7 +375,7 @@ mod tests { #[test] fn clap_parses_rewrite_host_as_a_bool() { assert!( - !parse_args(&["ts"]).rewrite_host, + !parse_args(&["ts", "--listen", "127.0.0.1:18080"]).rewrite_host, "absent --rewrite-host is false" ); assert!( diff --git a/crates/trusted-server-cli/src/commands/dev/proxy/mod.rs b/crates/trusted-server-cli/src/commands/dev/proxy/mod.rs index 1bebd872d..6b6d2b748 100644 --- a/crates/trusted-server-cli/src/commands/dev/proxy/mod.rs +++ b/crates/trusted-server-cli/src/commands/dev/proxy/mod.rs @@ -78,6 +78,7 @@ async fn finish_interrupted_run( /// `ts dev proxy [OPTIONS]` — see the design spec §4. #[derive(Debug, clap::Args)] +#[command(arg_required_else_help = true)] pub struct ProxyArgs { /// Rewrite rule `FROM=TO` (repeatable). #[arg(long = "map", value_name = "FROM=TO")] diff --git a/crates/trusted-server-cli/src/run.rs b/crates/trusted-server-cli/src/run.rs index 13009d448..e5079ebb8 100644 --- a/crates/trusted-server-cli/src/run.rs +++ b/crates/trusted-server-cli/src/run.rs @@ -678,4 +678,25 @@ mod tests { "error should explain unsupported option" ); } + + #[test] + #[cfg(target_os = "macos")] + fn dev_proxy_bare_invocation_shows_help_before_running() { + let error = Args::try_parse_from(["ts", "dev", "proxy"]) + .expect_err("a bare `ts dev proxy` should short-circuit to help, not run"); + assert_eq!( + error.kind(), + clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand, + "should print help instead of touching system proxy state or attempting sudo" + ); + parse(&["ts", "dev", "proxy", "ca", "path"]); + } + + #[test] + #[cfg(target_os = "macos")] + fn dev_proxy_partial_rule_parses_instead_of_showing_help() { + // An explicit but incomplete rule (`--from` with no `--to`) must reach + // `run` and surface the concise no-rule error there, not clap help. + parse(&["ts", "dev", "proxy", "--from", "a.example.com"]); + } } From ae1d7c6a3d98cf8e521fe6e2efb2acd42a378caa Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Tue, 15 Sep 2026 19:59:10 +0530 Subject: [PATCH 2/3] Address PR review: preserve CA error attachments, fix test process-exit trap, dedupe listen default Signed-off-by: dhruv8sh --- crates/trusted-server-cli/src/commands/dev/mod.rs | 2 +- .../src/commands/dev/proxy/config.rs | 10 +++++++--- .../trusted-server-cli/src/commands/dev/proxy/mod.rs | 6 +++++- crates/trusted-server-cli/src/run.rs | 9 ++++++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/crates/trusted-server-cli/src/commands/dev/mod.rs b/crates/trusted-server-cli/src/commands/dev/mod.rs index 822d99fd1..7a61d769b 100644 --- a/crates/trusted-server-cli/src/commands/dev/mod.rs +++ b/crates/trusted-server-cli/src/commands/dev/mod.rs @@ -31,6 +31,6 @@ pub enum DevCommand { pub fn run(command: DevCommand) -> Result<(), String> { match command { #[cfg(target_os = "macos")] - DevCommand::Proxy(args) => proxy::run(&args).map_err(|report| format!("{report:#}")), + DevCommand::Proxy(args) => proxy::run(&args).map_err(|report| format!("{report:?}")), } } diff --git a/crates/trusted-server-cli/src/commands/dev/proxy/config.rs b/crates/trusted-server-cli/src/commands/dev/proxy/config.rs index 9d0ef2380..0b536808f 100644 --- a/crates/trusted-server-cli/src/commands/dev/proxy/config.rs +++ b/crates/trusted-server-cli/src/commands/dev/proxy/config.rs @@ -359,7 +359,11 @@ mod tests { }; fn base_args() -> crate::commands::dev::proxy::ProxyArgs { - parse_args(&["ts", "--listen", "127.0.0.1:18080"]) + parse_args(&[ + "ts", + "--listen", + crate::commands::dev::proxy::DEFAULT_LISTEN, + ]) } fn parse_args(argv: &[&str]) -> crate::commands::dev::proxy::ProxyArgs { @@ -369,13 +373,13 @@ mod tests { #[command(flatten)] a: crate::commands::dev::proxy::ProxyArgs, } - W::parse_from(argv).a + W::try_parse_from(argv).expect("should parse proxy args").a } #[test] fn clap_parses_rewrite_host_as_a_bool() { assert!( - !parse_args(&["ts", "--listen", "127.0.0.1:18080"]).rewrite_host, + !parse_args(&["ts", "--insecure"]).rewrite_host, "absent --rewrite-host is false" ); assert!( diff --git a/crates/trusted-server-cli/src/commands/dev/proxy/mod.rs b/crates/trusted-server-cli/src/commands/dev/proxy/mod.rs index 6b6d2b748..5cece9849 100644 --- a/crates/trusted-server-cli/src/commands/dev/proxy/mod.rs +++ b/crates/trusted-server-cli/src/commands/dev/proxy/mod.rs @@ -76,6 +76,10 @@ async fn finish_interrupted_run( let _ = tokio::time::timeout(std::time::Duration::from_secs(2), drain_manager).await; } +/// Default `--listen` address, shared with the `config` tests so they cannot +/// silently drift from the real default. +pub const DEFAULT_LISTEN: &str = "127.0.0.1:18080"; + /// `ts dev proxy [OPTIONS]` — see the design spec §4. #[derive(Debug, clap::Args)] #[command(arg_required_else_help = true)] @@ -95,7 +99,7 @@ pub struct ProxyArgs { pub to: Option, /// Proxy listen address. Non-loopback requires `--allow-non-loopback`. - #[arg(long, value_name = "ADDR", default_value = "127.0.0.1:18080")] + #[arg(long, value_name = "ADDR", default_value = DEFAULT_LISTEN)] pub listen: String, /// Permit binding a non-loopback `--listen` (disables blind tunnel/forward). diff --git a/crates/trusted-server-cli/src/run.rs b/crates/trusted-server-cli/src/run.rs index e5079ebb8..d6f7c8f3b 100644 --- a/crates/trusted-server-cli/src/run.rs +++ b/crates/trusted-server-cli/src/run.rs @@ -689,7 +689,14 @@ mod tests { clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand, "should print help instead of touching system proxy state or attempting sudo" ); - parse(&["ts", "dev", "proxy", "ca", "path"]); + } + + #[test] + #[cfg(target_os = "macos")] + fn dev_proxy_ca_subcommands_still_parse_under_arg_required_else_help() { + for action in ["path", "install", "uninstall", "regenerate"] { + parse(&["ts", "dev", "proxy", "ca", action]); + } } #[test] From 85ac1ff370431a7f8b61cf07c3ac62507d4089fb Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Tue, 15 Sep 2026 20:23:49 +0530 Subject: [PATCH 3/3] Move test-only clap import to module scope, harden second parse trap Signed-off-by: dhruv8sh --- crates/trusted-server-cli/src/commands/dev/proxy/config.rs | 2 +- crates/trusted-server-cli/tests/support/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/trusted-server-cli/src/commands/dev/proxy/config.rs b/crates/trusted-server-cli/src/commands/dev/proxy/config.rs index 0b536808f..42bae4023 100644 --- a/crates/trusted-server-cli/src/commands/dev/proxy/config.rs +++ b/crates/trusted-server-cli/src/commands/dev/proxy/config.rs @@ -349,6 +349,7 @@ fn resolve_basic_auth(args: &ProxyArgs) -> Result, ConfigError #[cfg(test)] mod tests { + use clap::Parser as _; use hyper::header::HeaderValue; use rustls::pki_types::ServerName; @@ -367,7 +368,6 @@ mod tests { } fn parse_args(argv: &[&str]) -> crate::commands::dev::proxy::ProxyArgs { - use clap::Parser; #[derive(clap::Parser)] struct W { #[command(flatten)] diff --git a/crates/trusted-server-cli/tests/support/mod.rs b/crates/trusted-server-cli/tests/support/mod.rs index 8e8e3e66f..6c464b015 100644 --- a/crates/trusted-server-cli/tests/support/mod.rs +++ b/crates/trusted-server-cli/tests/support/mod.rs @@ -206,7 +206,7 @@ fn resolve(argv: &[&str]) -> config::ResolvedConfig { #[command(flatten)] args: trusted_server_cli::commands::dev::proxy::ProxyArgs, } - let parsed = Wrapper::parse_from(argv); + let parsed = Wrapper::try_parse_from(argv).expect("should parse proxy args"); config::resolve(&parsed.args).expect("should resolve test config") }