From 4758bb2bf276652a3222f773cb7262c6c842b458 Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Fri, 27 Feb 2026 01:45:28 +0100 Subject: [PATCH] feat(pnpm): Add filter argument support - Add support for pnpm global filter arguments (`--filter`) across all pnpm commands (`list`, `outdated`, `install`, etc.). - Merge pnpm filters with native command arguments (`--filter` + custom args like `--depth`). - Implement proper handling for `Vec` (text filters) and `Vec` (OS-specific filters). - Add unit tests for `merge_pnpm_args` and CLI parsing to validate expected behavior. This enables users to apply workspace-specific filtering directly via `--filter @scope` before running pnpm commands, reducing token overhead by excluding irrelevant packages/dependencies from output. Fixes: #259 FIXME: Add notes for typechecking/building with workspaces when filters are applied. --- src/main.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index fcb39303..de937a1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,6 +137,10 @@ enum Commands { /// pnpm commands with ultra-compact output Pnpm { + /// pnpm filter arguments (can be repeated: --filter @app1 --filter @app2) + #[arg(long)] + filter: Vec, + #[command(subcommand)] command: PnpmCommands, }, @@ -852,6 +856,24 @@ enum GoCommands { Other(Vec), } +/// Merge pnpm global filters args with other ones +fn merge_pnpm_args(filters: &[String], args: &[String]) -> Vec { + filters + .iter() + .map(|filter| format!("--filter={}", filter)) + .chain(args.iter().map(|arg| arg.to_string())) + .collect() +} + +/// Merge pnpm global filters args with other ones +fn merge_pnpm_args_os(filters: &[String], args: &[OsString]) -> Vec { + filters + .iter() + .map(|filter| OsString::from(format!("--filter={}", filter))) + .chain(args.iter().map(|arg| arg.to_os_string())) + .collect() +} + fn main() -> Result<()> { let cli = Cli::parse(); @@ -941,28 +963,38 @@ fn main() -> Result<()> { gh_cmd::run(&subcommand, &args, cli.verbose, cli.ultra_compact)?; } - Commands::Pnpm { command } => match command { + Commands::Pnpm { filter, command } => match command { PnpmCommands::List { depth, args } => { - pnpm_cmd::run(pnpm_cmd::PnpmCommand::List { depth }, &args, cli.verbose)?; + pnpm_cmd::run( + pnpm_cmd::PnpmCommand::List { depth }, + &merge_pnpm_args(&filter, &args), + cli.verbose, + )?; } PnpmCommands::Outdated { args } => { - pnpm_cmd::run(pnpm_cmd::PnpmCommand::Outdated, &args, cli.verbose)?; + pnpm_cmd::run( + pnpm_cmd::PnpmCommand::Outdated, + &merge_pnpm_args(&filter, &args), + cli.verbose, + )?; } PnpmCommands::Install { packages, args } => { pnpm_cmd::run( pnpm_cmd::PnpmCommand::Install { packages }, - &args, + &merge_pnpm_args(&filter, &args), cli.verbose, )?; } PnpmCommands::Build { args } => { + // FIXME: if filters are present, we should find out wich workspaces are build before running build next_cmd::run(&args, cli.verbose)?; } PnpmCommands::Typecheck { args } => { + // FIXME: if filters are present, we should find out wich workspaces are typechecked before running tsc tsc_cmd::run(&args, cli.verbose)?; } PnpmCommands::Other(args) => { - pnpm_cmd::run_passthrough(&args, cli.verbose)?; + pnpm_cmd::run_passthrough(&merge_pnpm_args_os(&filter, &args), cli.verbose)?; } }, @@ -1546,4 +1578,49 @@ mod tests { _ => panic!("Expected Git Commit command"), } } + + #[test] + fn test_merge_filters_with_args() { + let filters = vec!["@app1".to_string(), "@app2".to_string()]; + let args = vec![ + "--filter=@app3".to_string(), + "--depth=0".to_string(), + "--no-verbose".to_string(), + ]; + let expected_args = vec![ + "--filter=@app1", + "--filter=@app2", + "--filter=@app3", + "--depth=0", + "--no-verbose", + ]; + assert_eq!(merge_pnpm_args(&filters, &args), expected_args); + } + + #[test] + fn test_merge_filters_with_args_os() { + let filters = vec!["@app1".to_string()]; + let args = vec![OsString::from("--depth=0")]; + let expected_args = vec![ + OsString::from("--filter=@app1"), + OsString::from("--depth=0"), + ]; + assert_eq!(merge_pnpm_args_os(&filters, &args), expected_args); + } + + #[test] + fn test_pnpm_subcommand_with_filter() { + let cli = Cli::try_parse_from(["rtk", "pnpm", "--filter", "@app1", "list"]).unwrap(); + match cli.command { + Commands::Pnpm { + filter, + command: PnpmCommands::List { depth, args }, + } => { + assert_eq!(depth, 0); + assert_eq!(filter, vec!["@app1"]); + assert!(args.is_empty()); + } + _ => panic!("Expected Pnpm List command"), + } + } }