use anyhow::Result;
use clap::{Parser, Subcommand};
mod commands;
#[derive(Parser)]
#[command(
name = "anvil",
about = "Forge a Rust web app — Anvilforge's CLI",
version,
long_about = None
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
New {
name: String,
},
#[command(name = "make:model")]
MakeModel {
name: String,
#[arg(long)]
with_migration: bool,
#[arg(trailing_var_arg = true)]
fields: Vec<String>,
},
#[command(name = "make:migration")]
MakeMigration { name: String },
#[command(name = "make:controller")]
MakeController {
name: String,
#[arg(long)]
resource: bool,
},
#[command(name = "make:request")]
MakeRequest { name: String },
#[command(name = "make:job")]
MakeJob { name: String },
#[command(name = "make:event")]
MakeEvent { name: String },
#[command(name = "make:listener")]
MakeListener {
name: String,
#[arg(long)]
event: Option<String>,
},
#[command(name = "make:test")]
MakeTest { name: String },
#[command(name = "make:seeder")]
MakeSeeder { name: String },
#[command(name = "make:factory")]
MakeFactory {
name: String,
#[arg(long)]
model: Option<String>,
},
#[command(name = "make:component")]
MakeComponent { name: String },
#[command(name = "make:auth")]
MakeAuth,
Migrate {
#[arg(long)]
step: bool,
#[arg(long)]
pretend: bool,
#[arg(long)]
seed: bool,
},
#[command(name = "migrate:rollback")]
MigrateRollback {
#[arg(long, default_value = "1")]
steps: u32,
},
#[command(name = "migrate:reset")]
MigrateReset,
#[command(name = "migrate:refresh")]
MigrateRefresh {
#[arg(long)]
seed: bool,
},
#[command(name = "migrate:fresh")]
MigrateFresh {
#[arg(long)]
seed: bool,
},
#[command(name = "migrate:install")]
MigrateInstall,
#[command(name = "migrate:status")]
MigrateStatus,
#[command(name = "db:seed")]
DbSeed {
#[arg(long)]
class: Option<String>,
},
#[command(name = "db:wipe")]
DbWipe,
Serve {
#[arg(long)]
watch: bool,
#[arg(long, default_value = "127.0.0.1:8080")]
addr: String,
},
Dev {
#[arg(long, default_value = "127.0.0.1:8080")]
addr: String,
#[arg(long)]
fast: bool,
#[arg(long)]
hot: bool,
},
Doctor,
Fmt {
#[arg(long)]
check: bool,
},
Lint {
#[arg(long)]
fix: bool,
},
Install {
#[arg(long)]
force: bool,
},
Routes {
#[arg(long)]
method: Option<String>,
#[arg(long)]
prefix: Option<String>,
#[arg(long)]
json: bool,
},
Bench {
#[arg(long, default_value = "100")]
concurrency: usize,
#[arg(long, default_value = "10")]
seconds: u64,
#[arg(long, default_value = "1")]
warmup_seconds: u64,
#[arg(long, default_value = "all")]
endpoint: String,
},
#[command(name = "bench:micro")]
BenchMicro,
#[command(name = "bench:full")]
BenchFull,
Mcp,
#[command(name = "boost:install")]
BoostInstall {
#[arg(long)]
force: bool,
},
#[command(name = "queue:work")]
QueueWork {
#[arg(long, default_value = "default")]
queue: String,
},
#[command(name = "schedule:run")]
ScheduleRun,
Test {
#[arg(trailing_var_arg = true)]
args: Vec<String>,
},
Repl,
}
fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_target(false)
.with_env_filter(std::env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string()))
.try_init()
.ok();
let cli = Cli::parse();
match cli.command {
Commands::New { name } => commands::new::run(&name),
Commands::MakeModel {
name,
with_migration,
fields,
} => commands::make::model(&name, with_migration, &fields),
Commands::MakeMigration { name } => commands::make::migration(&name),
Commands::MakeController { name, resource } => commands::make::controller(&name, resource),
Commands::MakeRequest { name } => commands::make::request(&name),
Commands::MakeJob { name } => commands::make::job(&name),
Commands::MakeEvent { name } => commands::make::event(&name),
Commands::MakeListener { name, event } => commands::make::listener(&name, event.as_deref()),
Commands::MakeTest { name } => commands::make::test(&name),
Commands::MakeSeeder { name } => commands::make::seeder(&name),
Commands::MakeFactory { name, model } => commands::make::factory(&name, model.as_deref()),
Commands::MakeComponent { name } => commands::make::component(&name),
Commands::MakeAuth => commands::auth::scaffold(),
Commands::Migrate {
step,
pretend,
seed,
} => commands::migrate::up(step, pretend, seed),
Commands::MigrateRollback { steps } => commands::migrate::rollback(steps),
Commands::MigrateReset => commands::migrate::reset(),
Commands::MigrateRefresh { seed } => commands::migrate::refresh(seed),
Commands::MigrateFresh { seed } => commands::migrate::fresh(seed),
Commands::MigrateInstall => commands::migrate::install(),
Commands::MigrateStatus => commands::migrate::status(),
Commands::DbSeed { class } => commands::db::seed(class.as_deref()),
Commands::DbWipe => commands::db::wipe(),
Commands::Serve { watch, addr } => commands::serve::run(watch, &addr),
Commands::Dev { addr, fast, hot } => {
if hot {
commands::dev::run_hot(&addr)
} else if fast {
commands::dev::run_fast(&addr)
} else {
commands::dev::run(&addr)
}
}
Commands::Doctor => commands::doctor::run(),
Commands::Fmt { check } => commands::fmt::run(check),
Commands::Lint { fix } => commands::lint::run(fix),
Commands::Install { force } => commands::install::run(force),
Commands::Routes {
method,
prefix,
json,
} => commands::routes::run(method.as_deref(), prefix.as_deref(), json),
Commands::Bench {
concurrency,
seconds,
warmup_seconds,
endpoint,
} => commands::bench::http(concurrency, seconds, warmup_seconds, &endpoint),
Commands::BenchMicro => commands::bench::micro(),
Commands::BenchFull => commands::bench::full(),
Commands::Mcp => commands::mcp::run(),
Commands::BoostInstall { force } => commands::boost::install(force),
Commands::QueueWork { queue } => commands::queue::work(&queue),
Commands::ScheduleRun => commands::schedule::run_once(),
Commands::Test { args } => commands::test::run(&args),
Commands::Repl => commands::repl::run(),
}
}