#![allow(dead_code)]
use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum};
use clap_complete::{Shell, generate};
use colored::Colorize;
use dialoguer::{Confirm, FuzzySelect, Input, MultiSelect, theme::ColorfulTheme};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::io;
use std::time::Duration;
mod commands;
mod error;
mod generators;
mod templates;
mod watcher;
use commands::{build, config, dev, generate, info, mock, new, openapi, repl, routes};
use error::{CliError, CliResult};
#[derive(Parser)]
#[command(name = "armature")]
#[command(author = "Pegasus Heavy Industries LLC")]
#[command(version)]
#[command(about = "🔧 CLI tool for Armature framework - code generation and development server")]
#[command(long_about = None)]
#[command(propagate_version = true)]
#[command(after_help = format!(
"{}\n {} armature new my-api\n {} armature dev\n {} armature g controller users --crud\n {} armature routes\n {} armature deploy --provider aws\n\n{}\n {} https://github.com/pegasusheavy/armature",
"Examples:".bright_cyan().bold(),
"$".dimmed(),
"$".dimmed(),
"$".dimmed(),
"$".dimmed(),
"$".dimmed(),
"Documentation:".bright_cyan().bold(),
"→".dimmed()
))]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(long, global = true)]
no_color: bool,
#[arg(short, long, global = true)]
verbose: bool,
#[arg(short, long, global = true)]
quiet: bool,
}
#[derive(Subcommand)]
enum Commands {
#[command(alias = "n", visible_alias = "create")]
New(NewArgs),
#[command(alias = "g")]
Generate {
#[command(subcommand)]
generator: GeneratorType,
},
#[command(alias = "d")]
Dev(DevArgs),
#[command(alias = "s", visible_alias = "start")]
Serve(ServeArgs),
#[command(alias = "b")]
Build(BuildArgs),
#[command(alias = "r")]
Routes(RoutesArgs),
#[command(alias = "c")]
Config(ConfigArgs),
#[command(visible_alias = "check")]
Doctor,
#[command(alias = "up")]
Upgrade(UpgradeArgs),
Db {
#[command(subcommand)]
command: DbCommands,
},
Deploy(DeployArgs),
Repl(ReplArgs),
Info,
Completions {
#[arg(value_enum)]
shell: Shell,
},
Docs,
Plugin {
#[command(subcommand)]
command: PluginCommands,
},
Bench(BenchArgs),
Lint(LintArgs),
Clean,
#[command(alias = "a")]
Add(AddArgs),
#[command(alias = "check")]
Validate(ValidateArgs),
Openapi {
#[command(subcommand)]
command: OpenapiCommands,
},
#[command(alias = "m")]
Mock(MockArgs),
}
#[derive(Args)]
struct MockArgs {
#[arg(short, long, default_value = "openapi.yaml")]
spec: String,
#[arg(short, long, default_value = "3000")]
port: u16,
#[arg(long, default_value = "127.0.0.1")]
host: String,
#[arg(short, long)]
data: Option<String>,
#[arg(long, default_value = "0")]
delay: u64,
#[arg(long, default_value = "true")]
cors: bool,
#[arg(long)]
seed: Option<u64>,
#[arg(short, long)]
watch: bool,
}
#[derive(Args)]
struct NewArgs {
name: Option<String>,
#[arg(short, long, value_enum)]
template: Option<ProjectTemplate>,
#[arg(long)]
skip_git: bool,
#[arg(long)]
skip_install: bool,
#[arg(short, long)]
interactive: bool,
#[arg(long, value_enum)]
database: Option<DatabaseType>,
#[arg(long)]
docker: bool,
#[arg(long)]
ci: bool,
}
#[derive(Clone, ValueEnum)]
enum ProjectTemplate {
Minimal,
Full,
Microservice,
Graphql,
Grpc,
Lambda,
Cloudrun,
}
#[derive(Clone, ValueEnum)]
enum DatabaseType {
Postgres,
Mysql,
Sqlite,
Mongodb,
Redis,
None,
}
#[derive(Subcommand)]
enum GeneratorType {
#[command(alias = "c")]
Controller {
name: String,
#[arg(long)]
crud: bool,
#[arg(long)]
skip_tests: bool,
#[arg(long)]
auth: bool,
},
#[command(alias = "m")]
Module {
name: String,
#[arg(short, long)]
controllers: Option<String>,
#[arg(short, long)]
providers: Option<String>,
},
#[command(alias = "mw")]
Middleware {
name: String,
#[arg(long)]
skip_tests: bool,
},
#[command(alias = "gu")]
Guard {
name: String,
#[arg(long)]
skip_tests: bool,
#[arg(long, value_enum, default_value = "custom")]
guard_type: GuardType,
},
#[command(alias = "s")]
Service {
name: String,
#[arg(long)]
skip_tests: bool,
},
#[command(alias = "r")]
Resource {
name: String,
#[arg(long)]
crud: bool,
},
Model {
name: String,
#[arg(short, long)]
fields: Option<String>,
#[arg(long)]
migration: bool,
},
Job {
name: String,
#[arg(long, value_enum, default_value = "async")]
job_type: JobType,
},
Event {
name: String,
},
Dto {
name: String,
#[arg(short, long)]
fields: Option<String>,
},
Scaffold {
name: String,
#[arg(short, long)]
fields: Option<String>,
},
#[command(alias = "repo")]
Repository {
name: String,
#[arg(long)]
skip_tests: bool,
},
#[command(alias = "ws")]
Websocket {
name: String,
#[arg(long)]
skip_tests: bool,
},
#[command(alias = "gql")]
Graphql {
name: String,
#[arg(long)]
skip_tests: bool,
},
Interceptor {
name: String,
#[arg(long)]
skip_tests: bool,
},
Pipe {
name: String,
#[arg(long)]
skip_tests: bool,
},
#[command(alias = "filter")]
ExceptionFilter {
name: String,
#[arg(long)]
skip_tests: bool,
},
#[command(alias = "cfg")]
Config {
name: String,
},
#[command(alias = "ent")]
Entity {
name: String,
#[arg(long, short, default_value = "generic")]
orm: String,
},
#[command(alias = "prax-schema")]
PraxSchema {
name: String,
},
#[command(alias = "prax-repo")]
PraxRepository {
name: String,
#[arg(long)]
skip_tests: bool,
},
#[command(alias = "prax")]
PraxModule {
name: String,
},
#[command(alias = "task", alias = "cron")]
Scheduler {
name: String,
#[arg(long)]
skip_tests: bool,
},
#[command(alias = "cache")]
CacheService {
name: String,
#[arg(long)]
skip_tests: bool,
},
#[command(alias = "client")]
ApiClient {
name: String,
},
Health,
}
#[derive(Clone, ValueEnum)]
enum GuardType {
Custom,
Auth,
Role,
Permission,
ApiKey,
RateLimit,
}
#[derive(Clone, ValueEnum)]
enum JobType {
Async,
Scheduled,
Recurring,
}
#[derive(Args)]
struct DevArgs {
#[arg(short, long, default_value = "3000")]
port: u16,
#[arg(long, default_value = "127.0.0.1")]
host: String,
#[arg(short, long)]
open: bool,
#[arg(long)]
https: bool,
#[arg(last = true)]
cargo_args: Vec<String>,
}
#[derive(Args)]
struct ServeArgs {
#[arg(short, long, env = "PORT", default_value = "3000")]
port: u16,
#[arg(long, env = "HOST", default_value = "0.0.0.0")]
host: String,
#[arg(short, long, env = "WORKERS")]
workers: Option<usize>,
#[arg(long, default_value = "30")]
shutdown_timeout: u64,
}
#[derive(Args)]
struct BuildArgs {
#[arg(short, long)]
release: bool,
#[arg(long)]
target: Option<String>,
#[arg(long)]
docker: bool,
#[arg(long, default_value = "latest")]
tag: String,
#[arg(last = true)]
cargo_args: Vec<String>,
}
#[derive(Args)]
struct RoutesArgs {
#[arg(short, long)]
method: Option<String>,
#[arg(short, long)]
path: Option<String>,
#[arg(long, value_enum, default_value = "table")]
format: OutputFormat,
#[arg(long)]
middleware: bool,
}
#[derive(Clone, ValueEnum)]
enum OutputFormat {
Table,
Json,
Yaml,
Markdown,
}
#[derive(Args)]
struct ConfigArgs {
#[command(subcommand)]
command: Option<ConfigCommands>,
}
#[derive(Subcommand)]
enum ConfigCommands {
Validate {
file: Option<String>,
},
Show {
key: Option<String>,
},
Set {
key: String,
value: String,
},
Init {
#[arg(short, long, default_value = "development")]
env: String,
},
}
#[derive(Subcommand)]
enum DbCommands {
Create,
Drop {
#[arg(short, long)]
force: bool,
},
Migrate {
#[arg(short, long)]
steps: Option<u32>,
},
Rollback {
#[arg(short, long, default_value = "1")]
steps: u32,
},
Reset {
#[arg(short, long)]
force: bool,
},
Status,
Seed {
seeder: Option<String>,
},
#[command(alias = "g")]
Generate {
name: String,
},
}
#[derive(Args)]
struct DeployArgs {
#[arg(short, long, value_enum)]
provider: Option<CloudProvider>,
#[arg(short, long, default_value = "production")]
env: String,
#[arg(short, long)]
yes: bool,
#[arg(long)]
dry_run: bool,
}
#[derive(Clone, ValueEnum)]
enum CloudProvider {
Aws,
Gcp,
Azure,
Fly,
Railway,
Render,
Shuttle,
Docker,
}
#[derive(Args)]
struct UpgradeArgs {
#[arg(long)]
check: bool,
#[arg(long = "target-version")]
target_version: Option<String>,
}
#[derive(Args)]
struct ReplArgs {
#[arg(long)]
simple: bool,
}
#[derive(Args)]
struct AddArgs {
#[arg(value_enum)]
feature: ArmatureFeature,
#[arg(long)]
dry_run: bool,
}
#[derive(Debug, Clone, ValueEnum)]
enum ArmatureFeature {
Auth,
Cache,
Config,
Graphql,
Grpc,
Jwt,
Openapi,
Telemetry,
Ratelimit,
Queue,
Cron,
Security,
Storage,
Mail,
Push,
Testing,
Validation,
Realtime,
Compression,
Webhooks,
}
impl ArmatureFeature {
fn crate_name(&self) -> &'static str {
match self {
Self::Auth => "armature-auth",
Self::Cache => "armature-cache",
Self::Config => "armature-config",
Self::Graphql => "armature-graphql",
Self::Grpc => "armature-grpc",
Self::Jwt => "armature-jwt",
Self::Openapi => "armature-openapi",
Self::Telemetry => "armature-opentelemetry",
Self::Ratelimit => "armature-ratelimit",
Self::Queue => "armature-queue",
Self::Cron => "armature-cron",
Self::Security => "armature-security",
Self::Storage => "armature-storage",
Self::Mail => "armature-mail",
Self::Push => "armature-push",
Self::Testing => "armature-testing",
Self::Validation => "armature-validation",
Self::Realtime => "armature-core", Self::Compression => "armature-compression",
Self::Webhooks => "armature-webhooks",
}
}
fn feature_flag(&self) -> Option<&'static str> {
match self {
Self::Auth => Some("auth"),
Self::Cache => Some("cache"),
Self::Config => Some("config"),
Self::Graphql => Some("graphql"),
Self::Grpc => None, Self::Jwt => Some("jwt"),
Self::Openapi => Some("openapi"),
Self::Telemetry => Some("opentelemetry"),
Self::Ratelimit => Some("ratelimit"),
Self::Queue => Some("queue"),
Self::Cron => Some("cron"),
Self::Security => Some("security"),
Self::Storage => None, Self::Mail => None, Self::Push => None, Self::Testing => Some("testing"),
Self::Validation => Some("validation"),
Self::Realtime => None, Self::Compression => Some("compression"),
Self::Webhooks => Some("webhooks"),
}
}
fn description(&self) -> &'static str {
match self {
Self::Auth => "Authentication with JWT, OAuth2, and SAML support",
Self::Cache => "Caching with Redis and Memcached backends",
Self::Config => "Configuration management with env, JSON, TOML support",
Self::Graphql => "GraphQL API with async-graphql integration",
Self::Grpc => "gRPC server and client support",
Self::Jwt => "JSON Web Token authentication",
Self::Openapi => "OpenAPI/Swagger documentation generation",
Self::Telemetry => "OpenTelemetry tracing and metrics",
Self::Ratelimit => "Rate limiting with multiple algorithms",
Self::Queue => "Background job queue with Redis",
Self::Cron => "Scheduled job execution",
Self::Security => "Security middleware (CORS, CSP, HSTS)",
Self::Storage => "File storage (Local, S3, GCS, Azure)",
Self::Mail => "Email sending with SMTP and cloud providers",
Self::Push => "Push notifications (Web Push, FCM, APNS)",
Self::Testing => "Testing utilities and mocks",
Self::Validation => "Input validation framework",
Self::Realtime => "WebSocket and Server-Sent Events",
Self::Compression => "Response compression (gzip, brotli, zstd)",
Self::Webhooks => "Webhook sending and receiving",
}
}
}
#[derive(Args)]
struct ValidateArgs {
#[arg(long)]
config_only: bool,
#[arg(long)]
deps_only: bool,
#[arg(long)]
fix: bool,
}
#[derive(Subcommand)]
enum OpenapiCommands {
#[command(alias = "client")]
Client(OpenapiClientArgs),
Validate {
spec: String,
},
Generate {
#[arg(short, long, default_value = "openapi.yaml")]
output: String,
#[arg(long, value_enum, default_value = "yaml")]
format: SpecFormat,
},
}
#[derive(Args)]
struct OpenapiClientArgs {
spec: String,
#[arg(short, long, default_value = "generated-client")]
output: String,
#[arg(short, long, value_enum, default_value = "typescript")]
language: ClientLanguageArg,
#[arg(long)]
name: Option<String>,
#[arg(long)]
with_logging: bool,
#[arg(long)]
with_retry: bool,
}
#[derive(Clone, ValueEnum)]
enum ClientLanguageArg {
Typescript,
Rust,
Both,
}
#[derive(Clone, ValueEnum)]
enum SpecFormat {
Yaml,
Json,
}
#[derive(Subcommand)]
enum PluginCommands {
List,
Install {
name: String,
},
Uninstall {
name: String,
},
New {
name: String,
},
}
#[derive(Args)]
struct BenchArgs {
pattern: Option<String>,
#[arg(long)]
save: Option<String>,
#[arg(long)]
compare: Option<String>,
}
#[derive(Args)]
struct LintArgs {
#[arg(long)]
fix: bool,
#[arg(long)]
category: Option<String>,
}
fn print_banner() {
println!(
"{}",
r#"
╔═══════════════════════════════════════════════════════════════╗
║ ║
║ █████╗ ██████╗ ███╗ ███╗ █████╗ ████████╗██╗ ██╗ ║
║ ██╔══██╗██╔══██╗████╗ ████║██╔══██╗╚══██╔══╝██║ ██║ ║
║ ███████║██████╔╝██╔████╔██║███████║ ██║ ██║ ██║ ║
║ ██╔══██║██╔══██╗██║╚██╔╝██║██╔══██║ ██║ ██║ ██║ ║
║ ██║ ██║██║ ██║██║ ╚═╝ ██║██║ ██║ ██║ ╚██████╔╝ ║
║ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ║
║ ║
╚═══════════════════════════════════════════════════════════════╝"#
.bright_cyan()
);
println!(
" {} {} • {}\n",
"Armature".bright_white().bold(),
format!("v{}", env!("CARGO_PKG_VERSION")).bright_yellow(),
"Rust Web Framework".dimmed()
);
}
fn print_mini_banner() {
println!(
"\n {} {} {}\n",
"⚡".bright_yellow(),
"Armature CLI".bright_white().bold(),
format!("v{}", env!("CARGO_PKG_VERSION")).dimmed()
);
}
#[allow(dead_code)]
fn create_spinner(msg: &str) -> ProgressBar {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.cyan} {msg}")
.unwrap()
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
);
pb.set_message(msg.to_string());
pb.enable_steady_tick(Duration::from_millis(80));
pb
}
fn success(msg: &str) {
println!(" {} {}", "✓".green().bold(), msg.green());
}
fn warn(msg: &str) {
println!(" {} {}", "⚠".yellow().bold(), msg.yellow());
}
fn info(msg: &str) {
println!(" {} {}", "→".cyan(), msg);
}
#[allow(dead_code)]
fn step(num: u32, total: u32, msg: &str) {
println!(
" {} {}",
format!("[{}/{}]", num, total).dimmed(),
msg.bright_white()
);
}
async fn run_interactive_wizard() -> CliResult<()> {
print_banner();
println!(
" {}\n",
"Let's create your new Armature project! 🚀".bright_white()
);
let theme = ColorfulTheme::default();
let name: String = Input::with_theme(&theme)
.with_prompt(" What's your project name?")
.default("my-app".to_string())
.interact_text()
.map_err(|e| CliError::Io(e.into()))?;
println!();
let templates = vec![
"minimal - Minimal API with health checks",
"full - Full-featured API with auth, caching, etc.",
"microservice - Microservice with queue workers",
"graphql - GraphQL API with async-graphql",
"grpc - gRPC service with tonic",
];
let template_idx = FuzzySelect::with_theme(&theme)
.with_prompt(" Choose a template")
.items(&templates)
.default(0)
.interact()
.map_err(|e| CliError::Io(e.into()))?;
let template = match template_idx {
0 => "minimal",
1 => "full",
2 => "microservice",
3 => "graphql",
4 => "grpc",
_ => "minimal",
};
println!();
let databases = vec![
"PostgreSQL",
"MySQL",
"SQLite",
"MongoDB",
"None (skip database)",
];
let db_idx = FuzzySelect::with_theme(&theme)
.with_prompt(" Choose a database")
.items(&databases)
.default(0)
.interact()
.map_err(|e| CliError::Io(e.into()))?;
println!();
let features = vec![
"Authentication (JWT + OAuth2)",
"Rate Limiting",
"Caching (Redis)",
"Background Jobs",
"WebSockets",
"File Uploads",
"Email Sending",
"OpenTelemetry",
];
let selected_features = MultiSelect::with_theme(&theme)
.with_prompt(" Select additional features (space to select, enter to confirm)")
.items(&features)
.interact()
.map_err(|e| CliError::Io(e.into()))?;
println!();
let include_docker = Confirm::with_theme(&theme)
.with_prompt(" Include Docker configuration?")
.default(true)
.interact()
.map_err(|e| CliError::Io(e.into()))?;
let include_ci = Confirm::with_theme(&theme)
.with_prompt(" Include CI/CD configuration (GitHub Actions)?")
.default(true)
.interact()
.map_err(|e| CliError::Io(e.into()))?;
println!();
println!(" {}", "─".repeat(50).dimmed());
println!();
println!(" {} Creating project with:", "📋".bright_white());
println!(" {} {}", "Name:".dimmed(), name.cyan());
println!(" {} {}", "Template:".dimmed(), template.cyan());
println!(" {} {}", "Database:".dimmed(), databases[db_idx].cyan());
if !selected_features.is_empty() {
println!(
" {} {}",
"Features:".dimmed(),
selected_features
.iter()
.map(|i| features[*i].split(' ').next().unwrap())
.collect::<Vec<_>>()
.join(", ")
.cyan()
);
}
if include_docker {
println!(" {} Docker", "✓".green());
}
if include_ci {
println!(" {} CI/CD", "✓".green());
}
println!();
let proceed = Confirm::with_theme(&theme)
.with_prompt(" Proceed with project creation?")
.default(true)
.interact()
.map_err(|e| CliError::Io(e.into()))?;
if !proceed {
println!("\n {} Project creation cancelled.", "✗".red());
return Ok(());
}
println!();
new::run(&name, template, false, false).await?;
println!();
println!(" {}", "━".repeat(50).bright_cyan());
println!();
println!(
" {} {}",
"🎉".bright_yellow(),
"Your project is ready!".bright_white().bold()
);
println!();
println!(" {}", "Next steps:".bright_white());
println!();
println!(" {} cd {}", "1.".dimmed(), name.cyan());
println!(" {} armature dev", "2.".dimmed());
println!();
println!(" {}", "Useful commands:".bright_white());
println!();
println!(
" {} Generate a controller",
"armature g controller users --crud".cyan()
);
println!(" {} List all routes", "armature routes".cyan());
println!(
" {} Build for production",
"armature build --release".cyan()
);
println!();
Ok(())
}
fn run_add_command(args: AddArgs) -> CliResult<()> {
let feature = args.feature;
let crate_name = feature.crate_name();
let description = feature.description();
println!(
" {} Adding feature: {}",
"→".cyan(),
format!("{:?}", feature).to_lowercase().bright_white()
);
println!(" {} {}", "ℹ".dimmed(), description.dimmed());
println!();
if args.dry_run {
println!(" {} Dry run - no changes will be made\n", "⚠".yellow());
}
if !std::path::Path::new("Cargo.toml").exists() {
return Err(CliError::NotInProject);
}
let cargo_toml = std::fs::read_to_string("Cargo.toml").map_err(CliError::Io)?;
if cargo_toml.contains(crate_name) {
println!(
" {} Feature {} is already added to this project",
"✓".green(),
crate_name.cyan()
);
return Ok(());
}
println!(" {} Changes to Cargo.toml:", "📝".cyan());
if let Some(flag) = feature.feature_flag() {
println!(
" {} armature = {{ features = [\"{}\"] }}",
"+".green(),
flag.green()
);
} else {
println!(" {} {} = \"*\"", "+".green(), crate_name.green());
}
println!();
if args.dry_run {
println!(" {} No changes made (dry run)", "ℹ".dimmed());
return Ok(());
}
let cargo_add_result = if let Some(flag) = feature.feature_flag() {
std::process::Command::new("cargo")
.args(["add", "armature", "--features", flag])
.status()
} else {
std::process::Command::new("cargo")
.args(["add", crate_name])
.status()
};
match cargo_add_result {
Ok(status) if status.success() => {
success(&format!("Feature {} added successfully!", crate_name));
println!("\n {} Example usage:", "💡".yellow());
print_feature_usage_example(&feature);
}
Ok(_) => {
warn(&format!(
"cargo add failed. Try manually adding to Cargo.toml:\n\n {} = \"*\"",
crate_name
));
}
Err(_) => {
warn(&format!(
"cargo add not available. Add manually to Cargo.toml:\n\n {} = \"*\"",
crate_name
));
}
}
Ok(())
}
fn print_feature_usage_example(feature: &ArmatureFeature) {
let example = match feature {
ArmatureFeature::Auth => {
r#"
use armature_auth::prelude::*;
let service = AuthService::new();
let hash = service.hash_password("secret")?;
"#
}
ArmatureFeature::Cache => {
r#"
use armature_cache::prelude::*;
let cache = RedisCache::new(config).await?;
cache.set("key", "value", ttl).await?;
"#
}
ArmatureFeature::Jwt => {
r#"
use armature_jwt::prelude::*;
let manager = JwtManager::new(JwtConfig::new("secret"))?;
let token = manager.sign(&claims)?;
"#
}
ArmatureFeature::Queue => {
r#"
use armature_queue::prelude::*;
let queue = Queue::new("redis://localhost", "default").await?;
queue.enqueue("job_type", json!({})).await?;
"#
}
ArmatureFeature::Mail => {
r#"
use armature_mail::prelude::*;
let mailer = Mailer::smtp(config).await?;
let email = Email::new().to("user@example.com").subject("Hello");
mailer.send(email).await?;
"#
}
_ => {
r#"
// Check the crate documentation for usage examples
"#
}
};
for line in example.lines().skip(1) {
println!(" {}", line.dimmed());
}
}
async fn run_validate_command(args: ValidateArgs) -> CliResult<()> {
let mut has_errors = false;
let mut has_warnings = false;
println!(" {}", "Project Validation".bright_white().bold());
println!(" {}\n", "─".repeat(40).dimmed());
if !std::path::Path::new("Cargo.toml").exists() {
println!(" {} Not in a Rust project directory", "✗".red());
return Err(CliError::NotInProject);
}
println!(" {} Found Cargo.toml", "✓".green());
if std::path::Path::new("src").exists() {
println!(" {} Found src directory", "✓".green());
} else {
println!(" {} Missing src directory", "✗".red());
has_errors = true;
}
let has_main = std::path::Path::new("src/main.rs").exists();
let has_lib = std::path::Path::new("src/lib.rs").exists();
if has_main || has_lib {
if has_main {
println!(" {} Found src/main.rs", "✓".green());
}
if has_lib {
println!(" {} Found src/lib.rs", "✓".green());
}
} else {
println!(" {} Missing src/main.rs or src/lib.rs", "✗".red());
has_errors = true;
}
if !args.deps_only {
println!("\n {}", "Configuration:".bright_white());
let config_files = [
(".env", "Environment variables"),
(".env.example", "Example environment"),
("config/default.toml", "Default config"),
("config/production.toml", "Production config"),
];
for (file, desc) in config_files {
if std::path::Path::new(file).exists() {
println!(
" {} Found {} ({})",
"✓".green(),
file.cyan(),
desc.dimmed()
);
} else {
println!(
" {} Missing {} ({})",
"○".yellow(),
file.dimmed(),
desc.dimmed()
);
}
}
}
if !args.config_only {
println!("\n {}", "Dependencies:".bright_white());
let cargo_check = std::process::Command::new("cargo")
.args(["check", "--message-format=short"])
.output();
match cargo_check {
Ok(output) if output.status.success() => {
println!(" {} Cargo check passed", "✓".green());
}
Ok(output) => {
println!(" {} Cargo check failed:", "✗".red());
let stderr = String::from_utf8_lossy(&output.stderr);
for line in stderr.lines().take(5) {
println!(" {}", line.dimmed());
}
has_errors = true;
}
Err(_) => {
println!(" {} Could not run cargo check", "○".yellow());
has_warnings = true;
}
}
}
println!();
if has_errors {
println!(
" {} Validation found {} that need attention",
"✗".red(),
"errors".red()
);
if args.fix {
println!(" {} Auto-fix is not yet implemented", "ℹ".dimmed());
}
} else if has_warnings {
println!(
" {} Validation passed with {}",
"○".yellow(),
"warnings".yellow()
);
} else {
println!(" {} All checks passed!", "✓".green().bold());
}
println!();
Ok(())
}
async fn run_doctor() -> CliResult<()> {
print_mini_banner();
println!(" {}", "System Diagnostics".bright_white().bold());
println!(" {}\n", "─".repeat(40).dimmed());
let mp = MultiProgress::new();
let style = ProgressStyle::default_spinner()
.template("{spinner:.cyan} {msg}")
.unwrap()
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]);
let pb = mp.add(ProgressBar::new_spinner());
pb.set_style(style.clone());
pb.set_message("Checking Rust installation...");
pb.enable_steady_tick(Duration::from_millis(80));
let rust_version = std::process::Command::new("rustc")
.arg("--version")
.output();
tokio::time::sleep(Duration::from_millis(300)).await;
match rust_version {
Ok(output) if output.status.success() => {
let version = String::from_utf8_lossy(&output.stdout);
pb.finish_with_message(format!(
"{} Rust: {}",
"✓".green(),
version.trim().bright_white()
));
}
_ => {
pb.finish_with_message(format!("{} Rust: Not found", "✗".red()));
}
}
let pb = mp.add(ProgressBar::new_spinner());
pb.set_style(style.clone());
pb.set_message("Checking Cargo...");
pb.enable_steady_tick(Duration::from_millis(80));
let cargo_version = std::process::Command::new("cargo")
.arg("--version")
.output();
tokio::time::sleep(Duration::from_millis(200)).await;
match cargo_version {
Ok(output) if output.status.success() => {
let version = String::from_utf8_lossy(&output.stdout);
pb.finish_with_message(format!(
"{} Cargo: {}",
"✓".green(),
version.trim().bright_white()
));
}
_ => {
pb.finish_with_message(format!("{} Cargo: Not found", "✗".red()));
}
}
let pb = mp.add(ProgressBar::new_spinner());
pb.set_style(style.clone());
pb.set_message("Checking cargo-watch...");
pb.enable_steady_tick(Duration::from_millis(80));
tokio::time::sleep(Duration::from_millis(200)).await;
if generators::has_cargo_watch() {
pb.finish_with_message(format!(
"{} cargo-watch: {}",
"✓".green(),
"installed".bright_white()
));
} else {
pb.finish_with_message(format!(
"{} cargo-watch: {} (run: {})",
"○".yellow(),
"not installed".yellow(),
"cargo install cargo-watch".dimmed()
));
}
let pb = mp.add(ProgressBar::new_spinner());
pb.set_style(style.clone());
pb.set_message("Checking Docker...");
pb.enable_steady_tick(Duration::from_millis(80));
let docker_version = std::process::Command::new("docker")
.arg("--version")
.output();
tokio::time::sleep(Duration::from_millis(200)).await;
match docker_version {
Ok(output) if output.status.success() => {
let version = String::from_utf8_lossy(&output.stdout);
pb.finish_with_message(format!(
"{} Docker: {}",
"✓".green(),
version
.trim()
.split(',')
.next()
.unwrap_or("")
.bright_white()
));
}
_ => {
pb.finish_with_message(format!(
"{} Docker: {}",
"○".yellow(),
"not installed (optional)".dimmed()
));
}
}
let pb = mp.add(ProgressBar::new_spinner());
pb.set_style(style.clone());
pb.set_message("Checking Git...");
pb.enable_steady_tick(Duration::from_millis(80));
let git_version = std::process::Command::new("git").arg("--version").output();
tokio::time::sleep(Duration::from_millis(200)).await;
match git_version {
Ok(output) if output.status.success() => {
let version = String::from_utf8_lossy(&output.stdout);
pb.finish_with_message(format!(
"{} Git: {}",
"✓".green(),
version.trim().bright_white()
));
}
_ => {
pb.finish_with_message(format!("{} Git: Not found", "✗".red()));
}
}
let pb = mp.add(ProgressBar::new_spinner());
pb.set_style(style);
pb.set_message("Checking evcxr_repl...");
pb.enable_steady_tick(Duration::from_millis(80));
let evcxr = std::process::Command::new("evcxr")
.arg("--version")
.output();
tokio::time::sleep(Duration::from_millis(200)).await;
match evcxr {
Ok(output) if output.status.success() => {
pb.finish_with_message(format!(
"{} evcxr_repl: {}",
"✓".green(),
"installed".bright_white()
));
}
_ => {
pb.finish_with_message(format!(
"{} evcxr_repl: {} (run: {})",
"○".yellow(),
"not installed".yellow(),
"cargo install evcxr_repl".dimmed()
));
}
}
println!();
println!(
" {} All essential tools are installed!",
"✓".green().bold()
);
println!();
Ok(())
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
if cli.no_color {
colored::control::set_override(false);
}
match &cli.command {
Commands::New(args) if args.interactive || args.name.is_none() => {
}
Commands::New { .. } | Commands::Generate { .. } => print_mini_banner(),
Commands::Doctor => {} _ => {}
}
let result: CliResult<()> = match cli.command {
Commands::New(args) => {
if args.interactive || args.name.is_none() {
run_interactive_wizard().await
} else {
let template = match args.template {
Some(ProjectTemplate::Minimal) => "minimal",
Some(ProjectTemplate::Full) => "full",
Some(ProjectTemplate::Microservice) => "microservice",
Some(ProjectTemplate::Graphql) => "graphql",
Some(ProjectTemplate::Grpc) => "grpc",
Some(ProjectTemplate::Lambda) => "lambda",
Some(ProjectTemplate::Cloudrun) => "cloudrun",
None => "minimal",
};
new::run(
args.name.as_deref().unwrap_or("my-app"),
template,
args.skip_git,
args.skip_install,
)
.await
}
}
Commands::Generate { generator } => match generator {
GeneratorType::Controller {
name,
crud,
skip_tests,
auth: _,
} => generate::controller(&name, crud, skip_tests).await,
GeneratorType::Module {
name,
controllers,
providers,
} => generate::module(&name, controllers.as_deref(), providers.as_deref()).await,
GeneratorType::Middleware { name, skip_tests } => {
generate::middleware(&name, skip_tests).await
}
GeneratorType::Guard {
name,
skip_tests,
guard_type: _,
} => generate::guard(&name, skip_tests).await,
GeneratorType::Service { name, skip_tests } => {
generate::service(&name, skip_tests).await
}
GeneratorType::Resource { name, crud } => generate::resource(&name, crud).await,
GeneratorType::Model {
name,
fields: _,
migration: _,
} => {
info(&format!("Generating model: {}", name.cyan()));
warn("Model generation is coming soon!");
Ok(())
}
GeneratorType::Job { name, job_type: _ } => {
generate::job(&name, false).await
}
GeneratorType::Event { name } => {
generate::event_handler(&name, false).await
}
GeneratorType::Dto { name, fields: _ } => {
generate::dto(&name).await
}
GeneratorType::Scaffold { name, fields: _ } => {
info(&format!("Generating full scaffold for: {}", name.cyan()));
async {
generate::entity(&name).await?;
generate::repository(&name, false).await?;
generate::dto(&name).await?;
generate::service(&name, false).await?;
generate::controller(&name, true, false).await
}.await
}
GeneratorType::Repository { name, skip_tests } => {
generate::repository(&name, skip_tests).await
}
GeneratorType::Websocket { name, skip_tests } => {
generate::websocket(&name, skip_tests).await
}
GeneratorType::Graphql { name, skip_tests } => {
generate::graphql_resolver(&name, skip_tests).await
}
GeneratorType::Interceptor { name, skip_tests } => {
generate::interceptor(&name, skip_tests).await
}
GeneratorType::Pipe { name, skip_tests } => {
generate::pipe(&name, skip_tests).await
}
GeneratorType::ExceptionFilter { name, skip_tests } => {
generate::exception_filter(&name, skip_tests).await
}
GeneratorType::Config { name } => {
generate::config(&name).await
}
GeneratorType::Entity { name, orm } => {
match orm.parse::<generate::OrmType>() {
Ok(orm_type) => generate::entity_with_orm(&name, orm_type).await,
Err(e) => Err(crate::error::CliError::InvalidArgument(e)),
}
}
GeneratorType::PraxSchema { name } => {
generate::prax_schema(&name).await
}
GeneratorType::PraxRepository { name, skip_tests } => {
generate::prax_repository(&name, skip_tests).await
}
GeneratorType::PraxModule { name } => {
generate::prax_module(&name).await
}
GeneratorType::Scheduler { name, skip_tests } => {
generate::scheduler(&name, skip_tests).await
}
GeneratorType::CacheService { name, skip_tests } => {
generate::cache_service(&name, skip_tests).await
}
GeneratorType::ApiClient { name } => {
generate::api_client(&name).await
}
GeneratorType::Health => {
generate::health_controller().await
}
},
Commands::Dev(args) => dev::run(args.port, &args.host, &args.cargo_args).await,
Commands::Serve(args) => {
info(&format!(
"Starting production server on {}:{}",
args.host.cyan(),
args.port.to_string().cyan()
));
if let Some(workers) = args.workers {
info(&format!("Workers: {}", workers));
}
warn("Production serve is coming soon!");
Ok(())
}
Commands::Build(args) => build::run(args.release, &args.cargo_args).await,
Commands::Routes(args) => {
print_mini_banner();
if let Some(method) = args.method.as_deref() {
routes::execute_with_filter(None, Some(method))
} else {
routes::execute(None)
}
}
Commands::Config(args) => match args.command {
Some(ConfigCommands::Validate { file }) => {
if let Some(f) = file {
config::validate_file(&f)
} else {
config::execute(None)
}
}
Some(ConfigCommands::Show { key: _ }) => {
warn("Config show is coming soon!");
Ok(())
}
Some(ConfigCommands::Set { key: _, value: _ }) => {
warn("Config set is coming soon!");
Ok(())
}
Some(ConfigCommands::Init { env: _ }) => {
warn("Config init is coming soon!");
Ok(())
}
None => config::execute(None),
},
Commands::Doctor => run_doctor().await,
Commands::Upgrade(args) => {
print_mini_banner();
if args.check {
info("Checking for updates...");
println!(
" {} Current version: {}",
"→".cyan(),
env!("CARGO_PKG_VERSION").bright_white()
);
warn("Update checking is coming soon!");
} else {
info("Upgrading Armature CLI...");
warn("Self-upgrade is coming soon!");
println!(
"\n {} Run: {}",
"💡".yellow(),
"cargo install armature-cli --force".dimmed()
);
}
Ok(())
}
Commands::Db { command } => {
print_mini_banner();
match command {
DbCommands::Create => {
info("Creating database...");
warn("Database commands are coming soon!");
}
DbCommands::Drop { force: _ } => {
info("Dropping database...");
warn("Database commands are coming soon!");
}
DbCommands::Migrate { steps: _ } => {
info("Running migrations...");
warn("Database commands are coming soon!");
}
DbCommands::Rollback { steps: _ } => {
info("Rolling back migrations...");
warn("Database commands are coming soon!");
}
DbCommands::Reset { force: _ } => {
info("Resetting database...");
warn("Database commands are coming soon!");
}
DbCommands::Status => {
info("Migration status:");
warn("Database commands are coming soon!");
}
DbCommands::Seed { seeder: _ } => {
info("Seeding database...");
warn("Database commands are coming soon!");
}
DbCommands::Generate { name } => {
info(&format!("Generating migration: {}", name.cyan()));
warn("Database commands are coming soon!");
}
}
Ok(())
}
Commands::Deploy(args) => {
print_mini_banner();
let provider = args.provider.map(|p| match p {
CloudProvider::Aws => "AWS",
CloudProvider::Gcp => "GCP",
CloudProvider::Azure => "Azure",
CloudProvider::Fly => "Fly.io",
CloudProvider::Railway => "Railway",
CloudProvider::Render => "Render",
CloudProvider::Shuttle => "Shuttle",
CloudProvider::Docker => "Docker",
});
info(&format!(
"Deploying to {} ({})",
provider.unwrap_or("auto-detected").cyan(),
args.env.cyan()
));
if args.dry_run {
info("Dry run - no changes will be made");
}
warn("Deploy command is coming soon!");
Ok(())
}
Commands::Repl(args) => {
if args.simple {
repl::execute_simple()
} else {
repl::execute()
}
}
Commands::Info => info::run().await,
Commands::Completions { shell } => {
let mut cmd = Cli::command();
generate(shell, &mut cmd, "armature", &mut io::stdout());
Ok(())
}
Commands::Docs => {
info("Opening documentation...");
let url = "https://github.com/pegasusheavy/armature";
if webbrowser::open(url).is_err() {
println!(" {} {}", "→".cyan(), url);
}
Ok(())
}
Commands::Plugin { command } => {
print_mini_banner();
match command {
PluginCommands::List => {
info("Installed plugins:");
println!(" {} No plugins installed", "○".dimmed());
}
PluginCommands::Install { name } => {
info(&format!("Installing plugin: {}", name.cyan()));
warn("Plugin system is coming soon!");
}
PluginCommands::Uninstall { name } => {
info(&format!("Uninstalling plugin: {}", name.cyan()));
warn("Plugin system is coming soon!");
}
PluginCommands::New { name } => {
info(&format!("Creating plugin: {}", name.cyan()));
warn("Plugin system is coming soon!");
}
}
Ok(())
}
Commands::Bench(args) => {
print_mini_banner();
if let Some(pattern) = args.pattern {
info(&format!("Running benchmarks matching: {}", pattern.cyan()));
} else {
info("Running all benchmarks...");
}
warn("Benchmark command is coming soon!");
Ok(())
}
Commands::Lint(args) => {
print_mini_banner();
if args.fix {
info("Linting with auto-fix...");
} else {
info("Linting project...");
}
warn("Lint command is coming soon!");
Ok(())
}
Commands::Clean => {
print_mini_banner();
info("Cleaning build artifacts...");
match std::process::Command::new("cargo").arg("clean").status() {
Ok(status) if status.success() => {
success("Build artifacts cleaned!");
Ok(())
}
Ok(_) => Err(CliError::Command("cargo clean failed".to_string())),
Err(e) => Err(CliError::Io(e)),
}
}
Commands::Add(args) => {
print_mini_banner();
run_add_command(args)
}
Commands::Validate(args) => {
print_mini_banner();
run_validate_command(args).await
}
Commands::Openapi { command } => {
print_mini_banner();
match command {
OpenapiCommands::Client(args) => {
let language = match args.language {
ClientLanguageArg::Typescript => openapi::ClientLanguage::TypeScript,
ClientLanguageArg::Rust => openapi::ClientLanguage::Rust,
ClientLanguageArg::Both => openapi::ClientLanguage::Both,
};
let options = openapi::ClientOptions {
base_url: None,
async_client: true,
with_logging: args.with_logging,
with_retry: args.with_retry,
client_name: args.name,
};
openapi::generate_client(&args.spec, &args.output, language, &options).await
}
OpenapiCommands::Validate { spec } => {
info(&format!("Validating OpenAPI spec: {}", spec.cyan()));
warn("OpenAPI validation is coming soon!");
Ok(())
}
OpenapiCommands::Generate { output, format: _ } => {
info(&format!("Generating OpenAPI spec to: {}", output.cyan()));
warn("OpenAPI generation from routes is coming soon!");
Ok(())
}
}
}
Commands::Mock(args) => {
print_mini_banner();
let mock_args = mock::MockArgs {
spec: args.spec,
port: args.port,
host: args.host,
data_dir: args.data,
delay_ms: args.delay,
cors: args.cors,
seed: args.seed,
watch: args.watch,
};
mock::run(mock_args).await
}
};
if let Err(e) = result {
eprintln!("\n {} {}\n", "Error:".red().bold(), e);
std::process::exit(1);
};
}