use anyhow::Result;
use clap::{Parser, Subcommand};
use runique::utils::init_logging;
use std::fs;
use std::path::Path;
#[derive(Parser)]
#[command(name = "runique")]
#[command(about = "CLI du framework Runique", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
New { name: String },
Start {
#[arg(long, default_value = "src/main.rs")]
main: String,
#[arg(long, default_value = "src/admin.rs")]
admin: String,
},
CreateSuperuser,
}
#[tokio::main]
async fn main() -> Result<()> {
init_logging();
let cli = Cli::parse();
match cli.command {
Commands::New { name } => create_new_project(&name)?,
Commands::Start { main, admin } => runique_start(&main, &admin)?,
Commands::CreateSuperuser => runique::admin::create_superuser().await?,
}
Ok(())
}
fn runique_start(main_path: &str, admin_path: &str) -> Result<()> {
let main_file = Path::new(main_path);
if !main_file.exists() {
anyhow::bail!(
"Fichier non trouvé: {}\nAssurez-vous d'être à la racine de votre projet Runique.",
main_path
);
}
let main_source = fs::read_to_string(main_file)?;
if !has_admin(&main_source) {
println!(" Add .with_admin(...) in your builder to enable the AdminPanel.");
return Ok(());
}
println!("Admin detected → starting the daemon");
let admin_path = admin_path.to_string();
std::thread::spawn(move || {
if let Err(e) = start_admin_daemon(&admin_path) {
eprintln!("[Daemon] Erreur: {}", e);
}
});
use std::process::Command;
let status = Command::new("cargo")
.arg("run")
.status()
.expect("Échec du lancement de cargo run");
if !status.success() {
anyhow::bail!("Le serveur applicatif n'a pas démarré correctement (cargo run)");
}
Ok(())
}
fn has_admin(source: &str) -> bool {
source.contains(".with_admin(")
}
fn start_admin_daemon(admin_path: &str) -> Result<()> {
use runique::admin::daemon::watch;
let admin_file = Path::new(admin_path);
if !admin_file.exists() {
anyhow::bail!(
"Fichier admin non trouvé: {}\nCréez src/admin.rs avec le macro admin!{{}}.",
admin_path
);
}
watch(admin_file).map_err(|e| anyhow::anyhow!("Erreur daemon: {}", e))?;
Ok(())
}
fn create_new_project(name: &str) -> Result<()> {
if name.is_empty() {
anyhow::bail!("The project name cannot be empty");
}
if !name
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
{
anyhow::bail!("The project name must contain only letters, numbers, _ or -");
}
if name.starts_with('-') {
anyhow::bail!("The project name cannot start with -");
}
let project_dir = Path::new(name);
if project_dir.exists() {
anyhow::bail!("The folder '{}' already exists", name);
}
println!("🦀 Creating project '{}'...", name);
let runique_version = env!("CARGO_PKG_VERSION");
let view_rs_content = include_bytes!("../composant-bin/code/views.rs").to_vec();
let formulaire = include_bytes!("../composant-bin/code/forms.rs").to_vec();
let url_rs = include_bytes!("../composant-bin/code/url.rs").to_vec();
let main_rs = include_bytes!("../composant-bin/code/main.rs").to_vec();
let user_exemple = include_bytes!("../composant-bin/code/users.rs").to_vec();
let mod_rs_content = include_bytes!("../composant-bin/code/mod.rs").to_vec();
let index_html = include_bytes!("../composant-bin/template/index.html").to_vec();
let about_html = include_bytes!("../composant-bin/template/about.html").to_vec();
let inscription_html =
include_bytes!("../composant-bin/template/inscription_form.html").to_vec();
let main_css = include_bytes!("../composant-bin/css/main.css").to_vec();
let about_css = include_bytes!("../composant-bin/css/about.css").to_vec();
let variable_css = include_bytes!("../composant-bin/css/variables.css").to_vec();
let inscription_css =
include_bytes!("../composant-bin/css/inscription/inscription.css").to_vec();
let inscription_label_css =
include_bytes!("../composant-bin/css/inscription/inscription-label.css").to_vec();
let image = include_bytes!("../composant-bin/image/toshiro.avif").to_vec();
let favicon = include_bytes!("../composant-bin/image/favicon.ico").to_vec();
let cargo_toml = include_str!("../composant-bin/config/apiconfig")
.replace("{{PROJECT_NAME}}", name)
.replace("{{RUNIQUE_VERSION}}", runique_version)
.into_bytes();
let env_file = include_bytes!("../composant-bin/config/secret").to_vec();
let gitignore = include_bytes!("../composant-bin/config/ignore").to_vec();
let readme_va = include_bytes!("../composant-bin/readme/README.md").to_vec();
let readme_fr = include_bytes!("../composant-bin/readme/README.fr.md").to_vec();
fs::create_dir_all(project_dir.join("src/models"))?;
fs::create_dir_all(project_dir.join("static/css/inscription"))?;
fs::create_dir_all(project_dir.join("static/js"))?;
fs::create_dir_all(project_dir.join("media/favicon"))?;
fs::create_dir_all(project_dir.join("templates/about"))?;
fs::write(project_dir.join("Cargo.toml"), cargo_toml)?;
fs::write(project_dir.join(".env"), env_file)?;
fs::write(project_dir.join(".gitignore"), gitignore)?;
fs::write(project_dir.join("README.md"), readme_va)?;
fs::write(project_dir.join("README.fr.md"), readme_fr)?;
fs::write(project_dir.join("src/main.rs"), main_rs)?;
fs::write(project_dir.join("src/forms.rs"), formulaire)?;
fs::write(project_dir.join("src/url.rs"), url_rs)?;
fs::write(project_dir.join("src/views.rs"), view_rs_content)?;
fs::write(project_dir.join("src/models/mod.rs"), mod_rs_content)?;
fs::write(project_dir.join("src/models/users.rs"), user_exemple)?;
fs::write(project_dir.join("templates/index.html"), index_html)?;
fs::write(project_dir.join("templates/about/about.html"), about_html)?;
fs::write(
project_dir.join("templates/inscription_form.html"),
inscription_html,
)?;
fs::write(project_dir.join("static/css/main.css"), main_css)?;
fs::write(project_dir.join("static/css/about.css"), about_css)?;
fs::write(project_dir.join("static/css/variables.css"), variable_css)?;
fs::write(
project_dir.join("static/css/inscription/inscription.css"),
inscription_css,
)?;
fs::write(
project_dir.join("static/css/inscription/inscription-label.css"),
inscription_label_css,
)?;
fs::write(project_dir.join("media/toshiro.avif"), image)?;
fs::write(project_dir.join("media/favicon/favicon.ico"), favicon)?;
println!(" Project '{}' created successfully!", name);
println!("\n To get started:");
println!(" cd {}", name);
println!(" cargo run");
Ok(())
}