diff --git a/crates/tuono/src/build.rs b/crates/tuono/src/build.rs index d2d6023a..234911b0 100644 --- a/crates/tuono/src/build.rs +++ b/crates/tuono/src/build.rs @@ -86,12 +86,18 @@ pub fn build(mut app: App, ssg: bool, no_js_emit: bool) { while !is_server_ready { trace!("Checking server availability"); - let server_url = format!("http://{}:{}", config.server.host, config.server.port); + + let server_url = match &config.server.origin { + Some(origin) => origin.clone(), + None => 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)); } diff --git a/crates/tuono_internal/src/config.rs b/crates/tuono_internal/src/config.rs index 9e2229ff..f5c02866 100644 --- a/crates/tuono_internal/src/config.rs +++ b/crates/tuono_internal/src/config.rs @@ -6,6 +6,7 @@ use std::path::PathBuf; #[derive(Deserialize, Serialize, Debug, Clone)] pub struct ServerConfig { pub host: String, + pub origin: Option, pub port: u16, } diff --git a/crates/tuono_internal/tests/config.rs b/crates/tuono_internal/tests/config.rs index 35e668e1..d90a3782 100644 --- a/crates/tuono_internal/tests/config.rs +++ b/crates/tuono_internal/tests/config.rs @@ -20,6 +20,30 @@ fn should_correctly_read_the_config_file() { let config = config.unwrap(); assert_eq!(config.server.host, "localhost"); + assert_eq!(config.server.origin, None); + assert_eq!(config.server.port, 3000); +} + +#[test] +#[serial] +fn should_correctly_read_the_config_file_with_origin() { + let folder = TempTuonoProject::new(); + + folder.add_file_with_content( + "./.tuono/config/config.json", + r#"{ "server": {"host": "localhost", "origin": "https://tuono.localhost", "port": 3000}}"#, + ); + + let config = Config::get(); + + assert!(config.is_ok()); + + let config = config.unwrap(); + assert_eq!(config.server.host, "localhost".to_string()); + assert_eq!( + config.server.origin, + Some("https://tuono.localhost".to_string()) + ); assert_eq!(config.server.port, 3000); } diff --git a/crates/tuono_lib/src/server.rs b/crates/tuono_lib/src/server.rs index 8a7cf222..0bb5c24c 100644 --- a/crates/tuono_lib/src/server.rs +++ b/crates/tuono_lib/src/server.rs @@ -25,9 +25,31 @@ pub struct Server { mode: Mode, pub listener: tokio::net::TcpListener, pub address: String, + pub origin: Option, } impl Server { + fn display_start_message(&self) { + /* + * Format the server address as a valid URL so that it becomes clickable in the CLI + * @see https://github.com/tuono-labs/tuono/issues/460 + */ + let server_base_url = format!("http://{}", self.address); + + if self.mode == Mode::Dev { + println!(" Ready at: {}\n", server_base_url.blue().bold()); + } else { + println!( + " Production server at: {}\n", + server_base_url.blue().bold() + ); + } + + if let Some(origin) = &self.origin { + println!(" Origin: {}\n", origin.blue().bold()); + } + } + pub async fn init(router: Router, mode: Mode) -> Server { let config = Config::get().expect("[SERVER] Failed to load config"); @@ -44,6 +66,7 @@ impl Server { router, mode, address: server_address.clone(), + origin: config.server.origin.clone(), listener: tokio::net::TcpListener::bind(&server_address) .await .expect("[SERVER] Failed to bind to address"), @@ -51,14 +74,9 @@ impl Server { } pub async fn start(self) { - /* - * Format the server address as a valid URL so that it becomes clickable in the CLI - * @see https://github.com/tuono-labs/tuono/issues/460 - */ - let server_base_url = format!("http://{}", self.address); + self.display_start_message(); if self.mode == Mode::Dev { - println!(" Ready at: {}\n", server_base_url.blue().bold()); let router = self .router .to_owned() @@ -74,10 +92,6 @@ impl Server { .await .expect("Failed to serve development server"); } else { - println!( - " Production server at: {}\n", - server_base_url.blue().bold() - ); let router = self .router .to_owned() diff --git a/packages/tuono/src/build/config/create-json-config.spec.ts b/packages/tuono/src/build/config/create-json-config.spec.ts index f469a761..9a8a0306 100644 --- a/packages/tuono/src/build/config/create-json-config.spec.ts +++ b/packages/tuono/src/build/config/create-json-config.spec.ts @@ -1,4 +1,4 @@ -import fs from 'fs/promises' +import fs from 'node:fs/promises' import { beforeEach, describe, expect, it, vitest } from 'vitest' import react from '@vitejs/plugin-react-swc' @@ -12,9 +12,9 @@ describe('createJsonConfig', () => { writeFileSpy.mockClear() }) - const sampleConfig = { server: { host: 'h', port: 1 } } - it('should process config with only server property', async () => { + const sampleConfig = { server: { host: 'h', origin: null, port: 1 } } + await createJsonConfig(sampleConfig) expect(writeFileSpy).toHaveBeenCalledWith( @@ -25,6 +25,8 @@ describe('createJsonConfig', () => { }) it('should process config with plugins', async () => { + const sampleConfig = { server: { host: 'h', origin: null, port: 1 } } + await createJsonConfig({ ...sampleConfig, vite: { plugins: [react()] } }) expect(writeFileSpy).toHaveBeenCalledWith( @@ -33,4 +35,16 @@ describe('createJsonConfig', () => { expect.any(String), ) }) + + it('should process config with only server property including origin', async () => { + const sampleConfig = { server: { host: 'h', origin: 'o', port: 1 } } + + await createJsonConfig(sampleConfig) + + expect(writeFileSpy).toHaveBeenCalledWith( + expect.any(String), + expect.stringContaining(JSON.stringify(sampleConfig)), + expect.any(String), + ) + }) }) diff --git a/packages/tuono/src/build/config/normalize-config.spec.ts b/packages/tuono/src/build/config/normalize-config.spec.ts index cedbb980..fa155205 100644 --- a/packages/tuono/src/build/config/normalize-config.spec.ts +++ b/packages/tuono/src/build/config/normalize-config.spec.ts @@ -15,7 +15,11 @@ describe('normalizeConfig', () => { const config: TuonoConfig = {} expect(normalizeConfig(config)).toStrictEqual({ - server: { host: 'localhost', port: 3000 }, + server: { + host: 'localhost', + origin: null, + port: 3000, + }, vite: { alias: undefined, css: undefined, @@ -28,7 +32,11 @@ describe('normalizeConfig', () => { it('should return an empty config if invalid values are provided', () => { // @ts-expect-error testing invalid config expect(normalizeConfig({ invalid: true })).toStrictEqual({ - server: { host: 'localhost', port: 3000 }, + server: { + host: 'localhost', + origin: null, + port: 3000, + }, vite: { alias: undefined, css: undefined, @@ -55,6 +63,28 @@ describe('normalizeConfig', () => { }) }) + describe('server - origin', () => { + it('should assign the origin defined by the user', () => { + const config: TuonoConfig = { + server: { + host: '0.0.0.0', + origin: 'https://tuono.localhost', + port: 8080, + }, + } + + expect(normalizeConfig(config)).toStrictEqual( + expect.objectContaining({ + server: expect.objectContaining({ + host: '0.0.0.0', + origin: 'https://tuono.localhost', + port: 8080, + }) as unknown, + }), + ) + }) + }) + describe('vite - alias', () => { it('should not modify alias pointing to packages', () => { const libraryName = '@tabler/icons-react' diff --git a/packages/tuono/src/build/config/normalize-config.ts b/packages/tuono/src/build/config/normalize-config.ts index f4ff4ad2..73e55437 100644 --- a/packages/tuono/src/build/config/normalize-config.ts +++ b/packages/tuono/src/build/config/normalize-config.ts @@ -71,6 +71,7 @@ export const normalizeConfig = (config: TuonoConfig): InternalTuonoConfig => { return { server: { host: config.server?.host ?? 'localhost', + origin: config.server?.origin ?? null, port: config.server?.port ?? 3000, }, vite: { diff --git a/packages/tuono/src/config/types.ts b/packages/tuono/src/config/types.ts index 4a4fde86..43941f83 100644 --- a/packages/tuono/src/config/types.ts +++ b/packages/tuono/src/config/types.ts @@ -11,6 +11,7 @@ import type { export interface TuonoConfig { server?: { host?: string + origin?: string | null port?: number } vite?: { diff --git a/packages/tuono/src/shared/DevResources.tsx b/packages/tuono/src/shared/DevResources.tsx index 5a80dfe4..7808d52e 100644 --- a/packages/tuono/src/shared/DevResources.tsx +++ b/packages/tuono/src/shared/DevResources.tsx @@ -3,13 +3,16 @@ import type { JSX } from 'react' import { useTuonoContextServerPayload } from './TuonoContext' const VITE_PROXY_PATH = '/vite-server' -const DEFAULT_SERVER_CONFIG = { host: 'localhost', port: 3000 } +const DEFAULT_SERVER_CONFIG = { host: 'localhost', origin: null, port: 3000 } export const DevResources = (): JSX.Element => { const { devServerConfig } = useTuonoContextServerPayload() - const { host, port } = devServerConfig ?? DEFAULT_SERVER_CONFIG + const { host, origin, port } = devServerConfig ?? DEFAULT_SERVER_CONFIG - const viteBaseUrl = `http://${host}:${port}${VITE_PROXY_PATH}` + const viteBaseUrl = + origin != null + ? `${origin}${VITE_PROXY_PATH}` + : `http://${host}:${port}${VITE_PROXY_PATH}` return ( <> diff --git a/packages/tuono/src/types.ts b/packages/tuono/src/types.ts index a221d1bf..5c2fcb45 100644 --- a/packages/tuono/src/types.ts +++ b/packages/tuono/src/types.ts @@ -25,6 +25,7 @@ export interface ServerPayload { /** Available only on 'Dev' mode */ devServerConfig?: { port: number + origin: string | null host: string } }