ready_set/builtins/
list.rs1use 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#[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
30pub 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}