11use crate :: build;
2- use crate :: build:: build_types:: SourceType ;
2+ use crate :: build:: build_types:: { BuildCommandState , SourceType } ;
33use crate :: build:: clean;
44use crate :: cmd;
5+ use crate :: config;
56use crate :: helpers;
67use crate :: helpers:: StrippedVerbatimPath ;
78use crate :: helpers:: emojis:: * ;
@@ -12,7 +13,7 @@ use anyhow::{Context, Result};
1213use futures_timer:: Delay ;
1314use notify:: event:: ModifyKind ;
1415use notify:: { Config , Error , Event , EventKind , RecommendedWatcher , RecursiveMode , Watcher } ;
15- use std:: path:: Path ;
16+ use std:: path:: { Path , PathBuf } ;
1617use std:: sync:: Arc ;
1718use std:: sync:: Mutex ;
1819use std:: time:: { Duration , Instant } ;
@@ -57,32 +58,116 @@ fn matches_filter(path_buf: &Path, filter: &Option<regex::Regex>) -> bool {
5758 filter. as_ref ( ) . map ( |re| !re. is_match ( & name) ) . unwrap_or ( true )
5859}
5960
61+ /// Computes the list of paths to watch based on the build state.
62+ /// Returns tuples of (path, recursive_mode) for each watch target.
63+ fn compute_watch_paths ( build_state : & BuildCommandState , root : & Path ) -> Vec < ( PathBuf , RecursiveMode ) > {
64+ // Use a HashMap to deduplicate paths, giving precedence to Recursive mode
65+ // when the same path appears with different modes (e.g. package root watched
66+ // NonRecursively for rescript.json changes, but also as a source folder with
67+ // Recursive mode).
68+ let mut watch_paths: std:: collections:: HashMap < PathBuf , RecursiveMode > = std:: collections:: HashMap :: new ( ) ;
69+
70+ let mut insert = |path : PathBuf , mode : RecursiveMode | {
71+ watch_paths
72+ . entry ( path)
73+ . and_modify ( |existing| {
74+ if mode == RecursiveMode :: Recursive {
75+ * existing = RecursiveMode :: Recursive ;
76+ }
77+ } )
78+ . or_insert ( mode) ;
79+ } ;
80+
81+ for ( _, package) in build_state. build_state . packages . iter ( ) {
82+ if !package. is_local_dep {
83+ continue ;
84+ }
85+
86+ // Watch the package root non-recursively to detect rescript.json changes.
87+ // We watch the directory rather than the file directly because many editors
88+ // use atomic writes (delete + recreate or write to temp + rename) which would
89+ // cause a direct file watch to be lost after the first edit.
90+ insert ( package. path . clone ( ) , RecursiveMode :: NonRecursive ) ;
91+
92+ // Watch each source folder
93+ for source in & package. source_folders {
94+ let dir = package. path . join ( & source. dir ) ;
95+ if !dir. exists ( ) {
96+ log:: error!(
97+ "Could not read folder: {:?}. Specified in dependency: {}, located {:?}..." ,
98+ source. dir,
99+ package. name,
100+ package. path
101+ ) ;
102+ continue ;
103+ }
104+ let mode = match & source. subdirs {
105+ Some ( config:: Subdirs :: Recurse ( true ) ) => RecursiveMode :: Recursive ,
106+ _ => RecursiveMode :: NonRecursive ,
107+ } ;
108+ insert ( dir, mode) ;
109+ }
110+ }
111+
112+ // Watch the lib/ directory for the lockfile (rescript.lock lives in lib/)
113+ let lib_dir = root. join ( "lib" ) ;
114+ if lib_dir. exists ( ) {
115+ insert ( lib_dir, RecursiveMode :: NonRecursive ) ;
116+ }
117+
118+ watch_paths. into_iter ( ) . collect ( )
119+ }
120+
121+ /// Registers all watch paths with the given watcher.
122+ fn register_watches ( watcher : & mut RecommendedWatcher , watch_paths : & [ ( PathBuf , RecursiveMode ) ] ) {
123+ for ( path, mode) in watch_paths {
124+ let mode_str = if * mode == RecursiveMode :: Recursive {
125+ "recursive"
126+ } else {
127+ "non-recursive"
128+ } ;
129+ log:: debug!( " watching ({mode_str}): {}" , path. display( ) ) ;
130+ if let Err ( e) = watcher. watch ( path, * mode) {
131+ log:: error!( "Could not watch {}: {}" , path. display( ) , e) ;
132+ }
133+ }
134+ }
135+
136+ /// Unregisters all watch paths from the given watcher.
137+ fn unregister_watches ( watcher : & mut RecommendedWatcher , watch_paths : & [ ( PathBuf , RecursiveMode ) ] ) {
138+ for ( path, _) in watch_paths {
139+ let _ = watcher. unwatch ( path) ;
140+ }
141+ }
142+
60143struct AsyncWatchArgs < ' a > {
144+ watcher : & ' a mut RecommendedWatcher ,
145+ current_watch_paths : Vec < ( PathBuf , RecursiveMode ) > ,
146+ initial_build_state : BuildCommandState ,
61147 q : Arc < FifoQueue < Result < Event , Error > > > ,
62148 path : & ' a Path ,
63149 show_progress : bool ,
64150 filter : & ' a Option < regex:: Regex > ,
65151 after_build : Option < String > ,
66152 create_sourcedirs : bool ,
67153 plain_output : bool ,
68- warn_error : Option < String > ,
69154}
70155
71156async fn async_watch (
72157 AsyncWatchArgs {
158+ watcher,
159+ mut current_watch_paths,
160+ initial_build_state,
73161 q,
74162 path,
75163 show_progress,
76164 filter,
77165 after_build,
78166 create_sourcedirs,
79167 plain_output,
80- warn_error,
81168 } : AsyncWatchArgs < ' _ > ,
82169) -> Result < ( ) > {
83- let mut build_state: build:: build_types:: BuildCommandState =
84- build:: initialize_build ( None , filter, show_progress, path, plain_output, warn_error)
85- . with_context ( || "Could not initialize build" ) ?;
170+ let mut build_state = initial_build_state;
86171 let mut needs_compile_type = CompileType :: Incremental ;
87172 // create a mutex to capture if ctrl-c was pressed
88173 let ctrlc_pressed = Arc :: new ( Mutex :: new ( false ) ) ;
@@ -128,6 +213,21 @@ async fn async_watch(
128213 return Ok ( ( ) ) ;
129214 }
130215
216+ // Detect rescript.json changes and trigger a full rebuild
217+ if event
218+ . paths
219+ . iter ( )
220+ . any ( |p| p. file_name ( ) . map ( |name| name == "rescript.json" ) . unwrap_or ( false ) )
221+ && matches ! (
222+ event. kind,
223+ EventKind :: Modify ( _) | EventKind :: Create ( _) | EventKind :: Remove ( _)
224+ )
225+ {
226+ log:: debug!( "rescript.json changed -> full compile" ) ;
227+ needs_compile_type = CompileType :: Full ;
228+ continue ;
229+ }
230+
131231 let paths = event
132232 . paths
133233 . iter ( )
@@ -280,6 +380,12 @@ async fn async_watch(
280380 build_state. get_warn_error_override ( ) ,
281381 )
282382 . expect ( "Could not initialize build" ) ;
383+
384+ // Re-register watches based on the new build state
385+ unregister_watches ( watcher, & current_watch_paths) ;
386+ current_watch_paths = compute_watch_paths ( & build_state, path) ;
387+ register_watches ( watcher, & current_watch_paths) ;
388+
283389 let _ = build:: incremental_build (
284390 & mut build_state,
285391 None ,
@@ -334,23 +440,34 @@ pub fn start(
334440 let mut watcher = RecommendedWatcher :: new ( move |res| producer. push ( res) , Config :: default ( ) )
335441 . expect ( "Could not create watcher" ) ;
336442
337- log :: debug! ( "watching { folder}" ) ;
443+ let path = Path :: new ( folder) ;
338444
339- watcher
340- . watch ( Path :: new ( folder) , RecursiveMode :: Recursive )
341- . expect ( "Could not start watcher" ) ;
445+ // Do an initial build to discover packages and source folders
446+ let build_state: BuildCommandState = build:: initialize_build (
447+ None ,
448+ filter,
449+ show_progress,
450+ path,
451+ plain_output,
452+ warn_error. clone ( ) ,
453+ )
454+ . with_context ( || "Could not initialize build" ) ?;
342455
343- let path = Path :: new ( folder) ;
456+ // Compute and register targeted watches based on source folders
457+ let current_watch_paths = compute_watch_paths ( & build_state, path) ;
458+ register_watches ( & mut watcher, & current_watch_paths) ;
344459
345460 async_watch ( AsyncWatchArgs {
461+ watcher : & mut watcher,
462+ current_watch_paths,
463+ initial_build_state : build_state,
346464 q : consumer,
347465 path,
348466 show_progress,
349467 filter,
350468 after_build,
351469 create_sourcedirs,
352470 plain_output,
353- warn_error : warn_error. clone ( ) ,
354471 } )
355472 . await
356473 } )
0 commit comments