Repository navigation
fix(cli): restore a leading -- for 14 passthrough commands #4768
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2498,6 +2498,37 @@ fn pings_telemetry(parsed: &Result<Cli, clap::Error>) -> bool { | |
| ) | ||
| } | ||
|
|
||
| /// clap's `trailing_var_arg` strips a `--` that leads a variant's arguments, so | ||
| /// `rtk tsc -- zzz` would forward `tsc zzz` (#4753). Restoring it here, once, lets each | ||
| /// module place it after the options it injects, the way it places the rest of `args`. | ||
| fn restore_leading_double_dash(command: &mut Commands) { | ||
| match command { | ||
| Commands::Tsc { args } | ||
| | Commands::Prettier { args } | ||
| | Commands::Phpunit { args } | ||
| | Commands::Phpstan { args } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocks merge (item 1): phpstan appends |
||
| | Commands::Pest { args } | ||
| | Commands::Mvn { args } | ||
| | Commands::Rspec { args } | ||
| | Commands::Rubocop { args } | ||
| | Commands::Vitest { args } | ||
| | Commands::Jest { args } => *args = core::args_utils::restore_double_dash(args), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocks merge (item 2): these 10 arms now call |
||
| Commands::Go { | ||
| command: GoCommands::Other(args), | ||
| } | ||
| | Commands::Cargo { | ||
| command: CargoCommands::Other(args), | ||
| } | ||
| | Commands::Docker { | ||
| command: DockerCommands::Other(args), | ||
| } | ||
| | Commands::Kubectl { | ||
| command: KubectlCommands::Other(args), | ||
| } => *args = core::args_utils::restore_double_dash_os(args), | ||
| _ => {} | ||
| } | ||
| } | ||
|
|
||
| fn run_cli() -> Result<i32> { | ||
| let parsed = try_parse_cli_from(std::env::args_os()); | ||
|
|
||
|
|
@@ -2506,7 +2537,7 @@ fn run_cli() -> Result<i32> { | |
| core::telemetry::maybe_ping(); | ||
| } | ||
|
|
||
| let cli = match parsed { | ||
| let mut cli = match parsed { | ||
| Ok(cli) => cli, | ||
| Err(e) => { | ||
| if matches!(e.kind(), ErrorKind::DisplayHelp | ErrorKind::DisplayVersion) { | ||
|
|
@@ -2515,6 +2546,7 @@ fn run_cli() -> Result<i32> { | |
| return run_fallback(e); | ||
| } | ||
| }; | ||
| restore_leading_double_dash(&mut cli.command); | ||
|
|
||
| // Warn if installed hook is outdated/missing (1/day, non-blocking). | ||
| // Skip for Gain (shows its own inline warning), Init/Verify (manage the | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| //! A leading `--` for the passthrough commands of #4753. clap's `trailing_var_arg` strips a | ||
| //! `--` that leads `args` (or an external subcommand), so `rtk tsc -- zzz` forwarded | ||
| //! `tsc zzz` and a path after the separator was read as an option or a subcommand. The | ||
| //! separator has to reach the tool, after any options rtk injects, and a word after it such | ||
| //! as `run` must stay an argument. Stubs each tool on PATH with a script that records its argv. | ||
|
|
||
| #![cfg(unix)] | ||
|
|
||
| use std::fs; | ||
| use std::os::unix::fs::PermissionsExt; | ||
|
|
||
| mod common; | ||
|
|
||
| fn shell_quote(path: &std::path::Path) -> String { | ||
| format!("'{}'", path.display().to_string().replace('\'', "'\\''")) | ||
| } | ||
|
|
||
| /// Runs `rtk <tool> <args>` against a `<tool>` stub and returns the argv the stub received. | ||
| fn tool_argv(tool: &str, args: &[&str]) -> Vec<String> { | ||
| let dir = common::tempdir(); | ||
| let argv_file = dir.path().join("argv.txt"); | ||
| let stub_path = dir.path().join(tool); | ||
|
|
||
| fs::write( | ||
| &stub_path, | ||
| format!( | ||
| "#!/bin/sh\nprintf '%s\\n' \"$@\" > {}\n", | ||
| shell_quote(&argv_file) | ||
| ), | ||
| ) | ||
| .expect("write stub"); | ||
| fs::set_permissions(&stub_path, fs::Permissions::from_mode(0o755)).expect("chmod stub"); | ||
|
|
||
| let path_with_stub = format!( | ||
| "{}:{}", | ||
| dir.path().display(), | ||
| std::env::var("PATH").unwrap_or_default() | ||
| ); | ||
|
|
||
| let out = common::rtk_command() | ||
| .arg(tool) | ||
| .args(args) | ||
| .env("PATH", path_with_stub) | ||
| .env("LC_ALL", "C") | ||
| .current_dir(dir.path()) | ||
| .output() | ||
| .expect("spawn rtk"); | ||
|
|
||
| fs::read_to_string(&argv_file) | ||
| .unwrap_or_else(|e| { | ||
| panic!( | ||
| "read captured argv for {tool}: {e}; rtk stdout={} stderr={}", | ||
| String::from_utf8_lossy(&out.stdout), | ||
| String::from_utf8_lossy(&out.stderr) | ||
| ) | ||
| }) | ||
| .lines() | ||
| .map(str::to_string) | ||
| .collect() | ||
| } | ||
|
|
||
| #[test] | ||
| fn leading_double_dash_reaches_the_tool() { | ||
| let cases: &[(&str, &[&str])] = &[ | ||
| ("tsc", &[]), | ||
| ("prettier", &[]), | ||
| ("go", &[]), | ||
| ("cargo", &[]), | ||
| ("phpunit", &[]), | ||
| ("phpstan", &[]), | ||
| ("mvn", &[]), | ||
| ("kubectl", &[]), | ||
| ("docker", &[]), | ||
| ("rspec", &["--format", "json"]), | ||
| ("rubocop", &["--format", "json"]), | ||
| ("vitest", &["run", "--reporter=json"]), | ||
| ("jest", &["--no-watch", "--json"]), | ||
| ("pest", &["--no-progress"]), | ||
| ]; | ||
| let mut failures = Vec::new(); | ||
| for (tool, injected) in cases { | ||
| let mut expected: Vec<&str> = injected.to_vec(); | ||
| expected.extend(["--", "zzz"]); | ||
| let argv = tool_argv(tool, &["--", "zzz"]); | ||
| if argv != expected { | ||
| failures.push(format!("{tool}: got {argv:?}, want {expected:?}")); | ||
| } | ||
| } | ||
| assert!(failures.is_empty(), "{}", failures.join("\n")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn a_double_dash_after_an_argument_is_forwarded_once() { | ||
| assert_eq!(tool_argv("tsc", &["-p", "--", "zzz"]), ["-p", "--", "zzz"]); | ||
| assert_eq!(tool_argv("go", &["zzz", "--", "zzz"]), ["zzz", "--", "zzz"]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn run_after_double_dash_stays_an_argument() { | ||
| assert_eq!( | ||
| tool_argv("vitest", &["--", "run"]), | ||
| ["run", "--reporter=json", "--", "run"] | ||
| ); | ||
| assert_eq!( | ||
| tool_argv("jest", &["--", "run"]), | ||
| ["--no-watch", "--json", "--", "run"] | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocks merge (item 3): nothing pins this
args_os()read. Withstd::env::args().map(OsString::from).collect()instead, all 5220 tests pass. A test that runs anOtherarm with a non-UTF-8 argument (rtk go foo $'\xff.py', which works on head) and asserts the child's argv bytes and exit code would turn that mutation red.