mod fullstack;
mod network;
mod process;
mod ui;
use std::path::Path;
use anyhow::{Result, bail};
use tokio::signal;
use crate::build::config::BuildConfig;
use crate::build::types::read_bundle_manifest;
use crate::config::SeamConfig;
use crate::dev_server;
use crate::ui as root_ui;
use fullstack::run_dev_fullstack;
use process::{ChildProcess, label_color, pipe_output, spawn_child, wait_any};
use ui::{build_frontend, print_dev_banner};
pub use fullstack::run_dev_workspace;
pub async fn run_dev(config: &SeamConfig, base_dir: &Path) -> Result<()> {
let build_config = BuildConfig::from_seam_config(config);
if build_config.as_ref().is_ok_and(|bc| bc.is_fullstack) {
return run_dev_fullstack(config, base_dir).await;
}
let backend_cmd = config.backend.dev_command.as_ref();
let frontend_cmd = config.frontend.dev_command.as_ref();
let has_entry = config.frontend.entry.is_some();
let use_embedded = frontend_cmd.is_none() && has_entry;
if backend_cmd.is_none() && frontend_cmd.is_none() && !has_entry {
bail!(
"no dev_command configured \
(set backend.dev_command, frontend.dev_command, or frontend.entry)"
);
}
print_dev_banner(
config,
backend_cmd.map(crate::config::CommandConfig::command),
frontend_cmd.map(crate::config::CommandConfig::command),
use_embedded,
);
if use_embedded {
build_frontend(config, base_dir)?;
}
let mut children: Vec<ChildProcess> = Vec::new();
if let Some(cmd) = backend_cmd {
let port_str = config.backend.port.to_string();
let cwd = cmd.resolve_cwd(base_dir);
let mut proc = spawn_child("backend", cmd.command(), &cwd, &[("PORT", &port_str)])?;
pipe_output(&mut proc).await;
children.push(proc);
}
if let Some(cmd) = frontend_cmd {
let cwd = cmd.resolve_cwd(base_dir);
let mut proc = spawn_child("frontend", cmd.command(), &cwd, &[])?;
pipe_output(&mut proc).await;
children.push(proc);
}
if use_embedded {
let dev_port = config.frontend.dev_port.unwrap_or(5173);
let (manifest_path, static_dir) = match &build_config {
Ok(bc) => (base_dir.join(bc.bundler_manifest()), base_dir.join(bc.dist_dir())),
Err(_) => (base_dir.join(".seam/dist/.vite/manifest.json"), base_dir.join(".seam/dist")),
};
let assets = read_bundle_manifest(&manifest_path)?;
let public_dir = base_dir.join("public");
let public_dir = if public_dir.is_dir() { Some(public_dir) } else { None };
if children.is_empty() {
tokio::select! {
_ = signal::ctrl_c() => {
println!();
root_ui::shutting_down();
}
result = dev_server::start_dev_server(static_dir, dev_port, config.backend.port, assets, public_dir) => {
if let Err(e) = result {
root_ui::error(&format!("dev server: {e}"));
}
}
}
} else {
tokio::select! {
_ = signal::ctrl_c() => {
println!();
root_ui::shutting_down();
}
result = wait_any(&mut children) => {
let (label, status) = result;
let color = label_color(label);
root_ui::process_exited(label, color, status);
}
result = dev_server::start_dev_server(static_dir, dev_port, config.backend.port, assets, public_dir) => {
if let Err(e) = result {
root_ui::error(&format!("dev server: {e}"));
}
}
}
}
} else {
tokio::select! {
_ = signal::ctrl_c() => {
println!();
root_ui::shutting_down();
}
result = wait_any(&mut children) => {
let (label, status) = result;
let color = label_color(label);
root_ui::process_exited(label, color, status);
}
}
}
Ok(())
}