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::{
assets, cargo,
new::NewCommand,
reload, sass, serve, wasm,
watch::{self, Watched},
Html,
};
use std::{env, path::PathBuf};
use tokio::{
signal,
sync::{broadcast, RwLock},
};
use util::{wait_for, PathBufAdditions};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Msg {
ShutDown,
SrcChanged,
AssetsChanged(Watched),
StyleChanged,
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)]
#[clap(version)]
pub struct Cli {
#[arg(long)]
manifest_path: Option<String>,
#[arg(long)]
log: Vec<Log>,
#[command(subcommand)]
command: Commands,
}
#[derive(Debug, Subcommand, PartialEq)]
enum Commands {
Config,
Build(Opts),
Test(Opts),
Serve(Opts),
Watch(Opts),
New(NewCommand),
}
#[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 Commands::New(new) = &args.command {
return new.run().await;
}
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::New(_) => panic!(""),
Commands::Config => 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::Config | Commands::New(_) => panic!(),
Commands::Build(_) => build(&config, true).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, copy_assets: bool) -> Result<()> {
log::debug!(r#"Leptos cleaning contents of "target/site/pkg""#);
util::rm_dir_content("target/site/pkg")?;
if copy_assets {
assets::update(config)?;
}
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, true).await?;
if config.cli.csr {
serve::spawn(&config).await.await?;
Ok(())
} else {
cargo::run(&config).await
}
}
async fn watch(config: &Config) -> Result<()> {
let _ = watch::spawn(config).await?;
if let Some(assets_dir) = &config.leptos.assets_dir {
let _ = assets::spawn(assets_dir).await?;
}
if config.cli.csr {
serve::spawn(&config).await;
}
reload::spawn(&config).await;
loop {
match build(config, false).await {
Ok(_) => {
send_reload().await;
if config.cli.csr {
wait_for(&[Msg::SrcChanged, Msg::ShutDown]).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(())
}