Skip to content

Commit 614ff5c

Browse files
feat(pnpm): add fallback passthrough for unsupported subcommands
Add external_subcommand variant to PnpmCommands enum to handle unsupported pnpm operations by passing them through directly. Changes: - src/pnpm_cmd.rs: Add run_passthrough() function with OsString support - src/pnpm_cmd.rs: Add OsString import - src/main.rs: Add PnpmCommands::Other variant with external_subcommand - src/main.rs: Add match arm for pnpm passthrough - Unit test: test_run_passthrough_accepts_args verifies signature - Smoke tests: Conditional test for pnpm help if pnpm is available This maintains compatibility with all pnpm commands while preserving RTK's token-optimized versions for list/outdated/install. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 32bbd02 commit 614ff5c

4 files changed

Lines changed: 213 additions & 28 deletions

File tree

scripts/test-all.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ assert_help "rtk pnpm" rtk pnpm
227227
assert_help "rtk pnpm build" rtk pnpm build
228228
assert_help "rtk pnpm typecheck" rtk pnpm typecheck
229229

230+
if command -v pnpm >/dev/null 2>&1; then
231+
assert_ok "rtk pnpm help" rtk pnpm help
232+
fi
233+
230234
# ── 10. Grep ─────────────────────────────────────────
231235

232236
section "Grep"

src/git.rs

Lines changed: 170 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ pub fn run(cmd: GitCommand, args: &[String], max_lines: Option<usize>, verbose:
3737
}
3838

3939
fn run_diff(args: &[String], max_lines: Option<usize>, verbose: u8) -> Result<()> {
40+
let timer = tracking::TimedExecution::start();
41+
4042
// Check if user wants stat output
4143
let wants_stat = args
4244
.iter()
@@ -63,6 +65,14 @@ fn run_diff(args: &[String], max_lines: Option<usize>, verbose: u8) -> Result<()
6365

6466
let stdout = String::from_utf8_lossy(&output.stdout);
6567
println!("{}", stdout.trim());
68+
69+
timer.track(
70+
&format!("git diff {}", args.join(" ")),
71+
&format!("rtk git diff {} (passthrough)", args.join(" ")),
72+
&stdout,
73+
&stdout,
74+
);
75+
6676
return Ok(());
6777
}
6878

@@ -75,14 +85,14 @@ fn run_diff(args: &[String], max_lines: Option<usize>, verbose: u8) -> Result<()
7585
}
7686

7787
let output = cmd.output().context("Failed to run git diff")?;
78-
let stdout = String::from_utf8_lossy(&output.stdout);
88+
let stat_stdout = String::from_utf8_lossy(&output.stdout);
7989

8090
if verbose > 0 {
8191
eprintln!("Git diff summary:");
8292
}
8393

8494
// Print stat summary first
85-
println!("{}", stdout.trim());
95+
println!("{}", stat_stdout.trim());
8696

8797
// Now get actual diff but compact it
8898
let mut diff_cmd = Command::new("git");
@@ -94,12 +104,22 @@ fn run_diff(args: &[String], max_lines: Option<usize>, verbose: u8) -> Result<()
94104
let diff_output = diff_cmd.output().context("Failed to run git diff")?;
95105
let diff_stdout = String::from_utf8_lossy(&diff_output.stdout);
96106

107+
let mut final_output = stat_stdout.to_string();
97108
if !diff_stdout.is_empty() {
98109
println!("\n--- Changes ---");
99110
let compacted = compact_diff(&diff_stdout, max_lines.unwrap_or(100));
100111
println!("{}", compacted);
112+
final_output.push_str("\n--- Changes ---\n");
113+
final_output.push_str(&compacted);
101114
}
102115

116+
timer.track(
117+
&format!("git diff {}", args.join(" ")),
118+
&format!("rtk git diff {}", args.join(" ")),
119+
&format!("{}\n{}", stat_stdout, diff_stdout),
120+
&final_output,
121+
);
122+
103123
Ok(())
104124
}
105125

@@ -259,6 +279,8 @@ pub(crate) fn compact_diff(diff: &str, max_lines: usize) -> String {
259279
}
260280

261281
fn run_log(args: &[String], _max_lines: Option<usize>, verbose: u8) -> Result<()> {
282+
let timer = tracking::TimedExecution::start();
283+
262284
let mut cmd = Command::new("git");
263285
cmd.arg("log");
264286

@@ -277,9 +299,16 @@ fn run_log(args: &[String], _max_lines: Option<usize>, verbose: u8) -> Result<()
277299
cmd.args(["--pretty=format:%h %s (%ar) <%an>"]);
278300
}
279301

280-
if !has_limit_flag {
302+
let limit = if !has_limit_flag {
281303
cmd.arg("-10");
282-
}
304+
10
305+
} else {
306+
// Extract limit from args if provided
307+
args.iter()
308+
.find(|arg| arg.starts_with('-') && arg.chars().nth(1).map_or(false, |c| c.is_ascii_digit()))
309+
.and_then(|arg| arg[1..].parse::<usize>().ok())
310+
.unwrap_or(10)
311+
};
283312

284313
// Only add --no-merges if user didn't explicitly request merge commits
285314
let wants_merges = args
@@ -309,11 +338,38 @@ fn run_log(args: &[String], _max_lines: Option<usize>, verbose: u8) -> Result<()
309338
eprintln!("Git log output:");
310339
}
311340

312-
println!("{}", stdout.trim());
341+
// Post-process: truncate long messages, cap lines
342+
let filtered = filter_log_output(&stdout, limit);
343+
println!("{}", filtered);
344+
345+
timer.track(
346+
&format!("git log {}", args.join(" ")),
347+
&format!("rtk git log {}", args.join(" ")),
348+
&stdout,
349+
&filtered,
350+
);
313351

314352
Ok(())
315353
}
316354

355+
/// Filter git log output: truncate long messages, cap lines
356+
fn filter_log_output(output: &str, limit: usize) -> String {
357+
let lines: Vec<&str> = output.lines().collect();
358+
let capped: Vec<String> = lines
359+
.iter()
360+
.take(limit)
361+
.map(|line| {
362+
if line.len() > 80 {
363+
format!("{}...", &line[..77])
364+
} else {
365+
line.to_string()
366+
}
367+
})
368+
.collect();
369+
370+
capped.join("\n").trim().to_string()
371+
}
372+
317373
/// Format porcelain output into compact RTK status display
318374
fn format_status_output(porcelain: &str) -> String {
319375
let lines: Vec<&str> = porcelain.lines().collect();
@@ -410,10 +466,44 @@ fn format_status_output(porcelain: &str) -> String {
410466
output.trim_end().to_string()
411467
}
412468

469+
/// Minimal filtering for git status with user-provided args
470+
fn filter_status_with_args(output: &str) -> String {
471+
let mut result = Vec::new();
472+
473+
for line in output.lines() {
474+
let trimmed = line.trim();
475+
476+
// Skip empty lines
477+
if trimmed.is_empty() {
478+
continue;
479+
}
480+
481+
// Skip common git hints
482+
if trimmed.starts_with("(use \"git") {
483+
continue;
484+
}
485+
if trimmed.starts_with("(create/copy files") {
486+
continue;
487+
}
488+
if trimmed.contains("nothing to commit") && trimmed.contains("working tree clean") {
489+
result.push(line.to_string());
490+
break;
491+
}
492+
493+
result.push(line.to_string());
494+
}
495+
496+
if result.is_empty() {
497+
"ok ✓".to_string()
498+
} else {
499+
result.join("\n")
500+
}
501+
}
502+
413503
fn run_status(args: &[String], verbose: u8) -> Result<()> {
414504
let timer = tracking::TimedExecution::start();
415505

416-
// If user provided flags, pass through to git without RTK formatting
506+
// If user provided flags, apply minimal filtering
417507
if !args.is_empty() {
418508
let output = Command::new("git")
419509
.arg("status")
@@ -428,14 +518,15 @@ fn run_status(args: &[String], verbose: u8) -> Result<()> {
428518
eprint!("{}", stderr);
429519
}
430520

431-
print!("{}", stdout);
521+
// Apply minimal filtering: strip ANSI, remove hints, empty lines
522+
let filtered = filter_status_with_args(&stdout);
523+
print!("{}", filtered);
432524

433-
// Track passthrough mode
434525
timer.track(
435526
&format!("git status {}", args.join(" ")),
436-
&format!("rtk git status {} (passthrough)", args.join(" ")),
437-
&stdout,
527+
&format!("rtk git status {}", args.join(" ")),
438528
&stdout,
529+
&filtered,
439530
);
440531

441532
return Ok(());
@@ -466,6 +557,8 @@ fn run_status(args: &[String], verbose: u8) -> Result<()> {
466557
}
467558

468559
fn run_add(files: &[String], verbose: u8) -> Result<()> {
560+
let timer = tracking::TimedExecution::start();
561+
469562
let mut cmd = Command::new("git");
470563
cmd.arg("add");
471564

@@ -483,6 +576,12 @@ fn run_add(files: &[String], verbose: u8) -> Result<()> {
483576
eprintln!("git add executed");
484577
}
485578

579+
let raw_output = format!(
580+
"{}\n{}",
581+
String::from_utf8_lossy(&output.stdout),
582+
String::from_utf8_lossy(&output.stderr)
583+
);
584+
486585
if output.status.success() {
487586
// Count what was added
488587
let status_output = Command::new("git")
@@ -491,17 +590,26 @@ fn run_add(files: &[String], verbose: u8) -> Result<()> {
491590
.context("Failed to check staged files")?;
492591

493592
let stat = String::from_utf8_lossy(&status_output.stdout);
494-
if stat.trim().is_empty() {
495-
println!("ok (nothing to add)");
593+
let compact = if stat.trim().is_empty() {
594+
"ok (nothing to add)".to_string()
496595
} else {
497596
// Parse "1 file changed, 5 insertions(+)" format
498597
let short = stat.lines().last().unwrap_or("").trim();
499598
if short.is_empty() {
500-
println!("ok ✓");
599+
"ok ✓".to_string()
501600
} else {
502-
println!("ok ✓ {}", short);
601+
format!("ok ✓ {}", short)
503602
}
504-
}
603+
};
604+
605+
println!("{}", compact);
606+
607+
timer.track(
608+
&format!("git add {}", files.join(" ")),
609+
&format!("rtk git add {}", files.join(" ")),
610+
&raw_output,
611+
&compact,
612+
);
505613
} else {
506614
let stderr = String::from_utf8_lossy(&output.stderr);
507615
let stdout = String::from_utf8_lossy(&output.stdout);
@@ -518,6 +626,8 @@ fn run_add(files: &[String], verbose: u8) -> Result<()> {
518626
}
519627

520628
fn run_commit(message: &str, verbose: u8) -> Result<()> {
629+
let timer = tracking::TimedExecution::start();
630+
521631
if verbose > 0 {
522632
eprintln!("git commit -m \"{}\"", message);
523633
}
@@ -527,24 +637,44 @@ fn run_commit(message: &str, verbose: u8) -> Result<()> {
527637
.output()
528638
.context("Failed to run git commit")?;
529639

640+
let stdout = String::from_utf8_lossy(&output.stdout);
641+
let stderr = String::from_utf8_lossy(&output.stderr);
642+
let raw_output = format!("{}\n{}", stdout, stderr);
643+
530644
if output.status.success() {
531-
let stdout = String::from_utf8_lossy(&output.stdout);
532645
// Extract commit hash from output like "[main abc1234] message"
533-
if let Some(line) = stdout.lines().next() {
646+
let compact = if let Some(line) = stdout.lines().next() {
534647
if let Some(hash_start) = line.find(' ') {
535648
let hash = line[1..hash_start].split(' ').last().unwrap_or("");
536649
if !hash.is_empty() && hash.len() >= 7 {
537-
println!("ok ✓ {}", &hash[..7.min(hash.len())]);
538-
return Ok(());
650+
format!("ok ✓ {}", &hash[..7.min(hash.len())])
651+
} else {
652+
"ok ✓".to_string()
539653
}
654+
} else {
655+
"ok ✓".to_string()
540656
}
541-
}
542-
println!("ok ✓");
657+
} else {
658+
"ok ✓".to_string()
659+
};
660+
661+
println!("{}", compact);
662+
663+
timer.track(
664+
&format!("git commit -m \"{}\"", message),
665+
"rtk git commit",
666+
&raw_output,
667+
&compact,
668+
);
543669
} else {
544-
let stderr = String::from_utf8_lossy(&output.stderr);
545-
let stdout = String::from_utf8_lossy(&output.stdout);
546670
if stderr.contains("nothing to commit") || stdout.contains("nothing to commit") {
547671
println!("ok (nothing to commit)");
672+
timer.track(
673+
&format!("git commit -m \"{}\"", message),
674+
"rtk git commit",
675+
&raw_output,
676+
"ok (nothing to commit)",
677+
);
548678
} else {
549679
eprintln!("FAILED: git commit");
550680
if !stderr.trim().is_empty() {
@@ -609,6 +739,8 @@ fn run_push(args: &[String], verbose: u8) -> Result<()> {
609739
}
610740

611741
fn run_pull(args: &[String], verbose: u8) -> Result<()> {
742+
let timer = tracking::TimedExecution::start();
743+
612744
if verbose > 0 {
613745
eprintln!("git pull");
614746
}
@@ -623,10 +755,11 @@ fn run_pull(args: &[String], verbose: u8) -> Result<()> {
623755

624756
let stdout = String::from_utf8_lossy(&output.stdout);
625757
let stderr = String::from_utf8_lossy(&output.stderr);
758+
let raw_output = format!("{}\n{}", stdout, stderr);
626759

627760
if output.status.success() {
628-
if stdout.contains("Already up to date") || stdout.contains("Already up-to-date") {
629-
println!("ok (up-to-date)");
761+
let compact = if stdout.contains("Already up to date") || stdout.contains("Already up-to-date") {
762+
"ok (up-to-date)".to_string()
630763
} else {
631764
// Count files changed
632765
let mut files = 0;
@@ -662,11 +795,20 @@ fn run_pull(args: &[String], verbose: u8) -> Result<()> {
662795
}
663796

664797
if files > 0 {
665-
println!("ok ✓ {} files +{} -{}", files, insertions, deletions);
798+
format!("ok ✓ {} files +{} -{}", files, insertions, deletions)
666799
} else {
667-
println!("ok ✓");
800+
"ok ✓".to_string()
668801
}
669-
}
802+
};
803+
804+
println!("{}", compact);
805+
806+
timer.track(
807+
&format!("git pull {}", args.join(" ")),
808+
&format!("rtk git pull {}", args.join(" ")),
809+
&raw_output,
810+
&compact,
811+
);
670812
} else {
671813
eprintln!("FAILED: git pull");
672814
if !stderr.trim().is_empty() {

src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,9 @@ enum PnpmCommands {
532532
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
533533
args: Vec<String>,
534534
},
535+
/// Passthrough: runs any unsupported pnpm subcommand directly
536+
#[command(external_subcommand)]
537+
Other(Vec<OsString>),
535538
}
536539

537540
#[derive(Subcommand)]
@@ -748,6 +751,9 @@ fn main() -> Result<()> {
748751
PnpmCommands::Typecheck { args } => {
749752
tsc_cmd::run(&args, cli.verbose)?;
750753
}
754+
PnpmCommands::Other(args) => {
755+
pnpm_cmd::run_passthrough(&args, cli.verbose)?;
756+
}
751757
},
752758

753759
Commands::Err { command } => {

0 commit comments

Comments
 (0)