use std::env;
use std::path::Path;
use super::output_formatter::OutputFormatter;
#[derive(Debug, Default)]
pub struct SystemInfo {
pub amberjs_version: String,
pub os: String,
pub arch: String,
pub cpu_count: usize,
pub total_memory_mb: u64,
pub available_memory_mb: u64,
pub v8_version: String,
pub rust_version: String,
pub cwd: String,
pub home_dir: String,
pub temp_dir: String,
pub is_ci: bool,
pub is_docker: bool,
pub global_packages: usize,
}
impl SystemInfo {
pub fn collect() -> Self {
let mut info = Self::default();
info.amberjs_version = env!("CARGO_PKG_VERSION").to_string();
info.os = Self::get_os_info();
info.arch = std::env::consts::ARCH.to_string();
info.cpu_count = num_cpus::get();
info.total_memory_mb = Self::get_total_memory_mb();
info.available_memory_mb = Self::get_available_memory_mb();
info.v8_version = Self::get_v8_version();
info.rust_version = Self::get_rust_version();
info.cwd = env::current_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| "unknown".to_string());
info.home_dir = dirs::home_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| "unknown".to_string());
info.temp_dir = env::temp_dir().to_string_lossy().to_string();
info.is_ci = Self::detect_ci();
info.is_docker = Self::detect_docker();
info
}
fn get_os_info() -> String {
let os_name: _ = std::env::consts::OS;
let family: _ = std::env::consts::FAMILY;
#[cfg(target_os = "macos")]
{
if let Ok(output) = std::process::Command::new("sw_vers")
.arg("-productVersion")
.output()
{
if output.status.success() {
let version: _ = String::from_utf8_lossy(&output.stdout);
return format!("macOS {}", version.trim());
}
}
}
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/etc/os-release") {
for line in content.lines() {
if line.starts_with("PRETTY_NAME=") {
let name: _ = line.trim_start_matches("PRETTY_NAME=").trim_matches('"');
return name.to_string();
}
}
}
}
format!("{} ({})", os_name, family)
}
fn get_total_memory_mb() -> u64 {
#[cfg(target_os = "macos")]
{
if let Ok(output) = std::process::Command::new("sysctl")
.args(["-n", "hw.memsize"])
.output()
{
if output.status.success() {
if let Ok(bytes) = String::from_utf8_lossy(&output.stdout)
.trim()
.parse::<u64>()
{
return bytes / (1024 * 1024);
}
}
}
}
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/proc/meminfo") {
for line in content.lines() {
if line.starts_with("MemTotal:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
if let Ok(kb) = parts[1].parse::<u64>() {
return kb / 1024;
}
}
}
}
}
}
0
}
fn get_available_memory_mb() -> u64 {
#[cfg(target_os = "macos")]
{
if let Ok(output) = std::process::Command::new("vm_stat").output() {
if output.status.success() {
let content: _ = String::from_utf8_lossy(&output.stdout);
let mut free_pages = 0u64;
for line in content.lines() {
if line.starts_with("Pages free:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if let Some(pages) = parts.last() {
if let Ok(p) = pages.trim_end_matches('.').parse::<u64>() {
free_pages = p;
}
}
}
}
return (free_pages * 4096) / (1024 * 1024);
}
}
}
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/proc/meminfo") {
for line in content.lines() {
if line.starts_with("MemAvailable:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
if let Ok(kb) = parts[1].parse::<u64>() {
return kb / 1024;
}
}
}
}
}
}
0
}
fn get_v8_version() -> String {
"10.x (rusty_v8 0.22)".to_string()
}
fn get_rust_version() -> String {
if let Ok(output) = std::process::Command::new("rustc")
.arg("--version")
.output()
{
if output.status.success() {
return String::from_utf8_lossy(&output.stdout).trim().to_string();
}
}
"unknown".to_string()
}
fn detect_ci() -> bool {
env::var("CI").is_ok()
|| env::var("GITHUB_ACTIONS").is_ok()
|| env::var("GITLAB_CI").is_ok()
|| env::var("CIRCLECI").is_ok()
|| env::var("TRAVIS").is_ok()
|| env::var("JENKINS_URL").is_ok()
}
fn detect_docker() -> bool {
Path::new("/.dockerenv").exists()
|| std::fs::read_to_string("/proc/1/cgroup")
.map(|s| s.contains("docker"))
.unwrap_or(false)
}
}
pub struct InfoCommand {
formatter: OutputFormatter,
verbose: bool,
}
impl InfoCommand {
pub fn new(verbose: bool) -> Self {
Self {
formatter: OutputFormatter::with_verbose(verbose),
verbose,
}
}
pub fn execute(&self) -> anyhow::Result<()> {
let info: _ = SystemInfo::collect();
self.formatter.print_banner();
self.formatter.title("Runtime Information");
self.formatter
.key_value_with_icon("📦", "Amber Version", &info.amberjs_version);
self.formatter
.key_value_with_icon("⚙️", "V8 Engine", &info.v8_version);
self.formatter
.key_value_with_icon("🦀", "Rust", &info.rust_version);
self.formatter.title("System Information");
self.formatter
.key_value_with_icon("💻", "Operating System", &info.os);
self.formatter
.key_value_with_icon("🔧", "Architecture", &info.arch);
self.formatter
.key_value_with_icon("🧠", "CPU Cores", &info.cpu_count.to_string());
if info.total_memory_mb > 0 {
self.formatter.key_value_with_icon(
"💾",
"Memory",
&format!(
"{} MB total, {} MB available",
info.total_memory_mb, info.available_memory_mb
),
);
}
self.formatter.title("Environment");
self.formatter
.key_value_with_icon("📂", "Working Directory", &info.cwd);
self.formatter
.key_value_with_icon("🏠", "Home Directory", &info.home_dir);
if self.verbose {
self.formatter
.key_value_with_icon("📁", "Temp Directory", &info.temp_dir);
}
self.formatter.title("Environment Detection");
if info.is_ci {
self.formatter
.key_value_with_icon("🔄", "CI Environment", "Yes");
} else {
self.formatter
.key_value_with_icon("🔄", "CI Environment", "No");
}
if info.is_docker {
self.formatter
.key_value_with_icon("🐳", "Docker Container", "Yes");
} else if self.verbose {
self.formatter
.key_value_with_icon("🐳", "Docker Container", "No");
}
self.formatter.title("Available Features");
self.print_features();
println!();
Ok(())
}
fn print_features(&self) {
let features: _ = [
("TypeScript", true, "Native TypeScript execution"),
("ESM Modules", true, "ES Module support"),
("CommonJS", true, "CommonJS module support"),
("WebSocket", true, "WebSocket client/server"),
("Fetch API", true, "Global fetch function"),
("Web APIs", true, "Standard Web APIs"),
("File System", true, "Node.js fs compatibility"),
("Workers", true, "Web Workers support"),
("WASM", true, "WebAssembly support"),
("Hot Reload", true, "File watching and hot reload"),
];
for (name, enabled, desc) in features {
let status: _ = if enabled { "✓" } else { "✗" };
let status_color: _ = if enabled { "\x1b[32m" } else { "\x1b[31m" };
if self.formatter.color_enabled {
println!(
" {}{}\x1b[0m {} {}",
status_color,
status,
name,
if self.verbose {
format!("- {}", desc)
} else {
String::new()
}
);
} else {
println!(
" {} {} {}",
status,
name,
if self.verbose {
format!("- {}", desc)
} else {
String::new()
}
);
}
}
}
pub fn execute_json(&self) -> anyhow::Result<()> {
let info: _ = SystemInfo::collect();
let json: _ = serde_json::json!({
"amberjs": {
"version": info.amberjs_version,
"v8_version": info.v8_version,
},
"system": {
"os": info.os,
"arch": info.arch,
"cpu_count": info.cpu_count,
"total_memory_mb": info.total_memory_mb,
"available_memory_mb": info.available_memory_mb,
},
"environment": {
"cwd": info.cwd,
"home_dir": info.home_dir,
"temp_dir": info.temp_dir,
"is_ci": info.is_ci,
"is_docker": info.is_docker,
}
});
println!("{}", serde_json::to_string_pretty(&json)?);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::{HashMap, BTreeMap};
use std::fs::File;
use anyhow::{Result, Error};
use std::time::Duration;
#[test]
fn test_system_info_collect() {
let info: _ = SystemInfo::collect();
assert!(!info.amberjs_version.is_empty());
assert!(!info.os.is_empty());
assert!(!info.arch.is_empty());
assert!(info.cpu_count > 0);
}
#[test]
fn test_info_command_execute() {
let cmd: _ = InfoCommand::new(false);
assert!(cmd.execute().is_ok());
}
#[test]
fn test_info_command_json() {
let cmd: _ = InfoCommand::new(false);
assert!(cmd.execute_json().is_ok());
}
}