11use camino:: { Utf8Path , Utf8PathBuf } ;
22use std:: net:: SocketAddr ;
3+ use std:: sync:: mpsc:: Receiver ;
4+ use std:: thread;
35
46use oranda:: config:: Config ;
57use oranda:: errors:: * ;
@@ -8,6 +10,7 @@ use axum::{http::StatusCode, routing::get_service, Router};
810
911use clap:: Parser ;
1012use tower_http:: services:: ServeDir ;
13+ use tower_livereload:: LiveReloadLayer ;
1114
1215#[ derive( Debug , Default , Parser ) ]
1316pub struct Serve {
@@ -23,19 +26,27 @@ impl Serve {
2326 }
2427
2528 pub fn run ( & self ) -> Result < ( ) > {
26- let workspace_config_path = & Utf8PathBuf :: from ( "./oranda-workspace.json" ) ;
27- let config = if workspace_config_path. exists ( ) {
28- Config :: build ( workspace_config_path) ?
29+ let config = Self :: build_config ( ) ?;
30+ if Utf8Path :: new ( & config. build . dist_dir ) . is_dir ( ) {
31+ self . serve ( & config. build . dist_dir , & config. build . path_prefix , None ) ?;
32+ Ok ( ( ) )
2933 } else {
30- Config :: build ( & Utf8PathBuf :: from ( "./oranda.json" ) ) ?
31- } ;
34+ Err ( OrandaError :: BuildNotFound {
35+ dist_dir : config. build . dist_dir . to_string ( ) ,
36+ } )
37+ }
38+ }
39+
40+ pub fn run_with_livereload ( & self , rx : Receiver < ( ) > ) -> Result < ( ) > {
41+ let config = Self :: build_config ( ) ?;
3242 if Utf8Path :: new ( & config. build . dist_dir ) . is_dir ( ) {
33- if let Some ( prefix) = config. build . path_prefix {
34- tracing:: debug!( "`path_prefix` configured: {}" , & prefix) ;
35- self . serve_prefix ( & config. build . dist_dir , & prefix) ?;
36- } else {
37- self . serve ( & config. build . dist_dir ) ?;
38- }
43+ let livereload = LiveReloadLayer :: new ( ) ;
44+ self . serve (
45+ & config. build . dist_dir ,
46+ & config. build . path_prefix ,
47+ Some ( ( livereload, rx) ) ,
48+ ) ?;
49+
3950 Ok ( ( ) )
4051 } else {
4152 Err ( OrandaError :: BuildNotFound {
@@ -45,7 +56,12 @@ impl Serve {
4556 }
4657
4758 #[ tokio:: main]
48- async fn serve ( & self , dist_dir : & str ) -> Result < ( ) > {
59+ async fn serve (
60+ & self ,
61+ dist_dir : & str ,
62+ path_prefix : & Option < String > ,
63+ livereload : Option < ( LiveReloadLayer , Receiver < ( ) > ) > ,
64+ ) -> Result < ( ) > {
4965 let serve_dir =
5066 get_service ( ServeDir :: new ( dist_dir) ) . handle_error ( |error : std:: io:: Error | async move {
5167 (
@@ -54,37 +70,45 @@ impl Serve {
5470 )
5571 } ) ;
5672
57- let app = Router :: new ( ) . nest_service ( "/" , serve_dir) ;
58-
59- let addr = SocketAddr :: from ( ( [ 127 , 0 , 0 , 1 ] , self . port ) ) ;
60- let msg = format ! ( "Your project is available at: http://{}" , addr) ;
61- tracing:: info!( success = true , "{}" , & msg) ;
62- axum:: Server :: bind ( & addr)
63- . serve ( app. into_make_service ( ) )
64- . await
65- . expect ( "failed to start server" ) ;
66- Ok ( ( ) )
67- }
73+ let prefix_route = if let Some ( prefix) = path_prefix {
74+ format ! ( "/{}" , prefix)
75+ } else {
76+ "/" . to_string ( )
77+ } ;
78+ let mut app = Router :: new ( ) . nest_service ( & prefix_route, serve_dir) ;
79+ if let Some ( livereload) = livereload {
80+ let ( livereload, rx) = livereload;
81+ let reloader = livereload. reloader ( ) ;
82+ app = app. layer ( livereload) ;
6883
69- #[ tokio:: main]
70- async fn serve_prefix ( & self , dist_dir : & str , prefix : & str ) -> Result < ( ) > {
71- let serve_dir =
72- get_service ( ServeDir :: new ( dist_dir) ) . handle_error ( |error : std:: io:: Error | async move {
73- (
74- StatusCode :: INTERNAL_SERVER_ERROR ,
75- format ! ( "Unhandled internal error: {}" , error) ,
76- )
84+ // Because the server will later block this thread, spawn another thread to handle
85+ // reload request messages.
86+ thread:: spawn ( move || loop {
87+ rx. recv ( ) . expect ( "broken pipe" ) ;
88+ reloader. reload ( ) ;
7789 } ) ;
78- let prefix_route = format ! ( "/{}" , prefix) ;
79- let app = Router :: new ( ) . nest_service ( & prefix_route, serve_dir) ;
90+ }
8091
8192 let addr = SocketAddr :: from ( ( [ 127 , 0 , 0 , 1 ] , self . port ) ) ;
82- let msg = format ! ( "Your project is available at: http://{}/{}" , addr, prefix) ;
93+ let msg = format ! (
94+ "Your project is available at: http://{}/{}" ,
95+ addr,
96+ path_prefix. as_ref( ) . unwrap_or( & String :: new( ) )
97+ ) ;
8398 tracing:: info!( success = true , "{}" , & msg) ;
8499 axum:: Server :: bind ( & addr)
85100 . serve ( app. into_make_service ( ) )
86101 . await
87102 . expect ( "failed to start server" ) ;
88103 Ok ( ( ) )
89104 }
105+
106+ fn build_config ( ) -> Result < Config > {
107+ let workspace_config_path = & Utf8PathBuf :: from ( "./oranda-workspace.json" ) ;
108+ if workspace_config_path. exists ( ) {
109+ Config :: build ( workspace_config_path)
110+ } else {
111+ Config :: build ( & Utf8PathBuf :: from ( "./oranda.json" ) )
112+ }
113+ }
90114}
0 commit comments