use crate::error::Result;
use colored::*;
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
const TEMPLATE_DIR: &str = "templates";
pub fn get_template_path(simple: bool) -> PathBuf {
let template_name = if simple {
"boilerplate-simple"
} else {
"boilerplate"
};
let candidates = vec![
{
if let Ok(exe_path) = std::env::current_exe() {
if let Some(exe_dir) = exe_path.parent() {
if exe_dir.ends_with("debug") || exe_dir.ends_with("release") {
if let Some(target_dir) = exe_dir.parent() {
if let Some(project_root) = target_dir.parent() {
return project_root.join(TEMPLATE_DIR).join(template_name);
}
}
}
exe_dir.join(TEMPLATE_DIR).join(template_name)
} else {
PathBuf::new()
}
} else {
PathBuf::new()
}
},
PathBuf::from("/usr/local/share/create-lamdera-app")
.join(TEMPLATE_DIR)
.join(template_name),
PathBuf::from(TEMPLATE_DIR).join(template_name),
];
for candidate in &candidates {
if candidate.exists() {
return candidate.clone();
}
}
candidates[0].clone()
}
pub fn copy_boilerplate(project_path: &Path, simple: bool) -> Result<()> {
let template_path = get_template_path(simple);
if !template_path.exists() {
return Err(anyhow::anyhow!(
"Boilerplate template not found at {:?}",
template_path
));
}
let message = if simple {
"Setting up simple boilerplate project (no counter/chat demos)..."
} else {
"Setting up boilerplate project..."
};
println!("{}", message.blue());
copy_recursive(&template_path, project_path)?;
Ok(())
}
fn copy_recursive(src: &Path, dest: &Path) -> Result<()> {
for entry in WalkDir::new(src).into_iter().filter_entry(|e| {
let file_name = e.file_name().to_string_lossy();
!matches!(
file_name.as_ref(),
".DS_Store" | "elm-stuff" | "node_modules" | ".git" | ".lamdera"
)
}) {
let entry = entry?;
let path = entry.path();
let relative_path = path.strip_prefix(src)?;
let dest_path = dest.join(relative_path);
if entry.file_type().is_dir() {
fs::create_dir_all(&dest_path)?;
} else {
let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
let final_dest_path = if file_name == "gitignore" {
dest_path.parent().unwrap().join(".gitignore")
} else {
dest_path
};
if let Some(parent) = final_dest_path.parent() {
fs::create_dir_all(parent)?;
}
fs::copy(path, &final_dest_path)?;
}
}
Ok(())
}
pub fn update_package_json(project_path: &Path, package_manager: &str) -> Result<()> {
let package_json_path = project_path.join("package.json");
if !package_json_path.exists() {
return Ok(());
}
let content = fs::read_to_string(&package_json_path)?;
let mut package_json: serde_json::Value = serde_json::from_str(&content)?;
let runner = if package_manager == "bun" { "bunx" } else { "npx" };
if let Some(scripts) = package_json.get_mut("scripts").and_then(|s| s.as_object_mut()) {
if let Some(start) = scripts.get_mut("start") {
*start = serde_json::Value::String(format!(
"concurrently \"{} tailwindcss -i ./src/styles.css -o ./public/styles.css --watch\" \"./lamdera-dev-watch.sh\"",
runner
));
}
if let Some(start_hot) = scripts.get_mut("start:hot") {
*start_hot = serde_json::Value::String(format!(
"concurrently \"{} tailwindcss -i ./src/styles.css -o ./public/styles.css --watch\" \"PORT=8001 ./lamdera-dev-watch.sh\"",
runner
));
}
}
let updated_content = serde_json::to_string_pretty(&package_json)?;
fs::write(&package_json_path, updated_content)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_template_path() {
let path = get_template_path(false);
assert!(path.to_string_lossy().contains("boilerplate"));
let simple_path = get_template_path(true);
assert!(simple_path.to_string_lossy().contains("boilerplate-simple"));
}
}