From ea5214bb1934b9679a6b877052db92b13c73c9b9 Mon Sep 17 00:00:00 2001 From: Felipe Santos Date: Sun, 1 Jun 2025 05:00:56 -0300 Subject: [PATCH 1/5] Revamp --query --- .vscode/launch.json | 50 ++++++++++ .vscode/settings.json | 8 ++ crates/cli/src/args.rs | 13 ++- crates/cli/src/main.rs | 4 +- crates/cli/src/query.rs | 199 ++++++++++++++++++++++++++++++++++---- crates/cli/src/resolve.rs | 4 +- 6 files changed, 254 insertions(+), 24 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..80543ff01 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'pkgx'", + "cargo": { + "args": ["build", "--bin=pkgx", "--package=pkgx"], + "filter": { + "name": "pkgx", + "kind": "bin" + } + }, + "args": ["+git", "--json=v2"], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'pkgx'", + "cargo": { + "args": ["test", "--no-run", "--bin=pkgx", "--package=pkgx"], + "filter": { + "name": "pkgx", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'libpkgx'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=libpkgx"], + "filter": { + "name": "libpkgx", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..550daddd0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.check.command": "clippy", + "rust-analyzer.rustfmt.rangeFormatting.enable": true, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "editor.formatOnSave": true + } +} diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index 3b0ebae20..a740746e1 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -1,5 +1,6 @@ use console::style; +#[derive(PartialEq)] pub enum Mode { X, Help, @@ -114,9 +115,15 @@ pub fn parse() -> Args { } } } else { - find_program = !arg.contains('/'); - collecting_args = true; - args.push(arg); + // Only start collecting args if not in query mode, or if we're already collecting + if mode == Mode::Query && !collecting_args { + // In query mode, continue processing flags until we hit a non-flag argument + args.push(arg); + } else { + find_program = !arg.contains('/'); + collecting_args = true; + args.push(arg); + } } } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index a8cea4a91..504430e78 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -42,8 +42,8 @@ async fn main() -> Result<(), Box> { Ok(()) } args::Mode::Query => { - let (conn, _, _, _) = setup(&flags).await?; - query::query(&args, flags.silent, &conn) + let (conn, _, config, _) = setup(&flags).await?; + query::query(&args, flags.silent, &conn, flags.json, &config).await } args::Mode::X => { let (mut conn, did_sync, config, mut spinner) = setup(&flags).await?; diff --git a/crates/cli/src/query.rs b/crates/cli/src/query.rs index 93133785f..4560dbce0 100644 --- a/crates/cli/src/query.rs +++ b/crates/cli/src/query.rs @@ -1,32 +1,197 @@ use std::error::Error; -use libpkgx::pantry_db; +use libpkgx::{config::Config, inventory, pantry_db}; use rusqlite::{params, Connection}; +use serde::Serialize; + +use crate::resolve::{parse_pkgspec, Pkgspec}; + +#[derive(Serialize, Clone)] +struct QueryResult { + project: String, + programs: Vec, +} + +fn resolve_projects_for_pkgspec( + pkgspec: &mut Pkgspec, + conn: &Connection, +) -> Result, Box> { + match pkgspec { + Pkgspec::Req(pkgreq) => { + // Check if this looks like a program name (no dots and wildcard constraint) + if !pkgreq.project.contains('.') && pkgreq.constraint.raw == "*" { + // Handle as program lookup + Ok(pantry_db::which(&pkgreq.project, conn)?) + } else { + // Handle as package spec - resolve project name and return single project + let (project, _) = resolve_project_name(&pkgreq.project, conn)?; + pkgreq.project = project.clone(); + Ok(vec![project]) + } + } + Pkgspec::Latest(program_or_project) => { + let (project, _) = resolve_project_name(program_or_project, conn)?; + Ok(vec![project]) + } + } +} + +fn resolve_project_name( + input: &str, + conn: &Connection, +) -> Result<(String, String), Box> { + let original = input.to_string(); + + // First, try to resolve as a program name + let projects = pantry_db::which(&input.to_string(), conn)?; + match projects.len() { + 0 => { + // If not found as a program and contains a dot, check if it exists as a project + if input.contains('.') { + let mut stmt = conn.prepare("SELECT COUNT(*) FROM provides WHERE project = ?")?; + let count: i64 = stmt.query_row(params![input], |row| row.get(0))?; + if count > 0 { + return Ok((input.to_string(), original)); + } + } + Err(format!("Package '{}' not found", original).into()) + } + 1 => Ok((projects[0].clone(), original)), + _ => Err(format!( + "Package '{}' is ambiguous: {}", + original, + projects.join(", ") + ) + .into()), + } +} + +fn get_programs_for_project( + project: &str, + conn: &Connection, +) -> Result, Box> { + let mut stmt = + conn.prepare("SELECT program FROM provides WHERE project = ? ORDER BY program")?; + let mut rows = stmt.query(params![project])?; + let mut programs = Vec::new(); + while let Some(row) = rows.next()? { + programs.push(row.get(0)?); + } + Ok(programs) +} + +async fn process_query_arg( + arg: &str, + conn: &Connection, + config: &Config, +) -> Result, Box> { + let mut pkgspec = parse_pkgspec(arg)?; + let projects = resolve_projects_for_pkgspec(&mut pkgspec, conn)?; + + if projects.is_empty() { + let name = match &pkgspec { + Pkgspec::Req(req) => &req.project, + Pkgspec::Latest(project) => project, + }; + return Err(format!("{} not found", name).into()); + } + + let mut results = Vec::new(); + + // Determine which projects to process + let projects_to_process = match &pkgspec { + Pkgspec::Req(pkgreq) if !pkgreq.project.contains('.') && pkgreq.constraint.raw == "*" => { + // For program lookups (no dots and wildcard), process all matching projects + &projects + } + _ => { + // For package specs and latest, process first project only + &projects[0..1] + } + }; + + // Process each project + for project in projects_to_process { + // For version specs with constraints, check if any matching versions are available + if let Pkgspec::Req(pkgreq) = &pkgspec { + if pkgreq.constraint.raw != "*" { + match inventory::ls(project, config).await { + Ok(versions) => { + let matching_versions: Vec<_> = versions + .iter() + .filter(|v| pkgreq.constraint.satisfies(v)) + .collect(); + + if matching_versions.is_empty() { + return Err(format!( + "No versions matching {} found for {}", + pkgreq.constraint.raw, project + ) + .into()); + } + } + Err(_) => { + return Err(format!("Failed to get versions for {}", project).into()); + } + } + } + } + + let programs = get_programs_for_project(project, conn)?; + results.push(QueryResult { + project: project.clone(), + programs, + }); + } + + Ok(results) +} + +fn format_standard_output(results: &[QueryResult]) -> Vec { + results + .iter() + .map(|result| result.project.clone()) + .collect() +} + +fn format_json_output(results: &[QueryResult]) -> String { + serde_json::to_string_pretty(results).unwrap_or_else(|_| "[]".to_string()) +} + +pub async fn query( + args: &Vec, + silent: bool, + conn: &Connection, + json_version: Option, + config: &Config, +) -> Result<(), Box> { + let is_json = json_version == Some(2); + let mut all_results = Vec::new(); -pub fn query(args: &Vec, silent: bool, conn: &Connection) -> Result<(), Box> { if args.is_empty() { - let mut stmt = conn.prepare("SELECT program FROM provides")?; + let mut stmt = conn.prepare("SELECT DISTINCT project FROM provides ORDER BY project")?; let mut rows = stmt.query(params![])?; + while let Some(row) = rows.next()? { - let program: String = row.get(0)?; - println!("{}", program); + let project: String = row.get(0)?; + let programs = get_programs_for_project(&project, conn)?; + all_results.push(QueryResult { project, programs }); } } else { - let mut fail = false; for arg in args { - let projects = pantry_db::which(arg, conn)?; - if projects.is_empty() && silent { - std::process::exit(1); - } else if projects.is_empty() { - println!("{} not found", arg); - fail = true; - } else if !silent { - println!("{}", projects.join(", ")); - } + let results = process_query_arg(arg, conn, config).await?; + all_results.extend(results); } - if fail { - std::process::exit(1); + } + + if is_json { + println!("{}", format_json_output(&all_results)); + } else if !silent { + let output_lines = format_standard_output(&all_results); + for line in output_lines { + println!("{}", line); } } + Ok(()) } diff --git a/crates/cli/src/resolve.rs b/crates/cli/src/resolve.rs index 37327ecee..38246c25a 100644 --- a/crates/cli/src/resolve.rs +++ b/crates/cli/src/resolve.rs @@ -91,7 +91,7 @@ pub async fn resolve( Ok((installations, graph)) } -enum Pkgspec { +pub enum Pkgspec { Req(PackageReq), Latest(String), } @@ -133,7 +133,7 @@ impl Pkgspec { } } -fn parse_pkgspec(pkgspec: &str) -> Result> { +pub fn parse_pkgspec(pkgspec: &str) -> Result> { if let Some(project) = pkgspec.strip_suffix("@latest") { Ok(Pkgspec::Latest(project.to_string())) } else { From 6f44be06a57e36a2053813b83db43061b8c5af9a Mon Sep 17 00:00:00 2001 From: Jacob Heider Date: Mon, 2 Jun 2025 10:29:01 -0400 Subject: [PATCH 2/5] fix lints --- crates/lib/src/install.rs | 4 +--- crates/lib/src/sync.rs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index bf097ea5f..c58e3bb6d 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -98,9 +98,7 @@ where } }); - let stream = stream - .map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e)) - .into_async_read(); + let stream = stream.map_err(futures::io::Error::other).into_async_read(); let stream = stream.compat(); // Step 2: Create a XZ decoder diff --git a/crates/lib/src/sync.rs b/crates/lib/src/sync.rs index 6d75138c5..f5df20954 100644 --- a/crates/lib/src/sync.rs +++ b/crates/lib/src/sync.rs @@ -58,9 +58,7 @@ async fn download_and_extract_pantry(url: &str, dest: &PathBuf) -> Result<(), Bo let stream = rsp.bytes_stream(); - let stream = stream - .map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e)) - .into_async_read(); + let stream = stream.map_err(futures::io::Error::other).into_async_read(); let stream = stream.compat(); let decoder = XzDecoder::new(stream); From 9ccf61f54f9c1d07ee602f08b45bedff1876ac83 Mon Sep 17 00:00:00 2001 From: Jacob Heider Date: Mon, 2 Feb 2026 20:22:53 -0500 Subject: [PATCH 3/5] some cleanup --- .vscode/launch.json | 50 -------- .vscode/settings.json | 8 -- Cargo.lock | 2 +- crates/cli/Cargo.toml | 2 +- crates/cli/src/args.rs | 8 +- crates/cli/src/main.rs | 2 +- crates/cli/src/query.rs | 276 +++++++++++++++++----------------------- 7 files changed, 125 insertions(+), 223 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 80543ff01..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'pkgx'", - "cargo": { - "args": ["build", "--bin=pkgx", "--package=pkgx"], - "filter": { - "name": "pkgx", - "kind": "bin" - } - }, - "args": ["+git", "--json=v2"], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'pkgx'", - "cargo": { - "args": ["test", "--no-run", "--bin=pkgx", "--package=pkgx"], - "filter": { - "name": "pkgx", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'libpkgx'", - "cargo": { - "args": ["test", "--no-run", "--lib", "--package=libpkgx"], - "filter": { - "name": "libpkgx", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 550daddd0..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "rust-analyzer.check.command": "clippy", - "rust-analyzer.rustfmt.rangeFormatting.enable": true, - "[rust]": { - "editor.defaultFormatter": "rust-lang.rust-analyzer", - "editor.formatOnSave": true - } -} diff --git a/Cargo.lock b/Cargo.lock index 6922dff3d..7a7101052 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1059,7 +1059,7 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "pkgx" -version = "2.7.1" +version = "2.8.0" dependencies = [ "console", "indicatif", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 4d584b2d3..085f1d976 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -3,7 +3,7 @@ name = "pkgx" description = "Run anything" authors = ["Max Howell ", "Jacob Heider "] license = "Apache-2.0" -version = "2.7.1" +version = "2.8.0" edition = "2021" repository = "https://github.com/pkgxdev/pkgx" diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index a740746e1..325da147b 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -115,15 +115,11 @@ pub fn parse() -> Args { } } } else { - // Only start collecting args if not in query mode, or if we're already collecting - if mode == Mode::Query && !collecting_args { - // In query mode, continue processing flags until we hit a non-flag argument - args.push(arg); - } else { + if mode != Mode::Query { find_program = !arg.contains('/'); collecting_args = true; - args.push(arg); } + args.push(arg); } } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 504430e78..c7a63f3f2 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -43,7 +43,7 @@ async fn main() -> Result<(), Box> { } args::Mode::Query => { let (conn, _, config, _) = setup(&flags).await?; - query::query(&args, flags.silent, &conn, flags.json, &config).await + query::query(&args, &flags, &conn, &config).await } args::Mode::X => { let (mut conn, did_sync, config, mut spinner) = setup(&flags).await?; diff --git a/crates/cli/src/query.rs b/crates/cli/src/query.rs index 4560dbce0..d462cda5e 100644 --- a/crates/cli/src/query.rs +++ b/crates/cli/src/query.rs @@ -4,72 +4,152 @@ use libpkgx::{config::Config, inventory, pantry_db}; use rusqlite::{params, Connection}; use serde::Serialize; -use crate::resolve::{parse_pkgspec, Pkgspec}; +use crate::{ + args::Flags, + resolve::{parse_pkgspec, Pkgspec}, +}; -#[derive(Serialize, Clone)] +#[derive(Serialize)] struct QueryResult { project: String, programs: Vec, } -fn resolve_projects_for_pkgspec( - pkgspec: &mut Pkgspec, +pub async fn query( + args: &Vec, + flags: &Flags, conn: &Connection, -) -> Result, Box> { - match pkgspec { - Pkgspec::Req(pkgreq) => { - // Check if this looks like a program name (no dots and wildcard constraint) - if !pkgreq.project.contains('.') && pkgreq.constraint.raw == "*" { - // Handle as program lookup - Ok(pantry_db::which(&pkgreq.project, conn)?) - } else { - // Handle as package spec - resolve project name and return single project - let (project, _) = resolve_project_name(&pkgreq.project, conn)?; - pkgreq.project = project.clone(); - Ok(vec![project]) + config: &Config, +) -> Result<(), Box> { + let is_json = flags.json == Some(2); + let silent = flags.silent; + + // print out the whole list if no args + if args.is_empty() { + // if they requested json, output full mapping of project -> programs + if is_json { + let mut stmt = + conn.prepare("SELECT DISTINCT project FROM provides ORDER BY project")?; + let mut rows = stmt.query(params![])?; + let mut results = Vec::new(); + while let Some(row) = rows.next()? { + let project: String = row.get(0)?; + let programs = get_programs(conn, &project)?; + results.push(QueryResult { project, programs }); + } + println!("{}", serde_json::to_string_pretty(&results)?); + // if not, just list all programs + } else { + let mut stmt = conn.prepare("SELECT program FROM provides")?; + let mut rows = stmt.query(params![])?; + while let Some(row) = rows.next()? { + let program: String = row.get(0)?; + println!("{}", program); + } + } + return Ok(()); + } + + let mut results = Vec::new(); + let mut fail = false; + + for arg in args { + let mut pkgspec = parse_pkgspec(arg)?; + + let projects = match &mut pkgspec { + Pkgspec::Req(req) if !req.project.contains('.') && req.constraint.raw == "*" => { + pantry_db::which(&req.project, conn)? + } + Pkgspec::Req(req) => match resolve_project(&req.project, conn) { + Ok(project) => { + req.project = project.clone(); + vec![project] + } + Err(e) => { + if silent { std::process::exit(1); } + println!("{}", e); + fail = true; + continue; + } + }, + Pkgspec::Latest(name) => match resolve_project(name, conn) { + Ok(project) => vec![project], + Err(e) => { + if silent { std::process::exit(1); } + println!("{}", e); + fail = true; + continue; + } + }, + }; + + if projects.is_empty() { + if silent { + std::process::exit(1); } + println!("{} not found", arg); + fail = true; + continue; } - Pkgspec::Latest(program_or_project) => { - let (project, _) = resolve_project_name(program_or_project, conn)?; - Ok(vec![project]) + + // validate version constraint if specified + if let Pkgspec::Req(req) = &pkgspec { + if req.constraint.raw != "*" { + let versions = inventory::ls(&projects[0], config).await?; + if !versions.iter().any(|v| req.constraint.satisfies(v)) { + if silent { + std::process::exit(1); + } + println!("no versions matching {} found for {}", req.constraint.raw, projects[0]); + fail = true; + continue; + } + } + } + + if is_json { + for project in &projects { + let programs = get_programs(conn, project)?; + results.push(QueryResult { + project: project.clone(), + programs, + }); + } + } else if !silent { + println!("{}", projects.join(", ")); } } -} -fn resolve_project_name( - input: &str, - conn: &Connection, -) -> Result<(String, String), Box> { - let original = input.to_string(); + if is_json { + println!("{}", serde_json::to_string_pretty(&results)?); + } + + if fail { + std::process::exit(1); + } + + Ok(()) +} - // First, try to resolve as a program name +fn resolve_project(input: &str, conn: &Connection) -> Result> { let projects = pantry_db::which(&input.to_string(), conn)?; match projects.len() { + 1 => Ok(projects[0].clone()), 0 => { - // If not found as a program and contains a dot, check if it exists as a project if input.contains('.') { let mut stmt = conn.prepare("SELECT COUNT(*) FROM provides WHERE project = ?")?; let count: i64 = stmt.query_row(params![input], |row| row.get(0))?; if count > 0 { - return Ok((input.to_string(), original)); + return Ok(input.to_string()); } } - Err(format!("Package '{}' not found", original).into()) + Err(format!("{} not found", input).into()) } - 1 => Ok((projects[0].clone(), original)), - _ => Err(format!( - "Package '{}' is ambiguous: {}", - original, - projects.join(", ") - ) - .into()), + _ => Err(format!("{} is ambiguous: {}", input, projects.join(", ")).into()), } } -fn get_programs_for_project( - project: &str, - conn: &Connection, -) -> Result, Box> { +fn get_programs(conn: &Connection, project: &str) -> Result, Box> { let mut stmt = conn.prepare("SELECT program FROM provides WHERE project = ? ORDER BY program")?; let mut rows = stmt.query(params![project])?; @@ -79,119 +159,3 @@ fn get_programs_for_project( } Ok(programs) } - -async fn process_query_arg( - arg: &str, - conn: &Connection, - config: &Config, -) -> Result, Box> { - let mut pkgspec = parse_pkgspec(arg)?; - let projects = resolve_projects_for_pkgspec(&mut pkgspec, conn)?; - - if projects.is_empty() { - let name = match &pkgspec { - Pkgspec::Req(req) => &req.project, - Pkgspec::Latest(project) => project, - }; - return Err(format!("{} not found", name).into()); - } - - let mut results = Vec::new(); - - // Determine which projects to process - let projects_to_process = match &pkgspec { - Pkgspec::Req(pkgreq) if !pkgreq.project.contains('.') && pkgreq.constraint.raw == "*" => { - // For program lookups (no dots and wildcard), process all matching projects - &projects - } - _ => { - // For package specs and latest, process first project only - &projects[0..1] - } - }; - - // Process each project - for project in projects_to_process { - // For version specs with constraints, check if any matching versions are available - if let Pkgspec::Req(pkgreq) = &pkgspec { - if pkgreq.constraint.raw != "*" { - match inventory::ls(project, config).await { - Ok(versions) => { - let matching_versions: Vec<_> = versions - .iter() - .filter(|v| pkgreq.constraint.satisfies(v)) - .collect(); - - if matching_versions.is_empty() { - return Err(format!( - "No versions matching {} found for {}", - pkgreq.constraint.raw, project - ) - .into()); - } - } - Err(_) => { - return Err(format!("Failed to get versions for {}", project).into()); - } - } - } - } - - let programs = get_programs_for_project(project, conn)?; - results.push(QueryResult { - project: project.clone(), - programs, - }); - } - - Ok(results) -} - -fn format_standard_output(results: &[QueryResult]) -> Vec { - results - .iter() - .map(|result| result.project.clone()) - .collect() -} - -fn format_json_output(results: &[QueryResult]) -> String { - serde_json::to_string_pretty(results).unwrap_or_else(|_| "[]".to_string()) -} - -pub async fn query( - args: &Vec, - silent: bool, - conn: &Connection, - json_version: Option, - config: &Config, -) -> Result<(), Box> { - let is_json = json_version == Some(2); - let mut all_results = Vec::new(); - - if args.is_empty() { - let mut stmt = conn.prepare("SELECT DISTINCT project FROM provides ORDER BY project")?; - let mut rows = stmt.query(params![])?; - - while let Some(row) = rows.next()? { - let project: String = row.get(0)?; - let programs = get_programs_for_project(&project, conn)?; - all_results.push(QueryResult { project, programs }); - } - } else { - for arg in args { - let results = process_query_arg(arg, conn, config).await?; - all_results.extend(results); - } - } - - if is_json { - println!("{}", format_json_output(&all_results)); - } else if !silent { - let output_lines = format_standard_output(&all_results); - for line in output_lines { - println!("{}", line); - } - } - - Ok(()) -} From 332aa0533c4aa47f02fb34ca86dbbb188893f5ea Mon Sep 17 00:00:00 2001 From: Jacob Heider Date: Mon, 2 Feb 2026 20:49:27 -0500 Subject: [PATCH 4/5] integration tests --- .github/workflows/ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb660a05c..169f7b585 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -167,6 +167,18 @@ jobs: ! pkgx -Qqq flubber-flubber pkgx -Q + - name: query --json + run: | + pkgx -Q --json=v2 git | pkgx jq -e '.[0].project == "git-scm.org"' + pkgx -Q --json=v2 git | pkgx jq -e '.[0].programs | length > 0' + pkgx -Q --json=v2 | pkgx jq -e 'length > 0' + + - name: query pkgspecs + run: | + pkgx -Q git-scm.org | grep -q git-scm.org + pkgx -Q node git | grep -q nodejs.org + ! pkgx -Q git-scm.org@99999 + - run: if [ $(find ~/.pkgx -name .tmp\* -type d | wc -l) -gt 0 ]; then exit 1; fi From c36fa4918af832b3baaf2e1ff0f5f072a167bfab Mon Sep 17 00:00:00 2001 From: Jacob Heider Date: Mon, 2 Feb 2026 20:54:56 -0500 Subject: [PATCH 5/5] fmt --- crates/cli/src/query.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/query.rs b/crates/cli/src/query.rs index d462cda5e..57fa08808 100644 --- a/crates/cli/src/query.rs +++ b/crates/cli/src/query.rs @@ -66,7 +66,9 @@ pub async fn query( vec![project] } Err(e) => { - if silent { std::process::exit(1); } + if silent { + std::process::exit(1); + } println!("{}", e); fail = true; continue; @@ -75,7 +77,9 @@ pub async fn query( Pkgspec::Latest(name) => match resolve_project(name, conn) { Ok(project) => vec![project], Err(e) => { - if silent { std::process::exit(1); } + if silent { + std::process::exit(1); + } println!("{}", e); fail = true; continue; @@ -100,7 +104,10 @@ pub async fn query( if silent { std::process::exit(1); } - println!("no versions matching {} found for {}", req.constraint.raw, projects[0]); + println!( + "no versions matching {} found for {}", + req.constraint.raw, projects[0] + ); fail = true; continue; }