From c6f5ad4dac842dd1b4a73655ca590a89267a49b8 Mon Sep 17 00:00:00 2001 From: Valerio Ageno Date: Sat, 15 Feb 2025 16:56:18 +0100 Subject: [PATCH 1/6] feat: setup default tracing subscriber --- crates/tuono/Cargo.toml | 2 ++ crates/tuono/src/cli.rs | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/crates/tuono/Cargo.toml b/crates/tuono/Cargo.toml index 19393528..36eddaf9 100644 --- a/crates/tuono/Cargo.toml +++ b/crates/tuono/Cargo.toml @@ -19,6 +19,8 @@ path = "src/lib.rs" [dependencies] clap = { version = "4.5.4", features = ["derive", "cargo"] } watchexec = "5.0.0" +tracing = "0.1.41" +tracing-subscriber = {version = "0.3.19", features = ["env-filter"]} miette = "7.2.0" watchexec-signals = "4.0.0" tokio = { version = "1", features = ["full"] } diff --git a/crates/tuono/src/cli.rs b/crates/tuono/src/cli.rs index 520933cf..cf1275ec 100644 --- a/crates/tuono/src/cli.rs +++ b/crates/tuono/src/cli.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; +use tracing::{debug, span, Level}; +use tracing_subscriber::EnvFilter; use crate::app::App; use crate::build; @@ -61,10 +63,20 @@ fn init_tuono_folder(mode: Mode) -> std::io::Result { } pub fn app() -> std::io::Result<()> { + tracing_subscriber::fmt() + // Not need for the time since the code is synchronous + .without_time() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + let args = Args::parse(); match args.action { Actions::Dev => { + let span = span!(Level::DEBUG, "DEV"); + + let _guard = span.enter(); + let mut app = init_tuono_folder(Mode::Dev)?; app.build_tuono_config() @@ -75,6 +87,10 @@ pub fn app() -> std::io::Result<()> { watch::watch().unwrap(); } Actions::Build { ssg, no_js_emit } => { + let span = span!(Level::DEBUG, "BUILD"); + + let _guard = span.enter(); + let app = init_tuono_folder(Mode::Prod)?; build::build(app, ssg, no_js_emit); @@ -84,6 +100,10 @@ pub fn app() -> std::io::Result<()> { template, head, } => { + let span = span!(Level::DEBUG, "NEW"); + + let _guard = span.enter(); + scaffold_project::create_new_project(folder_name, template, head); } } From 98910202077ccb756b58bc79d2607e93d3182c40 Mon Sep 17 00:00:00 2001 From: Valerio Ageno Date: Sun, 16 Feb 2025 18:10:09 +0100 Subject: [PATCH 2/6] feat: update base level to TRACE --- crates/tuono/src/cli.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/tuono/src/cli.rs b/crates/tuono/src/cli.rs index cf1275ec..b19ff4a0 100644 --- a/crates/tuono/src/cli.rs +++ b/crates/tuono/src/cli.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; -use tracing::{debug, span, Level}; +use tracing::{span, Level}; use tracing_subscriber::EnvFilter; use crate::app::App; @@ -73,7 +73,7 @@ pub fn app() -> std::io::Result<()> { match args.action { Actions::Dev => { - let span = span!(Level::DEBUG, "DEV"); + let span = span!(Level::TRACE, "DEV"); let _guard = span.enter(); @@ -87,7 +87,7 @@ pub fn app() -> std::io::Result<()> { watch::watch().unwrap(); } Actions::Build { ssg, no_js_emit } => { - let span = span!(Level::DEBUG, "BUILD"); + let span = span!(Level::TRACE, "BUILD"); let _guard = span.enter(); @@ -100,7 +100,7 @@ pub fn app() -> std::io::Result<()> { template, head, } => { - let span = span!(Level::DEBUG, "NEW"); + let span = span!(Level::TRACE, "NEW"); let _guard = span.enter(); From 8f4947ed6997f6462d642a6646e0af15f56e3f79 Mon Sep 17 00:00:00 2001 From: Valerio Ageno Date: Sun, 16 Feb 2025 18:53:23 +0100 Subject: [PATCH 3/6] feat: add tracing to 'tuono build' CMD --- crates/tuono/src/app.rs | 16 ++++--- crates/tuono/src/build.rs | 39 +++++++++++++---- crates/tuono/src/route.rs | 89 ++++++++++++++++++++++++++++++++------- 3 files changed, 114 insertions(+), 30 deletions(-) diff --git a/crates/tuono/src/app.rs b/crates/tuono/src/app.rs index 9742841e..86db505c 100644 --- a/crates/tuono/src/app.rs +++ b/crates/tuono/src/app.rs @@ -12,6 +12,7 @@ use std::path::PathBuf; use std::process::Child; use std::process::Command; use std::process::Stdio; +use tracing::error; use tuono_internal::config::Config; use crate::route::Route; @@ -176,15 +177,18 @@ impl App { pub fn build_react_prod(&self) { if !Path::new(BUILD_JS_SCRIPT).exists() { - eprintln!("Failed to find the build script. Please run `npm install`"); + error!("Failed to find the build script. Please run `npm install`"); std::process::exit(1); } - let output = Command::new(BUILD_JS_SCRIPT) - .output() - .expect("Failed to build the react source"); + + let output = Command::new(BUILD_JS_SCRIPT).output().unwrap_or_else(|_| { + error!("Failed to build the react source"); + std::process::exit(1); + }); + if !output.status.success() { - eprintln!("Failed to build the react source"); - eprintln!("Error: {}", String::from_utf8_lossy(&output.stderr)); + error!("Failed to build the react source"); + error!("Error: {}", String::from_utf8_lossy(&output.stderr)); std::process::exit(1); } } diff --git a/crates/tuono/src/build.rs b/crates/tuono/src/build.rs index a3202961..375c024b 100644 --- a/crates/tuono/src/build.rs +++ b/crates/tuono/src/build.rs @@ -3,24 +3,30 @@ use spinners::{Spinner, Spinners}; use std::path::PathBuf; use std::thread::sleep; use std::time::Duration; +use tracing::{error, trace}; use crate::app::App; use crate::mode::Mode; +fn gracefully_exit_with_error(msg: &str) -> ! { + error!(msg); + std::process::exit(1); +} + pub fn build(mut app: App, ssg: bool, no_js_emit: bool) { if no_js_emit { println!("Rust build successfully finished"); - return; + std::process::exit(0); } if ssg && app.has_dynamic_routes() { // TODO: allow dynamic routes static generation println!("Cannot statically build dynamic routes"); - return; + std::process::exit(1); } app.build_tuono_config() - .expect("Failed to build tuono.config.ts"); + .unwrap_or_else(|_| gracefully_exit_with_error("Failed to build tuono.config.ts")); let mut app_build_spinner = Spinner::new(Spinners::Dots, "Building app...".into()); @@ -38,41 +44,58 @@ pub fn build(mut app: App, ssg: bool, no_js_emit: bool) { let static_dir = PathBuf::from("out/static"); if static_dir.is_dir() { - std::fs::remove_dir_all(&static_dir).expect("Failed to clear the out/static folder"); + std::fs::remove_dir_all(&static_dir).unwrap_or_else(|_| { + gracefully_exit_with_error("Failed to clear the out/static folder") + }); } - std::fs::create_dir(&static_dir).expect("Failed to create static output dir"); + std::fs::create_dir(&static_dir) + .unwrap_or_else(|_| gracefully_exit_with_error("Failed to create static output dir")); copy( "./out/client", static_dir, &CopyOptions::new().overwrite(true).content_only(true), ) - .expect("Failed to clone assets into static output folder"); + .unwrap_or_else(|_| { + gracefully_exit_with_error("Failed to clone assets into static output folder") + }); // Start the server #[allow(clippy::zombie_processes)] let mut rust_server = app.run_rust_server(); + let mut exit_and_shut_server = |msg: &str| -> ! { + _ = rust_server.kill(); + gracefully_exit_with_error(msg) + }; + let reqwest_client = reqwest::blocking::Client::builder() .user_agent("") .build() - .expect("Failed to build reqwest client"); + .unwrap_or_else(|_| exit_and_shut_server("Failed to build reqwest client")); // Wait for server let mut is_server_ready = false; let config = app.config.as_ref().unwrap(); while !is_server_ready { + trace!("Checking server availability"); let server_url = format!("http://{}:{}", config.server.host, config.server.port); if reqwest_client.get(&server_url).send().is_ok() { is_server_ready = true; + } else { + trace!("Server not ready yet. Sleeping for 1 second"); } sleep(Duration::from_secs(1)); } + trace!("Server is ready, starting static site generation"); + for (_, route) in app.route_map { - route.save_ssg_file(&reqwest_client); + if let Err(msg) = route.save_ssg_file(&reqwest_client) { + exit_and_shut_server(&msg); + } } // Close server diff --git a/crates/tuono/src/route.rs b/crates/tuono/src/route.rs index 5255dbfd..c330e71f 100644 --- a/crates/tuono/src/route.rs +++ b/crates/tuono/src/route.rs @@ -7,6 +7,7 @@ use std::fs::File; use std::io; use std::path::PathBuf; use std::str::FromStr; +use tracing::trace; fn has_dynamic_path(route: &str) -> bool { let regex = Regex::new(r"\[(.*?)\]").expect("Failed to create the regex"); @@ -141,35 +142,72 @@ impl Route { self.axum_info = Some(AxumInfo::new(self)) } - pub fn save_ssg_file(&self, reqwest: &Client) { + pub fn save_ssg_file(&self, reqwest: &Client) -> Result<(), String> { let path = &self.path.replace("index", ""); - let mut response = reqwest - .get(format!("http://localhost:3000{path}")) - .send() - .unwrap(); + let url = format!("http://localhost:3000{path}"); + + trace!("Requesting the page: {}", url); + let mut response = match reqwest.get(format!("http://localhost:3000{path}")).send() { + Ok(response) => response, + Err(_) => return Err(format!("Failed to get the response: {}", url)), + }; let file_path = self.output_file_path(); - let parent_dir = file_path.parent().unwrap(); + let parent_dir = match file_path.parent() { + Some(parent_dir) => parent_dir, + None => { + return Err(format!( + "Failed to get the parent directory {:?}", + file_path + )) + } + }; if !parent_dir.is_dir() { - create_all(parent_dir, false).expect("Failed to create parent directories"); + if let Err(_) = create_all(parent_dir, false) { + return Err(format!( + "Failed to create the parent directory {:?}", + parent_dir + )); + } } - let mut file = File::create(file_path).expect("Failed to create the HTML file"); + trace!("Saving the HTML file: {:?}", file_path); - io::copy(&mut response, &mut file).expect("Failed to write the HTML on the file"); + let mut file = match File::create(&file_path) { + Ok(file) => file, + Err(_) => return Err(format!("Failed to create the file: {:?}", file_path)), + }; + + if let Err(_) = io::copy(&mut response, &mut file) { + return Err(format!("Failed to write the file: {:?}", file_path)); + } // Saving also the server response if self.axum_info.is_some() { + trace!("The route is an axum route, saving the JSON file"); + let data_file_path = PathBuf::from(&format!("out/static/__tuono/data{path}")); - let data_parent_dir = data_file_path.parent().unwrap(); + let data_parent_dir = match data_file_path.parent() { + Some(parent_dir) => parent_dir, + None => { + return Err(format!( + "Failed to get the parent directory {:?}", + data_file_path + )) + } + }; if !data_parent_dir.is_dir() { - create_all(data_parent_dir, false) - .expect("Failed to create data parent directories"); + if let Err(_) = create_all(data_parent_dir, false) { + return Err(format!( + "Failed to create the parent directory {:?}", + data_parent_dir + )); + } } let base = Url::parse("http://localhost:3000/__tuono/data").unwrap(); @@ -182,13 +220,32 @@ impl Route { .join(pathname) .expect("Failed to build the reqwest URL"); - let mut response = reqwest.get(url).send().unwrap(); + trace!("Requesting the JSON file: {}", url); + + let mut response = match reqwest.get(url.clone()).send() { + Ok(response) => { + trace!("Successfully got the response for: {url}"); + response + } + Err(_) => return Err(format!("Failed to get the response: {url}")), + }; - let mut data_file = - File::create(data_file_path).expect("Failed to create the JSON file"); + let mut data_file = match File::create(&data_file_path) { + Ok(file) => file, + Err(_) => { + return Err(format!( + "Failed to create the JSON file: {:?}", + data_file_path + )) + } + }; - io::copy(&mut response, &mut data_file).expect("Failed to write the JSON on the file"); + if let Err(_) = io::copy(&mut response, &mut data_file) { + return Err(format!("Failed to write the JSON file")); + } } + + Ok(()) } fn output_file_path(&self) -> PathBuf { From 4ca2dc743a3c1d9157930913fb71285bef63f275 Mon Sep 17 00:00:00 2001 From: Valerio Ageno Date: Sun, 16 Feb 2025 20:52:47 +0100 Subject: [PATCH 4/6] fix: JSON file --static build --- crates/tuono/src/route.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/tuono/src/route.rs b/crates/tuono/src/route.rs index c330e71f..9303013f 100644 --- a/crates/tuono/src/route.rs +++ b/crates/tuono/src/route.rs @@ -189,7 +189,7 @@ impl Route { if self.axum_info.is_some() { trace!("The route is an axum route, saving the JSON file"); - let data_file_path = PathBuf::from(&format!("out/static/__tuono/data{path}")); + let data_file_path = PathBuf::from(&format!("out/static/__tuono/data{path}.json")); let data_parent_dir = match data_file_path.parent() { Some(parent_dir) => parent_dir, From a7a247aaf6280db9f9d50e0b09f417126fb3df14 Mon Sep 17 00:00:00 2001 From: Valerio Ageno Date: Sun, 16 Feb 2025 21:05:32 +0100 Subject: [PATCH 5/6] fix: API routes SSG --- crates/tuono/src/route.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/tuono/src/route.rs b/crates/tuono/src/route.rs index 9303013f..fc75d3c7 100644 --- a/crates/tuono/src/route.rs +++ b/crates/tuono/src/route.rs @@ -143,6 +143,10 @@ impl Route { } pub fn save_ssg_file(&self, reqwest: &Client) -> Result<(), String> { + if self.is_api() { + return Ok(()); + } + let path = &self.path.replace("index", ""); let url = format!("http://localhost:3000{path}"); @@ -165,13 +169,11 @@ impl Route { } }; - if !parent_dir.is_dir() { - if let Err(_) = create_all(parent_dir, false) { - return Err(format!( - "Failed to create the parent directory {:?}", - parent_dir - )); - } + if !parent_dir.is_dir() || create_all(parent_dir, false).is_err() { + return Err(format!( + "Failed to create the parent directory {:?}", + parent_dir + )); } trace!("Saving the HTML file: {:?}", file_path); @@ -181,7 +183,7 @@ impl Route { Err(_) => return Err(format!("Failed to create the file: {:?}", file_path)), }; - if let Err(_) = io::copy(&mut response, &mut file) { + if io::copy(&mut response, &mut file).is_err() { return Err(format!("Failed to write the file: {:?}", file_path)); } @@ -201,13 +203,11 @@ impl Route { } }; - if !data_parent_dir.is_dir() { - if let Err(_) = create_all(data_parent_dir, false) { - return Err(format!( - "Failed to create the parent directory {:?}", - data_parent_dir - )); - } + if !data_parent_dir.is_dir() && create_all(data_parent_dir, false).is_err() { + return Err(format!( + "Failed to create the parent directory {:?}", + data_parent_dir + )); } let base = Url::parse("http://localhost:3000/__tuono/data").unwrap(); @@ -240,8 +240,8 @@ impl Route { } }; - if let Err(_) = io::copy(&mut response, &mut data_file) { - return Err(format!("Failed to write the JSON file")); + if io::copy(&mut response, &mut data_file).is_err() { + return Err("Failed to write the JSON file".to_string()); } } From 03e3230b9dfb0d69b84d277f9a65ed4bb99c449e Mon Sep 17 00:00:00 2001 From: Valerio Ageno Date: Mon, 17 Feb 2025 19:20:26 +0100 Subject: [PATCH 6/6] improve error log --- crates/tuono/src/build.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/tuono/src/build.rs b/crates/tuono/src/build.rs index 375c024b..d2d6023a 100644 --- a/crates/tuono/src/build.rs +++ b/crates/tuono/src/build.rs @@ -8,7 +8,7 @@ use tracing::{error, trace}; use crate::app::App; use crate::mode::Mode; -fn gracefully_exit_with_error(msg: &str) -> ! { +fn exit_gracefully_with_error(msg: &str) -> ! { error!(msg); std::process::exit(1); } @@ -26,7 +26,7 @@ pub fn build(mut app: App, ssg: bool, no_js_emit: bool) { } app.build_tuono_config() - .unwrap_or_else(|_| gracefully_exit_with_error("Failed to build tuono.config.ts")); + .unwrap_or_else(|_| exit_gracefully_with_error("Failed to build tuono.config.ts")); let mut app_build_spinner = Spinner::new(Spinners::Dots, "Building app...".into()); @@ -41,16 +41,21 @@ pub fn build(mut app: App, ssg: bool, no_js_emit: bool) { let mut app_build_static_spinner = Spinner::new(Spinners::Dots, "Static site generation".into()); + let mut exit_gracefully_with_error = |msg: &str| -> ! { + app_build_static_spinner.stop_with_message("\u{274C} Build failed\n".into()); + exit_gracefully_with_error(msg) + }; + let static_dir = PathBuf::from("out/static"); if static_dir.is_dir() { std::fs::remove_dir_all(&static_dir).unwrap_or_else(|_| { - gracefully_exit_with_error("Failed to clear the out/static folder") + exit_gracefully_with_error("Failed to clear the out/static folder") }); } std::fs::create_dir(&static_dir) - .unwrap_or_else(|_| gracefully_exit_with_error("Failed to create static output dir")); + .unwrap_or_else(|_| exit_gracefully_with_error("Failed to create static output dir")); copy( "./out/client", @@ -58,7 +63,7 @@ pub fn build(mut app: App, ssg: bool, no_js_emit: bool) { &CopyOptions::new().overwrite(true).content_only(true), ) .unwrap_or_else(|_| { - gracefully_exit_with_error("Failed to clone assets into static output folder") + exit_gracefully_with_error("Failed to clone assets into static output folder") }); // Start the server @@ -67,7 +72,7 @@ pub fn build(mut app: App, ssg: bool, no_js_emit: bool) { let mut exit_and_shut_server = |msg: &str| -> ! { _ = rust_server.kill(); - gracefully_exit_with_error(msg) + exit_gracefully_with_error(msg) }; let reqwest_client = reqwest::blocking::Client::builder()