From 3ee47e9481232764ff322ad0bbbb37b0ec502f08 Mon Sep 17 00:00:00 2001 From: Valerio Ageno <51341197+Valerioageno@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:01:29 +0100 Subject: [PATCH] feat: load `config` in tuono CLI (#399) --- crates/tuono/Cargo.toml | 1 + crates/tuono/src/app.rs | 53 ++++++++++++++++++++++++++--- crates/tuono/src/cli.rs | 45 ++++++------------------ crates/tuono/src/mode.rs | 2 +- crates/tuono/tests/cli_build.rs | 16 +++++---- crates/tuono_internal/src/config.rs | 4 +-- 6 files changed, 73 insertions(+), 48 deletions(-) diff --git a/crates/tuono/Cargo.toml b/crates/tuono/Cargo.toml index ce5b7fce..e98ee5e9 100644 --- a/crates/tuono/Cargo.toml +++ b/crates/tuono/Cargo.toml @@ -30,6 +30,7 @@ reqwest = { version = "0.12.4", features = ["blocking", "json"] } serde_json = "1.0" fs_extra = "1.3.0" http = "1.1.0" +tuono_internal = {path = "../tuono_internal", version = "0.17.3"} [dev-dependencies] tempfile = "3.14.0" diff --git a/crates/tuono/src/app.rs b/crates/tuono/src/app.rs index be569ece..9742841e 100644 --- a/crates/tuono/src/app.rs +++ b/crates/tuono/src/app.rs @@ -1,3 +1,4 @@ +use crate::mode::Mode; use glob::glob; use glob::GlobError; use http::Method; @@ -11,6 +12,7 @@ use std::path::PathBuf; use std::process::Child; use std::process::Command; use std::process::Stdio; +use tuono_internal::config::Config; use crate::route::Route; @@ -33,11 +35,12 @@ const BUILD_JS_SCRIPT: &str = "./node_modules/.bin/tuono-build-prod"; #[cfg(not(target_os = "windows"))] const BUILD_TUONO_CONFIG: &str = "./node_modules/.bin/tuono-build-config"; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct App { pub route_map: HashMap, pub base_path: PathBuf, pub has_app_state: bool, + pub config: Option, } fn has_app_state(base_path: PathBuf) -> std::io::Result { @@ -56,6 +59,7 @@ impl App { route_map: HashMap::new(), base_path: base_path.clone(), has_app_state: has_app_state(base_path).unwrap_or(false), + config: None, }; app.collect_routes(); @@ -138,6 +142,38 @@ impl App { self.route_map.iter().any(|(_, route)| route.is_dynamic) } + pub fn check_server_availability(&self, mode: Mode) { + // At this point the config should be available + let config = self.config.as_ref().unwrap(); + + let rust_listener = + std::net::TcpListener::bind(format!("{}:{}", config.server.host, config.server.port)); + + if let Err(_e) = rust_listener { + eprintln!("Error: Failed to bind to port {}", config.server.port); + eprintln!( + "Please ensure that port {} is not already in use by another process or application.", + config.server.port + ); + std::process::exit(1); + } + + if mode == Mode::Dev { + let vite_port = config.server.port + 1; + let vite_listener = + std::net::TcpListener::bind(format!("{}:{}", config.server.host, vite_port)); + + if let Err(_e) = vite_listener { + eprintln!("Error: Failed to bind to port {}", vite_port); + eprintln!( + "Please ensure that port {} is not already in use by another process or application.", + vite_port + ); + std::process::exit(1); + } + } + } + pub fn build_react_prod(&self) { if !Path::new(BUILD_JS_SCRIPT).exists() { eprintln!("Failed to find the build script. Please run `npm install`"); @@ -163,16 +199,25 @@ impl App { .expect("Failed to run the rust server") } - pub fn build_tuono_config(&self) -> Result { + pub fn build_tuono_config(&mut self) -> Result { if !Path::new(BUILD_TUONO_CONFIG).exists() { eprintln!("Failed to find the build script. Please run `npm install`"); std::process::exit(1); } - Command::new(BUILD_TUONO_CONFIG) + let config_build_command = Command::new(BUILD_TUONO_CONFIG) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .output() + .output(); + + if let Ok(config) = Config::get() { + self.config = Some(config); + } else { + eprintln!("[CLI] Failed to read tuono.config.ts"); + std::process::exit(1); + }; + + config_build_command } pub fn get_used_http_methods(&self) -> HashSet { let mut acc = HashSet::new(); diff --git a/crates/tuono/src/cli.rs b/crates/tuono/src/cli.rs index 0cb64458..ec4d80ae 100644 --- a/crates/tuono/src/cli.rs +++ b/crates/tuono/src/cli.rs @@ -11,9 +11,6 @@ use crate::scaffold_project; use crate::source_builder::{bundle_axum_source, check_tuono_folder, create_client_entry_files}; use crate::watch; -const TUONO_PORT: u16 = 3000; -const VITE_PORT: u16 = 3001; - #[derive(Subcommand, Debug)] enum Actions { /// Start the development environment @@ -54,47 +51,22 @@ fn init_tuono_folder(mode: Mode) -> std::io::Result { Ok(app) } -fn check_ports(mode: Mode) { - let rust_listener = std::net::TcpListener::bind(format!("0.0.0.0:{TUONO_PORT}")); - - if let Err(_e) = rust_listener { - eprintln!("Error: Failed to bind to port {}", TUONO_PORT); - eprintln!( - "Please ensure that port {} is not already in use by another process or application.", - TUONO_PORT - ); - std::process::exit(1); - } - - if mode == Mode::Dev { - let vite_listener = std::net::TcpListener::bind(format!("0.0.0.0:{VITE_PORT}")); - - if let Err(_e) = vite_listener { - eprintln!("Error: Failed to bind to port {}", VITE_PORT); - eprintln!( - "Please ensure that port {} is not already in use by another process or application.", - VITE_PORT - ); - std::process::exit(1); - } - } -} - pub fn app() -> std::io::Result<()> { let args = Args::parse(); match args.action { Actions::Dev => { - check_ports(Mode::Dev); + let mut app = init_tuono_folder(Mode::Dev)?; - let app = init_tuono_folder(Mode::Dev)?; app.build_tuono_config() .expect("Failed to build tuono.config.ts"); + app.check_server_availability(Mode::Dev); + watch::watch().unwrap(); } Actions::Build { ssg, no_js_emit } => { - let app = init_tuono_folder(Mode::Prod)?; + let mut app = init_tuono_folder(Mode::Prod)?; if no_js_emit { println!("Rust build successfully finished"); @@ -110,11 +82,11 @@ pub fn app() -> std::io::Result<()> { app.build_tuono_config() .expect("Failed to build tuono.config.ts"); + app.check_server_availability(Mode::Prod); + app.build_react_prod(); if ssg { - check_ports(Mode::Prod); - println!("SSG: generation started"); let static_dir = PathBuf::from("out/static"); @@ -145,8 +117,11 @@ pub fn app() -> std::io::Result<()> { // Wait for server let mut is_server_ready = false; + let config = app.config.as_ref().unwrap(); + while !is_server_ready { - let server_url = format!("http://localhost:{}", TUONO_PORT); + let server_url = + format!("http://{}:{}", config.server.host, config.server.port); if reqwest.get(server_url).send().is_ok() { is_server_ready = true } diff --git a/crates/tuono/src/mode.rs b/crates/tuono/src/mode.rs index e54cd212..8f49bc3d 100644 --- a/crates/tuono/src/mode.rs +++ b/crates/tuono/src/mode.rs @@ -1,4 +1,4 @@ -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Debug, Clone)] pub enum Mode { Prod, Dev, diff --git a/crates/tuono/tests/cli_build.rs b/crates/tuono/tests/cli_build.rs index 54b4469b..c5d6c74d 100644 --- a/crates/tuono/tests/cli_build.rs +++ b/crates/tuono/tests/cli_build.rs @@ -7,6 +7,12 @@ use utils::TempTuonoProject; const POST_API_FILE: &str = r"#[tuono_lib::api(POST)]"; const GET_API_FILE: &str = r"#[tuono_lib::api(GET)]"; +#[cfg(target_os = "windows")] +const BUILD_TUONO_CONFIG: &str = ".\\node_modules\\.bin\\tuono-build-config.cmd"; + +#[cfg(not(target_os = "windows"))] +const BUILD_TUONO_CONFIG: &str = "./node_modules/.bin/tuono-build-config"; + #[test] #[serial] fn it_successfully_create_the_index_route() { @@ -144,7 +150,7 @@ fn it_successfully_create_catch_all_routes() { #[test] #[serial] fn it_fails_without_installed_build_config_script() { - TempTuonoProject::new(); + let _guard = TempTuonoProject::new(); let mut test_tuono_build = Command::cargo_bin("tuono").unwrap(); test_tuono_build @@ -158,12 +164,10 @@ fn it_fails_without_installed_build_config_script() { #[serial] fn it_fails_without_installed_build_script() { let temp_tuono_project = TempTuonoProject::new(); - - temp_tuono_project - .add_file_with_content("./node_modules/.bin/tuono-build-config", "#!/bin/bash"); + temp_tuono_project.add_file_with_content(BUILD_TUONO_CONFIG, "#!/bin/bash"); Command::new("chmod") .arg("+x") - .arg("./node_modules/.bin/tuono-build-config") + .arg(BUILD_TUONO_CONFIG) .assert() .success(); let mut test_tuono_build = Command::cargo_bin("tuono").unwrap(); @@ -171,5 +175,5 @@ fn it_fails_without_installed_build_script() { .arg("build") .assert() .failure() - .stderr("Failed to find the build script. Please run `npm install`\n"); + .stderr("[CLI] Failed to read tuono.config.ts\n"); } diff --git a/crates/tuono_internal/src/config.rs b/crates/tuono_internal/src/config.rs index 3340f9fc..ccebcad6 100644 --- a/crates/tuono_internal/src/config.rs +++ b/crates/tuono_internal/src/config.rs @@ -3,13 +3,13 @@ use std::fs::read_to_string; use std::io; use std::path::PathBuf; -#[derive(Deserialize)] +#[derive(Deserialize, Debug, Clone)] pub struct ServerConfig { pub host: String, pub port: u16, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug, Clone)] pub struct Config { pub server: ServerConfig, }