use anyhow::{Context, Result};
use console::{style, Emoji};
use std::collections::HashMap;
pub fn info(json: bool) -> Result<()> {
println!();
if json {
print_info_json()?;
} else {
print_info_pretty()?;
}
Ok(())
}
fn print_info_pretty() -> Result<()> {
println!("{} {}", Emoji("ℹ️", ""), style("Project Information").bold());
println!("{}", style("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━").dim());
println!();
let project_dir = std::env::current_dir()?;
let project_name = project_dir
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "unknown".to_string());
println!(" {} {}", style("Project:").dim(), style(&project_name).cyan().bold());
let cargo_path = project_dir.join("Cargo.toml");
let cargo_content = if cargo_path.exists() {
std::fs::read_to_string(&cargo_path).ok()
} else {
None
};
if let Some(ref content) = cargo_content {
if let Some(version) = extract_from_cargo(content, "version") {
println!(" {} {}", style("Version:").dim(), style(&version).cyan());
}
if let Some(name) = extract_from_cargo(content, "name") {
println!(" {} {}", style("Package:").dim(), style(&name).cyan());
}
}
println!();
println!(" {} {}", style("Structure:").dim(), style("✓ Found").green());
let dirs = [
("src/", "Source"),
("internal/", "Internal"),
("migrations/", "Migrations"),
("manifest/config/", "Config"),
("tests/", "Tests"),
];
for (dir, label) in dirs {
let path = project_dir.join(dir);
let status = if path.exists() {
style("✓").green()
} else {
style("✗").red()
};
println!(" {} {:20} {}", status, label, style(dir).dim());
}
println!();
println!(" {} {}", style("Dependencies:").dim(), style("Checking...").dim());
let rust_version = get_rust_version();
let cargo_version = get_cargo_version();
println!(" {} {}", style("Rust:").dim(), style(&rust_version).cyan());
println!(" {} {}", style("Cargo:").dim(), style(&cargo_version).cyan());
if let Some(ref content) = cargo_content {
if let Some(v) = get_kegani_version(content) {
println!(" {} {}", style("Kegani:").dim(), style(&v).cyan());
}
}
println!();
let db_url = std::env::var("DATABASE_URL").ok();
if let Some(url) = &db_url {
let masked = mask_database_url(url);
println!(" {} {}", style("Database:").dim(), style(&masked).cyan());
} else {
println!(" {} {}", style("Database:").dim(), style("Not configured").yellow());
}
let redis_url = std::env::var("REDIS_URL").ok();
if let Some(url) = &redis_url {
println!(" {} {}", style("Redis:").dim(), style(url).cyan());
} else {
println!(" {} {}", style("Redis:").dim(), style("Not configured").yellow());
}
println!();
println!("{} {}", Emoji("✨", ""), style("Done").dim());
println!();
Ok(())
}
fn print_info_json() -> Result<()> {
let project_dir = std::env::current_dir()?;
let project_name = project_dir
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "unknown".to_string());
let cargo_content = std::fs::read_to_string(project_dir.join("Cargo.toml")).ok();
let mut info = HashMap::new();
info.insert("project", project_name.clone());
info.insert("directory", project_dir.to_string_lossy().to_string());
if let Some(ref cargo) = cargo_content {
if let Some(version) = extract_from_cargo(cargo, "version") {
info.insert("version", version);
}
if let Some(name) = extract_from_cargo(cargo, "name") {
info.insert("package", name);
}
}
info.insert("rust_version", get_rust_version());
info.insert("cargo_version", get_cargo_version());
if let Some(ref cargo) = cargo_content {
if let Some(v) = get_kegani_version(cargo) {
info.insert("kegani_version", v);
}
}
if let Ok(db_url) = std::env::var("DATABASE_URL") {
info.insert("database_url", mask_database_url(&db_url));
}
if let Ok(redis_url) = std::env::var("REDIS_URL") {
info.insert("redis_url", redis_url);
}
let json = serde_json::to_string_pretty(&info).context("Failed to serialize to JSON")?;
println!("{}", json);
Ok(())
}
fn extract_from_cargo(content: &str, key: &str) -> Option<String> {
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with(key) && trimmed.contains('=') {
if let Some(eq_pos) = trimmed.find('=') {
let value = trimmed[eq_pos + 1..].trim().trim_matches('"').trim_matches('\'');
return Some(value.to_string());
}
}
}
None
}
fn get_rust_version() -> String {
std::process::Command::new("rustc")
.arg("--version")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "not found".to_string())
}
fn get_cargo_version() -> String {
std::process::Command::new("cargo")
.arg("--version")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "not found".to_string())
}
fn get_kegani_version(cargo_content: &str) -> Option<String> {
extract_from_cargo(cargo_content, "kegani")
}
fn mask_database_url(url: &str) -> String {
if let Some(at_pos) = url.find('@') {
if let Some(colon_pos) = url[..at_pos].find(':') {
let before = &url[..colon_pos];
let after = &url[at_pos..];
return format!("{}:***{}", before, after);
}
}
url.to_string()
}