greentic_setup/cli_commands/
inspect.rs1use anyhow::{Context, Result};
4
5use crate::bundle;
6use crate::cli_args::*;
7use crate::cli_helpers::{copy_dir_recursive, resolve_bundle_dir};
8use crate::cli_i18n::CliI18n;
9
10pub fn build(args: BundleBuildArgs, i18n: &CliI18n) -> Result<()> {
12 use crate::gtbundle;
13
14 let bundle_dir = resolve_bundle_dir(args.bundle)?;
15
16 bundle::validate_bundle_exists(&bundle_dir).context(i18n.t("cli.error.invalid_bundle"))?;
17
18 let out_str = args.out.to_string_lossy();
19 let is_archive = out_str.ends_with(".gtbundle");
20
21 println!("{}", i18n.t("cli.bundle.build.building"));
22 println!(
23 "{}",
24 i18n.tf(
25 "cli.bundle.add.bundle",
26 &[&bundle_dir.display().to_string()]
27 )
28 );
29 println!(
30 "{}",
31 i18n.tf(
32 "cli.bundle.build.output",
33 &[&args.out.display().to_string()]
34 )
35 );
36 println!(
37 " Format: {}",
38 if is_archive {
39 "archive (.gtbundle)"
40 } else {
41 "directory"
42 }
43 );
44
45 if let Some(ref tenant) = args.tenant {
46 println!("{}", i18n.tf("cli.bundle.add.tenant", &[tenant]));
47 }
48
49 if args.doctor && !args.skip_doctor {
50 println!("\n{}", i18n.t("cli.bundle.build.running_doctor"));
51 }
52
53 if is_archive {
54 gtbundle::create_gtbundle(&bundle_dir, &args.out)
55 .context("failed to create .gtbundle archive")?;
56 } else {
57 std::fs::create_dir_all(&args.out).context("failed to create output directory")?;
58 copy_dir_recursive(&bundle_dir, &args.out, args.only_used_providers)
59 .context("failed to copy bundle")?;
60 }
61
62 println!(
63 "\n{}",
64 i18n.tf(
65 "cli.bundle.build.success",
66 &[&args.out.display().to_string()]
67 )
68 );
69
70 Ok(())
71}
72
73pub fn list(args: BundleListArgs, i18n: &CliI18n) -> Result<()> {
75 let bundle_dir = resolve_bundle_dir(args.bundle)?;
76
77 bundle::validate_bundle_exists(&bundle_dir).context(i18n.t("cli.error.invalid_bundle"))?;
78
79 let mut packs = Vec::new();
80 let providers_dir = bundle_dir.join("providers");
81 let packs_dir = bundle_dir.join("packs");
82
83 let domain_dir = providers_dir.join(&args.domain);
84 if domain_dir.exists()
85 && let Ok(entries) = std::fs::read_dir(&domain_dir)
86 {
87 for entry in entries.flatten() {
88 let path = entry.path();
89 if path.extension().is_some_and(|e| e == "gtpack")
90 && let Some(name) = path.file_stem().and_then(|n| n.to_str())
91 {
92 packs.push((name.to_string(), args.domain.clone()));
93 }
94 }
95 }
96
97 if packs_dir.exists()
98 && let Ok(entries) = std::fs::read_dir(&packs_dir)
99 {
100 for entry in entries.flatten() {
101 let path = entry.path();
102 if path.extension().is_some_and(|e| e == "gtpack")
103 && let Some(name) = path.file_stem().and_then(|n| n.to_str())
104 {
105 packs.push((name.to_string(), "pack".to_string()));
106 }
107 }
108 }
109
110 if let Some(ref pack_filter) = args.pack {
111 packs.retain(|(name, _)| name.contains(pack_filter));
112 }
113
114 if args.format == "json" {
115 let output = serde_json::json!({
116 "bundle": bundle_dir.display().to_string(),
117 "domain": args.domain,
118 "pack_count": packs.len(),
119 "packs": packs.iter().map(|(name, domain)| {
120 serde_json::json!({
121 "name": name,
122 "domain": domain,
123 })
124 }).collect::<Vec<_>>(),
125 });
126 println!("{}", serde_json::to_string_pretty(&output)?);
127 } else {
128 println!(
129 "{}",
130 i18n.tf(
131 "cli.bundle.list.bundle",
132 &[&bundle_dir.display().to_string()]
133 )
134 );
135 println!("{}", i18n.tf("cli.bundle.list.domain", &[&args.domain]));
136 println!(
137 "{}",
138 i18n.tf("cli.bundle.list.packs_found", &[&packs.len().to_string()])
139 );
140
141 for (name, domain) in &packs {
142 println!(" - {} ({})", name, domain);
143 }
144 }
145
146 Ok(())
147}
148
149pub fn status(args: BundleStatusArgs, i18n: &CliI18n) -> Result<()> {
151 let bundle_dir = resolve_bundle_dir(args.bundle)?;
152
153 if !bundle_dir.exists() {
154 if args.format == "json" {
155 println!(r#"{{"exists": false, "path": "{}"}}"#, bundle_dir.display());
156 } else {
157 println!(
158 "{}",
159 i18n.tf(
160 "cli.bundle.status.not_found",
161 &[&bundle_dir.display().to_string()]
162 )
163 );
164 }
165 return Ok(());
166 }
167
168 let is_valid = bundle::is_bundle_root(&bundle_dir);
169
170 let providers_dir = bundle_dir.join("providers");
171 let packs_dir = bundle_dir.join("packs");
172 let mut pack_count = 0;
173 let mut packs = Vec::new();
174
175 if providers_dir.exists() {
176 for domain in &[
177 "messaging",
178 "events",
179 "oauth",
180 "secrets",
181 "mcp",
182 "state",
183 "other",
184 ] {
185 let domain_dir = providers_dir.join(domain);
186 if domain_dir.exists()
187 && let Ok(entries) = std::fs::read_dir(&domain_dir)
188 {
189 for entry in entries.flatten() {
190 let path = entry.path();
191 if path.extension().is_some_and(|e| e == "gtpack") {
192 pack_count += 1;
193 if let Some(name) = path.file_stem().and_then(|n| n.to_str()) {
194 packs.push(format!("providers/{}/{}", domain, name));
195 }
196 }
197 }
198 }
199 }
200 }
201
202 if packs_dir.exists()
203 && let Ok(entries) = std::fs::read_dir(&packs_dir)
204 {
205 for entry in entries.flatten() {
206 let path = entry.path();
207 if path.extension().is_some_and(|e| e == "gtpack") {
208 pack_count += 1;
209 if let Some(name) = path.file_stem().and_then(|n| n.to_str()) {
210 packs.push(format!("packs/{}", name));
211 }
212 }
213 }
214 }
215
216 let tenants_dir = bundle_dir.join("tenants");
217 let mut tenant_count = 0;
218 let mut tenants = Vec::new();
219
220 if tenants_dir.exists()
221 && let Ok(entries) = std::fs::read_dir(&tenants_dir)
222 {
223 for entry in entries.flatten() {
224 if entry.path().is_dir() {
225 tenant_count += 1;
226 if let Some(name) = entry.file_name().to_str() {
227 tenants.push(name.to_string());
228 }
229 }
230 }
231 }
232
233 if args.format == "json" {
234 let status = serde_json::json!({
235 "exists": true,
236 "valid": is_valid,
237 "path": bundle_dir.display().to_string(),
238 "pack_count": pack_count,
239 "packs": packs,
240 "tenant_count": tenant_count,
241 "tenants": tenants,
242 });
243 println!("{}", serde_json::to_string_pretty(&status)?);
244 } else {
245 println!(
246 "{}",
247 i18n.tf(
248 "cli.bundle.status.bundle_label",
249 &[&bundle_dir.display().to_string()]
250 )
251 );
252 let valid_status = if is_valid {
253 i18n.t("cli.bundle.status.valid_yes")
254 } else {
255 i18n.t("cli.bundle.status.valid_no")
256 };
257 println!("Valid: {}", valid_status);
258 println!(
259 "{}",
260 i18n.tf("cli.bundle.status.packs", &[&pack_count.to_string()])
261 );
262 for pack in &packs {
263 println!(" - {}", pack);
264 }
265 println!(
266 "{}",
267 i18n.tf("cli.bundle.status.tenants", &[&tenant_count.to_string()])
268 );
269 for tenant in &tenants {
270 println!(" - {}", tenant);
271 }
272 }
273
274 Ok(())
275}