Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/r2x-cli/src/commands/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ pub fn handle_config(action: Option<ConfigAction>, opts: GlobalOpts) {
cache_suffix.dimmed()
);

// Show log file location
if let Some(log_path) = r2x_logger::get_log_path() {
println!(" {}: {}", "log-file".cyan(), log_path.display());
}

// Show other explicit config values
if let Some(ref uv) = config.uv_path {
println!(" {}: {}", "uv-path".cyan(), uv);
Expand Down Expand Up @@ -707,6 +712,7 @@ mod tests {
quiet: 1,
verbose: 0,
log_python: false,
no_stdout: false,
}
}

Expand All @@ -715,6 +721,7 @@ mod tests {
quiet: 0,
verbose: 1,
log_python: false,
no_stdout: false,
}
}

Expand All @@ -723,6 +730,7 @@ mod tests {
quiet: 0,
verbose: 0,
log_python: false,
no_stdout: false,
}
}

Expand Down
18 changes: 18 additions & 0 deletions crates/r2x-cli/src/commands/plugins/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@ pub fn install_plugin(
};
logger::debug(&format!("get_package_info took: {:?}", start.elapsed()));

// Resolve source path for editable installs
let source_path = if editable {
// If it's a local path, canonicalize it
if Path::new(package).exists() {
fs::canonicalize(package)
.ok()
.and_then(|p| p.to_str().map(|s| s.to_string()))
} else {
None
}
} else {
None
};

let start = std::time::Instant::now();
let entry_count = discover_and_register_entry_points_with_deps(
&uv_path,
Expand All @@ -127,6 +141,8 @@ pub fn install_plugin(
dependencies,
package_version: package_version.clone(),
no_cache,
editable,
source_path,
},
)?;
logger::debug(&format!(
Expand Down Expand Up @@ -379,6 +395,8 @@ except Exception as e:
dependencies,
package_version: package_version.clone(),
no_cache,
editable: false,
source_path: None,
},
) {
Ok(entry_count) => {
Expand Down
71 changes: 69 additions & 2 deletions crates/r2x-cli/src/commands/plugins/list.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::config_manager::Config;
use crate::plugins::get_package_info;
use crate::r2x_manifest::{ImplementationType, Manifest};
use crate::GlobalOpts;
use colored::Colorize;
Expand Down Expand Up @@ -45,8 +47,46 @@ pub fn list_plugins(

if has_plugins {
println!("{}", "Plugins:".bold().green());

// Get package version info
let config = Config::load().ok();
let python_path = config.as_ref().map(|c| c.get_venv_python_path());
let uv_path = config
.as_ref()
.and_then(|c| c.uv_path.as_deref())
.unwrap_or("uv");

for (package_name, plugin_names) in &packages {
println!(" {}:", package_name.bold().blue());
// Get package metadata
let pkg = manifest.packages.iter().find(|p| p.name == *package_name);
let is_editable = pkg.map(|p| p.editable_install).unwrap_or(false);

// Get version info
let version_info = if let Some(ref py_path) = python_path {
get_package_info(uv_path, py_path, package_name)
.ok()
.and_then(|(v, _)| v)
} else {
None
};

// Build package header with version and editable status
let mut package_header = format!(" {}:", package_name.bold().blue());
if let Some(version) = version_info {
package_header.push_str(&format!(" {}", format!("v{}", version).dimmed()));
}
if is_editable {
if let Some(source_path) = pkg.and_then(|p| p.resolved_source_path.as_ref()) {
package_header.push_str(&format!(
" {}",
format!("(file://{})", source_path).dimmed()
));
} else {
package_header.push_str(&format!(" {}", "[editable]".yellow()));
}
}
println!("{}", package_header);

for plugin_name in plugin_names {
println!(" - {}", plugin_name);
}
Expand Down Expand Up @@ -106,11 +146,38 @@ fn show_plugin_details(
.find(|pkg| pkg.name == plugin_filter)
.ok_or_else(|| format!("Plugin package '{}' not found", plugin_filter))?;

println!(
// Build package header with version and editable info
let config = Config::load().ok();
let python_path = config.as_ref().map(|c| c.get_venv_python_path());
let uv_path = config
.as_ref()
.and_then(|c| c.uv_path.as_deref())
.unwrap_or("uv");

let version_info = if let Some(ref py_path) = python_path {
get_package_info(uv_path, py_path, &package.name)
.ok()
.and_then(|(v, _)| v)
} else {
None
};

print!(
"{} {}",
"Package:".bold().green(),
package.name.bold().blue()
);
if let Some(version) = version_info {
print!(" {}", format!("v{}", version).dimmed());
}
if package.editable_install {
if let Some(ref source_path) = package.resolved_source_path {
print!(" {}", format!("(file://{})", source_path).dimmed());
} else {
print!(" {}", "[editable]".yellow());
}
}
println!();
println!();

// Filter plugins by module name if provided
Expand Down
4 changes: 4 additions & 0 deletions crates/r2x-cli/src/commands/plugins/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ pub fn remove_plugin(package: &str, _opts: &GlobalOpts) -> Result<(), String> {
manifest.remove_decorator_registrations(package);

if removed_count > 0 {
// Remove the package entirely from the manifest
manifest.remove_package(package);

for dep in &orphaned_dependencies {
let count = manifest.remove_plugins_by_package(dep);
manifest.remove_decorator_registrations(dep);
if count > 0 {
logger::info(&format!("Removing orphaned dependency package '{}'", dep));
manifest.remove_package(dep);
removed_count += count;
}
}
Expand Down
3 changes: 3 additions & 0 deletions crates/r2x-cli/src/commands/plugins/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ pub fn sync_manifest(_opts: &GlobalOpts) -> Result<(), String> {
package_version,
// Always re-scan when syncing so manifest reflects the latest plugin code
no_cache: true,
// During sync, preserve existing editable/source_path from manifest
editable: false,
source_path: None,
},
) {
Ok(_) => {
Expand Down
31 changes: 28 additions & 3 deletions crates/r2x-cli/src/commands/run/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ fn run_pipeline(
let pipeline_start = Instant::now();
eprintln!("{}", format!("Running: {}", pipeline_name).cyan().bold());

// Show log file location to user
if let Some(log_path) = logger::get_log_path() {
eprintln!("{}", format!(" Log file: {}", log_path.display()).dimmed());
}

let mut current_stdin: Option<String> = None;

let resolved_output_folder = if let Some(folder) = &config.output_folder {
Expand Down Expand Up @@ -208,6 +213,17 @@ fn run_pipeline(
logger::debug(&format!("Invoking: {}", target));
logger::debug(&format!("Config: {}", final_config_json));

// Set current plugin context for logging
logger::set_current_plugin(Some(plugin_name.to_string()));

// Reconfigure Python logging with plugin name
if let Err(e) = Bridge::reconfigure_logging_for_plugin(plugin_name) {
logger::warn(&format!(
"Failed to reconfigure Python logging for plugin {}: {}",
plugin_name, e
));
}

let invocation_result =
match bridge.invoke_plugin(&target, &final_config_json, stdin_json, Some(plugin)) {
Ok(inv_result) => {
Expand Down Expand Up @@ -235,14 +251,23 @@ fn run_pipeline(
total_steps,
super::format_duration(elapsed)
));
// Clear plugin context before returning error
logger::set_current_plugin(None);
return Err(RunError::Bridge(e));
}
};

// Clear plugin context after execution
logger::set_current_plugin(None);

let result = invocation_result.output;

if !result.is_empty() && result != "null" {
logger::debug(&format!("Plugin produced output ({} bytes)", result.len()));
if !opts.no_stdout {
logger::debug(&format!("Plugin produced output ({} bytes)", result.len()));
} else {
logger::debug("Plugin produced output (suppressed by --no-stdout)");
}
current_stdin = Some(result);
} else {
logger::debug("Plugin produced no output or output not used");
Expand All @@ -265,8 +290,8 @@ fn run_pipeline(
std::fs::write(output_path, final_output.as_bytes())
.map_err(|e| RunError::Pipeline(PipelineError::Io(e)))?;
logger::success(&format!("Output saved to: {}", output_path));
} else if opts.suppress_stdout() {
logger::debug("Pipeline output suppressed due to -qq");
} else if opts.suppress_stdout() || opts.no_stdout {
logger::debug("Pipeline output suppressed");
} else {
println!("{}", final_output);
}
Expand Down
18 changes: 16 additions & 2 deletions crates/r2x-cli/src/commands/run/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ fn run_plugin(plugin_name: &str, args: &[String], opts: &GlobalOpts) -> Result<(
logger::debug(&format!("Invoking plugin with target: {}", target));
logger::debug(&format!("Config: {}", config_json));

// Set current plugin context for logging
logger::set_current_plugin(Some(plugin_name.to_string()));

// Reconfigure Python logging with plugin name
if let Err(e) = Bridge::reconfigure_logging_for_plugin(plugin_name) {
logger::warn(&format!(
"Failed to reconfigure Python logging for plugin {}: {}",
plugin_name, e
));
}

let start = Instant::now();
let invocation_result = bridge.invoke_plugin(&target, &config_json, None, Some(plugin))?;
let PluginInvocationResult {
Expand All @@ -100,9 +111,12 @@ fn run_plugin(plugin_name: &str, args: &[String], opts: &GlobalOpts) -> Result<(
let elapsed = start.elapsed();
let duration_msg = format!("({})", super::format_duration(elapsed).dimmed());

// Clear plugin context after execution
logger::set_current_plugin(None);

if !result.is_empty() && result != "null" {
if opts.suppress_stdout() {
logger::debug("Plugin output suppressed due to -qq");
if opts.suppress_stdout() || opts.no_stdout {
logger::debug("Plugin output suppressed");
} else {
println!("{}", result);
}
Expand Down
7 changes: 7 additions & 0 deletions crates/r2x-cli/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ pub struct GlobalOpts {
help = "Show Python logs on console (always logged to file)"
)]
pub log_python: bool,

#[arg(
long,
global = true,
help = "Disable logging stdout to file (useful with --log-python to avoid large system objects in logs)"
)]
pub no_stdout: bool,
}

impl GlobalOpts {
Expand Down
9 changes: 6 additions & 3 deletions crates/r2x-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,12 @@ enum PluginsAction {
fn main() {
let cli = Cli::parse();

// Initialize logger with verbosity level and log_python flag
if let Err(e) = logger::init_with_verbosity(cli.global.verbosity_level(), cli.global.log_python)
{
// Initialize logger with verbosity level, log_python flag, and no_stdout flag
if let Err(e) = logger::init_with_verbosity(
cli.global.verbosity_level(),
cli.global.log_python,
cli.global.no_stdout,
) {
eprintln!("Warning: Failed to initialize logger: {}", e);
}

Expand Down
Loading