use anyhow::Result;
use clap::{Parser, Subcommand};
use std::fs;
use std::path::PathBuf;
use tracing::{info, warn};
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
mod config;
mod doctor;
mod project;
use config::BlincConfig;
#[derive(Parser)]
#[command(name = "blinc")]
#[command(version = env!("CARGO_PKG_VERSION"))]
#[command(about = "Blinc UI Framework CLI", long_about = None)]
struct Cli {
#[arg(short, long, global = true)]
verbose: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Build {
#[arg(default_value = ".")]
source: String,
#[arg(short, long, default_value = "desktop")]
target: String,
#[arg(short, long)]
release: bool,
#[arg(short, long)]
output: Option<String>,
},
Dev {
#[arg(default_value = ".")]
source: String,
#[arg(short, long, default_value = "desktop")]
target: String,
#[arg(short, long, default_value = "3000")]
port: u16,
#[arg(long)]
device: Option<String>,
},
Run {
#[arg(default_value = ".")]
source: String,
},
Plugin {
#[command(subcommand)]
command: PluginCommands,
},
New {
name: String,
#[arg(short, long, default_value = "default")]
template: String,
#[arg(short, long, default_value = "com.example")]
org: String,
#[arg(long)]
rust: bool,
},
Init {
#[arg(short, long, default_value = "default")]
template: String,
#[arg(short, long, default_value = "com.example")]
org: String,
},
Check {
#[arg(default_value = ".")]
source: String,
},
Info,
Doctor,
}
#[derive(Subcommand)]
enum PluginCommands {
Build {
#[arg(default_value = ".")]
path: String,
#[arg(short, long, default_value = "dynamic")]
mode: String,
},
New {
name: String,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
let filter = if cli.verbose {
EnvFilter::new("debug")
} else {
EnvFilter::new("info")
};
tracing_subscriber::registry()
.with(fmt::layer())
.with(filter)
.init();
match cli.command {
Commands::Build {
source,
target,
release,
output,
} => cmd_build(&source, &target, release, output.as_deref()),
Commands::Dev {
source,
target,
port,
device,
} => cmd_dev(&source, &target, port, device.as_deref()),
Commands::Run { source } => cmd_run(&source),
Commands::Plugin { command } => match command {
PluginCommands::Build { path, mode } => cmd_plugin_build(&path, &mode),
PluginCommands::New { name } => cmd_plugin_new(&name),
},
Commands::New {
name,
template,
org,
rust,
} => cmd_new(&name, &template, &org, rust),
Commands::Init { template, org } => cmd_init(&template, &org),
Commands::Check { source } => cmd_check(&source),
Commands::Info => cmd_info(),
Commands::Doctor => cmd_doctor(),
}
}
fn cmd_build(source: &str, target: &str, release: bool, output: Option<&str>) -> Result<()> {
let path = PathBuf::from(source);
let config = BlincConfig::load_from_dir(&path)?;
info!(
"Building {} for {} ({})",
config.project.name,
target,
if release { "release" } else { "debug" }
);
let valid_targets = [
"desktop", "android", "ios", "macos", "windows", "linux", "wasm",
];
if !valid_targets.contains(&target) {
anyhow::bail!(
"Invalid target '{}'. Valid targets: {:?}",
target,
valid_targets
);
}
warn!("Build not yet implemented - waiting for Zyntax Grammar2");
if let Some(out) = output {
info!("Output will be written to: {}", out);
}
Ok(())
}
fn cmd_dev(source: &str, target: &str, port: u16, device: Option<&str>) -> Result<()> {
let path = PathBuf::from(source);
let config = BlincConfig::load_from_dir(&path)?;
info!(
"Starting dev server for {} on port {} targeting {}",
config.project.name, port, target
);
if let Some(dev) = device {
info!("Running on device: {}", dev);
}
warn!("Dev server not yet implemented - waiting for Zyntax Runtime2");
Ok(())
}
fn cmd_run(source: &str) -> Result<()> {
info!("Running {}", source);
warn!("Run not yet implemented - waiting for Zyntax Runtime2");
Ok(())
}
fn cmd_plugin_build(path: &str, mode: &str) -> Result<()> {
info!("Building plugin at {} (mode: {})", path, mode);
let valid_modes = ["dynamic", "static"];
if !valid_modes.contains(&mode) {
anyhow::bail!("Invalid mode '{}'. Valid modes: {:?}", mode, valid_modes);
}
warn!("Plugin build not yet implemented");
Ok(())
}
fn cmd_plugin_new(name: &str) -> Result<()> {
info!("Creating new plugin: {}", name);
let path = PathBuf::from(name);
if path.exists() {
anyhow::bail!("Directory '{}' already exists", name);
}
fs::create_dir_all(&path)?;
project::create_plugin_project(&path, name)?;
info!("Plugin created at {}/", name);
Ok(())
}
fn cmd_new(name: &str, template: &str, org: &str, rust: bool) -> Result<()> {
let path = PathBuf::from(name);
let project_name = path.file_name().and_then(|n| n.to_str()).unwrap_or(name);
if rust {
info!("Creating new Rust project: {}", project_name);
} else {
info!(
"Creating new project: {} (template: {})",
project_name, template
);
}
info!("Organization prefix: {}", org);
if path.exists() {
anyhow::bail!("Directory '{}' already exists", name);
}
fs::create_dir_all(&path)?;
if rust {
project::create_rust_project(&path, project_name, org)?;
info!("Rust project created at {}/", name);
info!("To get started:");
info!(" cd {}", name);
info!(" cargo run --features desktop");
} else {
project::create_project(&path, name, template, org)?;
info!("Project created at {}/", name);
info!("To get started:");
info!(" cd {}", name);
info!(" blinc dev");
}
Ok(())
}
fn cmd_init(template: &str, org: &str) -> Result<()> {
let cwd = std::env::current_dir()?;
let name = cwd
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("blinc_app");
info!(
"Initializing Blinc project in current directory (template: {})",
template
);
info!("Organization prefix: {}", org);
if cwd.join(".blincproj").exists() {
anyhow::bail!("This directory already contains a .blincproj");
}
if cwd.join("blinc.toml").exists() {
anyhow::bail!("This directory already contains a blinc.toml (legacy format)");
}
project::create_project(&cwd, name, template, org)?;
info!("Project initialized!");
info!("Run `blinc dev` to start development");
Ok(())
}
fn cmd_check(source: &str) -> Result<()> {
let path = PathBuf::from(source);
let config = BlincConfig::load_from_dir(&path)?;
info!("Checking project: {}", config.project.name);
warn!("Check not yet implemented - waiting for Zyntax Grammar2");
Ok(())
}
fn cmd_info() -> Result<()> {
println!("Blinc UI Framework");
println!("==================");
println!();
let git_hash = option_env!("BLINC_GIT_HASH").unwrap_or("unknown");
println!("Version: {} ({})", env!("CARGO_PKG_VERSION"), git_hash);
println!();
println!("Supported targets:");
println!(" - desktop (native window)");
println!(" - macos");
println!(" - windows");
println!(" - linux");
println!(" - android");
println!(" - ios");
println!(" - wasm (WebGPU/WebGL2)");
println!();
println!("Build modes:");
println!(" - JIT (development, hot-reload) - requires Zyntax Runtime2");
println!(" - AOT (production) - requires Zyntax Grammar2");
println!();
println!("Status:");
println!(" - Core reactive system: Ready");
println!(" - FSM runtime: Ready");
println!(" - Animation system: Ready");
println!(" - Zyntax integration: Pending Grammar2/Runtime2");
Ok(())
}
fn cmd_doctor() -> Result<()> {
let categories = doctor::run_doctor();
doctor::print_doctor_results(&categories);
let has_errors = categories
.iter()
.any(|c| c.status() == doctor::CheckStatus::Error);
if has_errors {
std::process::exit(1);
}
Ok(())
}