mod config;
mod logger;
mod run;
pub mod util;
use anyhow::Result;
use binary_install::Cache;
use clap::{Parser, Subcommand, ValueEnum};
use config::Config;
use run::{cargo, reload, sass, serve, wasm, watch, Html};
use std::{env, path::PathBuf};
use tokio::{
signal,
sync::{broadcast, RwLock},
};
use util::PathBufAdditions;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Msg {
ShutDown,
SrcChanged,
Reload(String),
}
lazy_static::lazy_static! {
pub static ref MSG_BUS: broadcast::Sender<Msg> = {
broadcast::channel(10).0
};
pub static ref SHUTDOWN: RwLock<bool> = RwLock::new(false);
pub static ref INSTALL_CACHE: Cache = Cache::new("cargo-leptos").unwrap();
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum Log {
Wasm,
Server,
}
#[derive(Debug, Clone, Parser, PartialEq, Default)]
pub struct Opts {
#[arg(short, long)]
release: bool,
#[arg(long)]
csr: bool,
#[arg(short, action = clap::ArgAction::Count)]
verbose: u8,
}
#[derive(Debug, Parser)]
pub struct Cli {
#[arg(long)]
manifest_path: Option<String>,
#[arg(long)]
log: Vec<Log>,
#[command(subcommand)]
command: Commands,
}
#[derive(Debug, Subcommand, PartialEq)]
enum Commands {
Init,
Build(Opts),
Test(Opts),
Serve(Opts),
Watch(Opts),
}
#[tokio::main]
async fn main() -> Result<()> {
let mut args: Vec<String> = env::args().collect();
if args.get(1).map(|a| a == "leptos").unwrap_or(false) {
args.remove(1);
}
let args = Cli::parse_from(&args);
if let Some(path) = &args.manifest_path {
let path = PathBuf::from(path).without_last();
std::env::set_current_dir(path)?;
}
let opts = match &args.command {
Commands::Init => return Ok(println!(include_str!("leptos.toml"))),
Commands::Build(opts)
| Commands::Serve(opts)
| Commands::Test(opts)
| Commands::Watch(opts) => opts,
};
logger::setup(opts.verbose, &args.log);
let config = config::read(&args, opts.clone())?;
tokio::spawn(async {
signal::ctrl_c().await.expect("failed to listen for event");
log::info!("Leptos ctrl-c received");
*SHUTDOWN.write().await = true;
MSG_BUS.send(Msg::ShutDown).unwrap();
});
match args.command {
Commands::Init => panic!(),
Commands::Build(_) => build(&config).await,
Commands::Serve(_) => serve(&config).await,
Commands::Test(_) => cargo::test(&config).await,
Commands::Watch(_) => watch(&config).await,
}
}
async fn send_reload() {
if !*SHUTDOWN.read().await {
if let Err(e) = MSG_BUS.send(Msg::Reload("reload".to_string())) {
log::error!("Leptos failed to send reload: {e}");
}
}
}
async fn build(config: &Config) -> Result<()> {
log::info!(r#"Leptos cleaning contents of "target/site""#);
util::rm_dir_content("target/site")?;
build_client(&config).await?;
if !config.cli.csr {
cargo::build(&config, false).await?;
}
Ok(())
}
async fn build_client(config: &Config) -> Result<()> {
sass::run(&config).await?;
let html = Html::read(&config.leptos.index_file)?;
if config.cli.csr {
wasm::build(&config).await?;
html.generate_html(&config)?;
} else {
wasm::build(&config).await?;
html.generate_rust(&config)?;
}
Ok(())
}
async fn serve(config: &Config) -> Result<()> {
build(&config).await?;
if config.cli.csr {
serve::run(&config).await
} else {
cargo::run(&config).await
}
}
async fn watch(config: &Config) -> Result<()> {
let cfg = config.clone();
let _ = tokio::spawn(async move { watch::run(cfg).await });
if config.cli.csr {
let cfg = config.clone();
let _ = tokio::spawn(async move { serve::run(&cfg).await });
}
reload::run(&config).await?;
loop {
match build(config).await {
Ok(_) => {
send_reload().await;
if config.cli.csr {
MSG_BUS.subscribe().recv().await?;
} else {
cargo::run(&config).await?;
}
if *SHUTDOWN.read().await {
break;
} else {
log::info!("Leptos ===================== rebuilding =====================");
}
}
Err(e) => {
log::warn!("Leptos rebuild stopped due to error: {e}");
while MSG_BUS.subscribe().recv().await? != Msg::SrcChanged {}
}
}
}
Ok(())
}