Skip to main content

ready_set/builtins/
list.rs

1//! `ready-set --list`.
2//!
3//! Walks PATH, resolves metadata via the cache → sidecar → `__describe`
4//! waterfall, prints results in human or JSON.
5
6use std::ffi::OsString;
7
8use ready_set_sdk::ExitCode;
9use ready_set_sdk::OutputMode;
10use ready_set_sdk::describe::Platform;
11use serde::Serialize;
12
13use crate::cache::PluginCache;
14use crate::discovery::list_all;
15use crate::env::EnvContract;
16use crate::metadata::resolve_metadata;
17
18/// One row of `--list` output.
19#[derive(Debug, Serialize)]
20struct Row {
21    kind: &'static str,
22    name: String,
23    description: Option<String>,
24    version: Option<String>,
25    stability: Option<String>,
26    binary_path: Option<String>,
27    platforms: Option<Vec<String>>,
28}
29
30/// Run `ready-set --list`.
31pub fn run(args: &[OsString], contract: &EnvContract) -> ExitCode {
32    let all = args.iter().any(|a| a == "--all");
33
34    let mut rows: Vec<Row> = ["ready", "set", "go", "help", "version", "list"]
35        .into_iter()
36        .map(builtin_row)
37        .collect();
38
39    let cache_path = PluginCache::default_path();
40    let mut cache = cache_path
41        .as_deref()
42        .map_or_else(PluginCache::default, PluginCache::load);
43    let mut cache_dirty = false;
44
45    let plugins = list_all();
46    let here = Platform::current();
47    for entry in plugins {
48        let manifest = resolve_metadata(&entry, &mut cache);
49        if manifest.is_some() {
50            cache_dirty = true;
51        }
52        if let Some(m) = manifest.as_ref()
53            && !all
54            && here.is_some_and(|p| !m.platforms.contains(&p))
55        {
56            continue;
57        }
58        rows.push(Row {
59            kind: "plugin",
60            name: entry.name.clone(),
61            description: manifest.as_ref().map(|m| m.description.clone()),
62            version: manifest.as_ref().map(|m| m.version.to_string()),
63            stability: manifest.as_ref().map(|m| match m.stability {
64                ready_set_sdk::describe::Stability::Stable => "stable".into(),
65                ready_set_sdk::describe::Stability::Experimental => "experimental".into(),
66                ready_set_sdk::describe::Stability::Deprecated => "deprecated".into(),
67            }),
68            binary_path: Some(entry.binary_path.display().to_string()),
69            platforms: manifest.as_ref().map(|m| {
70                m.platforms
71                    .iter()
72                    .map(|p| match p {
73                        Platform::Linux => "linux".into(),
74                        Platform::Macos => "macos".into(),
75                        Platform::Windows => "windows".into(),
76                    })
77                    .collect()
78            }),
79        });
80    }
81
82    if cache_dirty && let Some(path) = cache_path.as_deref() {
83        drop(cache.save(path));
84    }
85
86    match contract.output {
87        OutputMode::Json => match serde_json::to_string(&rows) {
88            Ok(s) => {
89                println!("{s}");
90                ExitCode::Ok
91            },
92            Err(err) => {
93                eprintln!("ready-set: failed to serialize --list output: {err}");
94                ExitCode::SystemError
95            },
96        },
97        OutputMode::Human => {
98            print_human(&rows);
99            ExitCode::Ok
100        },
101    }
102}
103
104fn builtin_row(name: &str) -> Row {
105    let description = match name {
106        "ready" => "Diagnose product capabilities and show the readiness matrix.",
107        "set" => "Configure or reconcile required product capabilities.",
108        "go" => "Execute provider-backed capability workflows.",
109        "help" => "Print dispatcher help.",
110        "version" => "Print the dispatcher version.",
111        "list" => "List built-in and discovered plugin subcommands.",
112        _ => unreachable!("unknown built-in row"),
113    };
114
115    Row {
116        kind: "builtin",
117        name: name.into(),
118        description: Some(description.into()),
119        version: Some(env!("CARGO_PKG_VERSION").into()),
120        stability: Some("stable".into()),
121        binary_path: None,
122        platforms: Some(vec!["linux".into(), "macos".into(), "windows".into()]),
123    }
124}
125
126fn print_human(rows: &[Row]) {
127    let name_width = rows.iter().map(|r| r.name.len()).max().unwrap_or(8).max(8);
128    println!(
129        "{:<width$}  KIND     DESCRIPTION",
130        "NAME",
131        width = name_width
132    );
133    for row in rows {
134        let desc = row
135            .description
136            .as_deref()
137            .unwrap_or("(metadata unavailable)");
138        println!(
139            "{:<width$}  {:<8} {desc}",
140            row.name,
141            row.kind,
142            width = name_width
143        );
144    }
145}