diff --git a/loco-new/setup.rhai b/loco-new/setup.rhai index 6998fccf3..b44455d29 100644 --- a/loco-new/setup.rhai +++ b/loco-new/setup.rhai @@ -105,3 +105,13 @@ if background { if asset { gen.copy_dir("assets"); // Static assets directory } + + +// ===================== +// Client side +// ===================== + +if settings.clientside { + gen.copy_dir("frontend"); + gen.create_file("frontend/dist/index.html", "this is a placeholder. please run your frontend build (npm build)"); +} diff --git a/loco-new/src/generator/executer/filesystem.rs b/loco-new/src/generator/executer/filesystem.rs index f80fa2d5f..1f7c744e8 100644 --- a/loco-new/src/generator/executer/filesystem.rs +++ b/loco-new/src/generator/executer/filesystem.rs @@ -1,9 +1,11 @@ -use super::Executer; -use crate::{generator, settings::Settings}; -use fs_extra::file::{move_file, write_all}; use std::path::{Path, PathBuf}; + +use fs_extra::file::{move_file, write_all}; use walkdir::WalkDir; +use super::Executer; +use crate::{generator, settings::Settings}; + #[derive(Debug, Default, Clone)] pub struct FileSystem { pub source_dir: PathBuf, @@ -83,6 +85,28 @@ impl Executer for FileSystem { Ok(target_path) } + fn create_file(&self, path: &Path, content: String) -> super::Result { + let target_path = self.target_dir.join(path); + if let Some(parent) = path.parent() { + fs_extra::dir::create_all(parent, false)?; + } + + let span = tracing::info_span!("create_file", target_path = %target_path.display()); + let _guard = span.enter(); + + tracing::debug!("starting file copy operation"); + + fs_extra::dir::create_all(target_path.parent().unwrap(), false).map_err(|error| { + tracing::debug!(error = %error, "error creating target parent directory"); + error + })?; + + fs_extra::file::write_all(&target_path, &content)?; + tracing::debug!("file created successfully"); + + Ok(target_path) + } + fn copy_dir(&self, directory_path: &Path) -> super::Result<()> { let source_path = self.source_dir.join(directory_path); let target_path = self.target_dir.join(directory_path); @@ -144,9 +168,10 @@ impl Executer for FileSystem { #[cfg(test)] mod tests { - use super::*; use tree_fs::TreeBuilder; + use super::*; + fn init_filesystem() -> FileSystem { let source_path = TreeBuilder::default() .add("test/foo.txt", "bar") diff --git a/loco-new/src/generator/executer/inmem.rs b/loco-new/src/generator/executer/inmem.rs index bd7266d0a..7852d6e01 100644 --- a/loco-new/src/generator/executer/inmem.rs +++ b/loco-new/src/generator/executer/inmem.rs @@ -1,7 +1,11 @@ +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + sync::Mutex, +}; + use super::Executer; use crate::{generator, settings::Settings}; -use std::path::{Path, PathBuf}; -use std::{collections::BTreeMap, sync::Mutex}; pub struct Inmem { pub source_path: PathBuf, @@ -45,6 +49,14 @@ impl Executer for Inmem { Ok(file_path.to_path_buf()) } + fn create_file(&self, path: &Path, content: String) -> super::Result { + self.file_store + .lock() + .unwrap() + .insert(path.to_path_buf(), content); + Ok(path.to_path_buf()) + } + fn copy_dir(&self, directory_path: &Path) -> super::Result<()> { let directory_content = fs_extra::dir::get_dir_content(directory_path)?; for file in directory_content.files { diff --git a/loco-new/src/generator/executer/mod.rs b/loco-new/src/generator/executer/mod.rs index 1e1bb9fb6..f6da8975e 100644 --- a/loco-new/src/generator/executer/mod.rs +++ b/loco-new/src/generator/executer/mod.rs @@ -6,11 +6,12 @@ use crate::settings::Settings; mod filesystem; mod inmem; +use std::path::{Path, PathBuf}; + pub use filesystem::FileSystem; pub use inmem::Inmem; #[cfg(test)] use mockall::{automock, predicate::*}; -use std::path::{Path, PathBuf}; pub type Result = std::result::Result; @@ -41,29 +42,40 @@ pub trait Executer: Send + Sync { /// /// # Errors /// - /// Returns an error if the file cannot be copied, such as if the path is invalid - /// or if a file system error occurs. + /// Returns an error if the file cannot be copied, such as if the path is + /// invalid or if a file system error occurs. fn copy_file(&self, path: &Path) -> Result; + /// Copies a single file from the specified path. + /// + /// # Errors + /// + /// Returns an error if the file cannot be copied, such as if the path is + /// invalid or if a file system error occurs. + fn create_file(&self, path: &Path, content: String) -> Result; + /// Copies an entire directory from the specified path. /// /// # Errors /// - /// Returns an error if the directory cannot be copied, such as if the path is invalid - /// or if a file system error occurs. + /// Returns an error if the directory cannot be copied, such as if the path + /// is invalid or if a file system error occurs. fn copy_dir(&self, path: &Path) -> Result<()>; /// Copies a template file from the specified path, applying settings. /// /// # Errors /// - /// Returns an error if the template cannot be copied or if any settings-related error occurs. + /// Returns an error if the template cannot be copied or if any + /// settings-related error occurs. fn copy_template(&self, path: &Path, data: &Settings) -> Result<()>; - /// Copies an entire template directory from the specified path, applying settings. + /// Copies an entire template directory from the specified path, applying + /// settings. /// /// # Errors /// - /// Returns an error if the template directory cannot be copied or if any settings-related error occurs. + /// Returns an error if the template directory cannot be copied or if any + /// settings-related error occurs. fn copy_template_dir(&self, path: &Path, data: &Settings) -> Result<()>; } diff --git a/loco-new/src/generator/mod.rs b/loco-new/src/generator/mod.rs index 09344e06c..16713f939 100644 --- a/loco-new/src/generator/mod.rs +++ b/loco-new/src/generator/mod.rs @@ -1,19 +1,22 @@ -//! This module defines the `Generator` struct, which is responsible for executing -//! scripted commands for file and template operations. It integrates with an -//! executor to perform file manipulations and uses a scripting engine to run -//! custom scripts based on application settings. +//! This module defines the `Generator` struct, which is responsible for +//! executing scripted commands for file and template operations. It integrates +//! with an executor to perform file manipulations and uses a scripting engine +//! to run custom scripts based on application settings. use std::path::{Path, PathBuf}; pub mod executer; pub mod template; -use crate::settings; +use std::sync::Arc; + use include_dir::{include_dir, Dir}; use rhai::{Engine, Scope}; -use std::sync::Arc; + +use crate::settings; static APP_TEMPLATE: Dir<'_> = include_dir!("loco-new/base_template"); -/// Extracts a default template to a temporary directory for use by the application. +/// Extracts a default template to a temporary directory for use by the +/// application. /// /// # Errors /// when could not extract the the base template @@ -29,8 +32,9 @@ pub fn extract_default_template() -> std::io::Result { Ok(generator_tmp_folder) } -/// The `Generator` struct provides functionality to execute scripted operations, -/// such as copying files and templates, based on the current settings. +/// The `Generator` struct provides functionality to execute scripted +/// operations, such as copying files and templates, based on the current +/// settings. #[derive(Clone)] pub struct Generator { pub executer: Arc, @@ -68,6 +72,7 @@ impl Generator { .build_type::() .build_type::() .register_fn("copy_file", Self::copy_file) + .register_fn("create_file", Self::create_file) .register_fn("copy_files", Self::copy_files) .register_fn("copy_dir", Self::copy_dir) .register_fn("copy_dirs", Self::copy_dirs) @@ -107,6 +112,25 @@ impl Generator { Ok(()) } + pub fn create_file( + &mut self, + path: &str, + content: &str, + ) -> Result<(), Box> { + let span = tracing::info_span!("create_file", path); + let _guard = span.enter(); + + self.executer + .create_file(Path::new(path), content.to_string()) + .map_err(|err| { + Box::new(rhai::EvalAltResult::ErrorSystem( + "create_file".to_string(), + err.into(), + )) + })?; + Ok(()) + } + /// Copies list of files from the specified path. /// /// # Errors @@ -184,7 +208,8 @@ impl Generator { }) } - /// Copies an entire template directory from the specified path, applying settings. + /// Copies an entire template directory from the specified path, applying + /// settings. /// /// # Errors /// @@ -205,10 +230,11 @@ impl Generator { #[cfg(test)] mod tests { - use super::*; use executer::MockExecuter; use mockall::predicate::*; + use super::*; + #[test] pub fn can_copy_file() { let mut executor = MockExecuter::new(); diff --git a/loco-new/src/settings.rs b/loco-new/src/settings.rs index e46e85cda..83e1f5aff 100644 --- a/loco-new/src/settings.rs +++ b/loco-new/src/settings.rs @@ -18,6 +18,7 @@ pub struct Settings { pub asset: Option, pub auth: bool, pub mailer: bool, + pub clientside: bool, pub initializers: Option, pub features: Features, pub loco_version_text: String, @@ -75,6 +76,7 @@ impl Settings { db: prompt_selection.db.clone().into(), background: prompt_selection.background.clone().into(), asset: prompt_selection.asset.clone().into(), + clientside: prompt_selection.asset.enable(), initializers: if prompt_selection.asset.enable() { Some(Initializers { view_engine: true }) } else { @@ -95,6 +97,7 @@ impl Default for Settings { asset: Default::default(), auth: Default::default(), mailer: Default::default(), + clientside: Default::default(), initializers: Default::default(), features: Default::default(), loco_version_text: get_loco_version_text(),