mod config;
mod ext;
mod logger;
mod run;
use ext::{fs, path, sync, util};
use crate::ext::anyhow::{Context, Result};
use binary_install::Cache;
use clap::{Parser, Subcommand, ValueEnum};
use config::Config;
use ext::path::PathBufExt;
use ext::sync::{send_reload, src_or_style_change, wait_for, Msg, MSG_BUS, SHUTDOWN};
use run::{assets, cargo, end2end, new, reload, sass, serve, wasm, watch, Html};
use std::{env, path::PathBuf};
use tokio::signal;
lazy_static::lazy_static! {
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),
EndToEnd(Opts),
Serve(Opts),
Watch(Opts),
New(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).dot()?;
}
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::EndToEnd(opts)
| Commands::Watch(opts) => opts,
};
logger::setup(opts.verbose, &args.log);
let config = config::read(&args, opts.clone()).await.dot()?;
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::EndToEnd(_) => e2e_test(&config).await,
Commands::Watch(_) => watch(&config).await,
}
}
async fn e2e_test(config: &Config) -> Result<()> {
build(config, true).await.dot()?;
let handle = if config.cli.csr {
serve::spawn(&config).await.dot()?
} else {
cargo::spawn_run(&config).await
};
end2end::run(config).await.dot()?;
MSG_BUS.send(Msg::ShutDown).dot()?;
handle.await.dot()?;
Ok(())
}
async fn build(config: &Config, copy_assets: bool) -> Result<()> {
log::debug!(r#"Leptos cleaning contents of "target/site/pkg""#);
fs::rm_dir_content("target/site/pkg").await.dot()?;
if copy_assets {
assets::update(config).await.dot()?;
}
build_client(&config).await.dot()?;
if !config.cli.csr {
cargo::build(&config, false).await.dot()?;
}
Ok(())
}
async fn build_client(config: &Config) -> Result<()> {
sass::run(&config).await.dot()?;
let html = Html::read(&config.leptos.index_file).await.dot()?;
if config.cli.csr {
wasm::build(&config).await.dot()?;
html.generate_html(&config).await.dot()?;
} else {
wasm::build(&config).await.dot()?;
html.generate_rust(&config).await.dot()?;
}
Ok(())
}
async fn serve(config: &Config) -> Result<()> {
build(&config, true).await.dot()?;
if config.cli.csr {
serve::spawn(&config).await?.await.dot()
} else {
cargo::run(&config).await
}
}
async fn watch(config: &Config) -> Result<()> {
let _ = watch::spawn(config).await.dot()?;
if let Some(assets_dir) = &config.leptos.assets_dir {
let _ = assets::spawn(assets_dir).await.dot()?;
}
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(src_or_style_change).await;
} else {
cargo::run(&config).await.dot()?;
}
}
Err(e) => {
log::warn!("Leptos rebuild stopped due to error: {e:?}");
wait_for(src_or_style_change).await;
}
}
if *SHUTDOWN.read().await {
break;
} else {
log::info!("Leptos ===================== rebuilding =====================");
}
}
Ok(())
}