feat: load config in tuono CLI (#399)

This commit is contained in:
Valerio Ageno
2025-01-22 18:01:29 +01:00
committed by GitHub
parent 4e649acd60
commit 3ee47e9481
6 changed files with 73 additions and 48 deletions
+1
View File
@@ -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"
+49 -4
View File
@@ -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<String, Route>,
pub base_path: PathBuf,
pub has_app_state: bool,
pub config: Option<Config>,
}
fn has_app_state(base_path: PathBuf) -> std::io::Result<bool> {
@@ -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<std::process::Output, std::io::Error> {
pub fn build_tuono_config(&mut self) -> Result<std::process::Output, std::io::Error> {
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<Method> {
let mut acc = HashSet::new();
+10 -35
View File
@@ -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<App> {
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
}
+1 -1
View File
@@ -1,4 +1,4 @@
#[derive(PartialEq, Eq)]
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Mode {
Prod,
Dev,
+10 -6
View File
@@ -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");
}
+2 -2
View File
@@ -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,
}