//! plugins: list the event plugins and test one against a synthetic event //! (issue #75). 'am plugins --test ' is the CI hook: it exercises the //! JSON contract end to end and exits non-zero on failure. use super::*; use crate::cli::PluginsArgs; use crate::plugins::{self, PluginStatus}; use anyhow::Result; pub fn run(app: &App, args: &PluginsArgs) -> Result { let dir = plugins::dir(app); let all = plugins::discover(app, &dir); if let Some(name) = &args.test { let Some(plugin) = all.iter().find(|p| &p.name == name) else { anyhow::bail!( "unknown plugin '{name}' — 'am plugins' lists the plugins of {}", dir.display() ); }; // Synthetic event (a start, agent 'test') to exercise the contract. let event = crate::events::Event::now(crate::events::EventKind::Start) .with_agent("test") .with_args(vec!["--probe".to_string()]); let r = plugins::run_plugin(app, plugin, "on_start", &event); if app.json() { crate::output::print_json(&serde_json::json!({ "name": plugin.name, "status": r.status.as_str(), "exit_code": r.exit_code, "duration_ms": r.duration_ms, "response": r.response, "stdout": r.stdout, "stderr": r.stderr, "error": r.error, })); } else { app.log.info(&format!( "plugin '{}' — {} in {}ms", plugin.name, r.status.as_str(), r.duration_ms )); if let Some(code) = r.exit_code { app.log.info(&format!("exit code: {code}")); } if let Some(resp) = &r.response { if let Some(msg) = &resp.message { app.log.info(&format!("message: {msg}")); } if let Some(err) = &resp.error { app.log.warn(&format!("error: {err}")); } } if !r.stdout.trim().is_empty() { app.log.info(&format!("stdout: {}", r.stdout.trim())); } if !r.stderr.trim().is_empty() { app.log.warn(&format!("stderr: {}", r.stderr.trim())); } if let Some(e) = &r.error { app.log.warn(&format!("{e}")); } } return Ok(if r.status == PluginStatus::Ok { 0 } else { 1 }); } if app.json() { let items: Vec = all .iter() .map(|p| { serde_json::json!({ "name": p.name, "events": p.events, "timeout_secs": p.timeout.as_secs(), "script": p.file.display().to_string(), }) }) .collect(); crate::output::print_json(&items); return Ok(0); } if all.is_empty() { app.log.info(&format!( "no plugins in {} — copy one from examples/plugins/ (notify, ci-webhook)", dir.display() )); return Ok(0); } let mut table = crate::output::Table::new(vec!["PLUGIN", "EVENTS", "TIMEOUT", "SCRIPT"]); for p in &all { table.row(vec![ p.name.clone(), p.events.join(", "), format!("{}s", p.timeout.as_secs()), p.file.display().to_string(), ]); } print!("{}", table.render()); Ok(0) }