diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a8b53189d..a5b01de39d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - Fix `reanalyze` reporting record labels reached through a record coercion as dead. The typed tree now keeps the source type of a coercion, so reading a label on the target counts as reading the source label of the same name. https://github.com/rescript-lang/rescript/issues/8643 - Fix GenType path resolution hanging on Windows when project paths use different short and canonical forms. https://github.com/rescript-lang/rescript/pull/8639 - Allow `rescript clean` to remove generated files when the source module graph is invalid. https://github.com/rescript-lang/rescript/pull/8640 +- Keep watch mode running after full rebuild initialization fails so it can recover after a later valid change. https://github.com/rescript-lang/rescript/pull/8641 - Fix constant folding of pattern matches on unboxed variants whose payload overlaps a literal constructor, so inlined calls agree with runtime matching. Reject multi-argument unboxed constructors instead of crashing. https://github.com/rescript-lang/rescript/pull/8631 - Fix escaped backticks and interpolation openers in backquoted `%raw`, `%ffi`, and `%re` payloads leaking into emitted JavaScript. https://github.com/rescript-lang/rescript/pull/8630 - Fix the side-effect analysis treating bigint exponentiation and bounds-checked array and string reads as pure, which let dead-code elimination drop an unused one that throws: `let _ = 2n ** -1n` no longer raised. https://github.com/rescript-lang/rescript/pull/8617 diff --git a/rewatch/src/watcher.rs b/rewatch/src/watcher.rs index e435c75785..9bb738a77a 100644 --- a/rewatch/src/watcher.rs +++ b/rewatch/src/watcher.rs @@ -486,7 +486,7 @@ async fn async_watch( let timing_total = Instant::now(); // Reinitialization runs cleanup for previous build artifacts, so full rebuilds need // the same build lock boundary as regular `rescript build`. - let result = build::with_build_lock(path, || { + let result = build::with_build_lock(path, || -> Result<_> { let mut next_build_state = build::initialize_build( None, filter, @@ -498,7 +498,7 @@ async fn async_watch( features.clone(), SourceMapCommand::Watch, ) - .expect("Could not initialize build"); + .context("Could not initialize build")?; // Full rebuilds can be triggered by editor atomic saves that surface as rename events. // Preserve warning state for unchanged modules so their warnings are re-emitted after the @@ -521,10 +521,10 @@ async fn async_watch( plain_output, ); build::write_build_ninja(&build_state); - result + Ok(result) }); match result { - Ok(result) => { + Ok(Ok(result)) => { finish_successful_watch_compile( after_build.clone(), timing_total, @@ -535,7 +535,13 @@ async fn async_watch( result, ); } - Err(_) => { + Ok(Err(_)) => { + if should_clear_screen(clear_screen, show_progress, plain_output) { + print_build_failed_footer(); + } + } + Err(error) => { + eprintln!("{error:#}"); if should_clear_screen(clear_screen, show_progress, plain_output) { print_build_failed_footer(); } diff --git a/rewatch/tests/suite.sh b/rewatch/tests/suite.sh index a38d72ee57..135c18372a 100755 --- a/rewatch/tests/suite.sh +++ b/rewatch/tests/suite.sh @@ -147,6 +147,7 @@ fi ./watch/04-watch-config-change.sh && ./watch/05-watch-ignores-non-source.sh && ./watch/06-watch-missing-source-folder.sh && +./watch/07-watch-recovers-from-invalid-config.sh && # Lock tests ./lock/01-lock-when-watching.sh && diff --git a/rewatch/tests/watch/07-watch-recovers-from-invalid-config.sh b/rewatch/tests/watch/07-watch-recovers-from-invalid-config.sh new file mode 100755 index 0000000000..775e54e0be --- /dev/null +++ b/rewatch/tests/watch/07-watch-recovers-from-invalid-config.sh @@ -0,0 +1,103 @@ +#!/bin/bash +cd $(dirname $0) +source "../utils.sh" +cd ../../testrepo + +bold "Test: Watcher recovers from an invalid config change" + +wait_for_pattern() { + local file="$1" pattern="$2" timeout="${3:-30}" + while [ "$timeout" -gt 0 ]; do + grep -q "$pattern" "$file" 2> /dev/null && return 0 + sleep 1 + timeout=$((timeout - 1)) + done + return 1 +} + +wait_for_pattern_count() { + local file="$1" pattern="$2" expected="$3" timeout="${4:-30}" + while [ "$timeout" -gt 0 ]; do + local count + count=$(grep -c "$pattern" "$file" 2> /dev/null || true) + [ "$count" -ge "$expected" ] && return 0 + sleep 1 + timeout=$((timeout - 1)) + done + return 1 +} + +rewatch clean > /dev/null 2>&1 +cp rescript.json rescript.json.watch-recovery.bak +rewatch_bg watch > rewatch.log 2>&1 & + +if ! wait_for_file "./src/Test.mjs" 20 || ! wait_for_file "lib/watch.lock" 20; then + error "Initial watch build did not complete" + cat rewatch.log + exit_watcher + mv rescript.json.watch-recovery.bak rescript.json + exit 1 +fi + +if ! wait_for_pattern rewatch.log "Finished initial compilation" 60; then + error "Initial watch build did not finish" + cat rewatch.log + exit_watcher + mv rescript.json.watch-recovery.bak rescript.json + exit 1 +fi + +initial_completed=$(grep -c "Finished compilation" rewatch.log 2> /dev/null || true) +printf '{"name":' > rescript.json.watch-recovery.next +mv rescript.json.watch-recovery.next rescript.json + +if ! wait_for_pattern rewatch.log "Could not initialize build" 30; then + error "Watcher did not report the invalid config" + cat rewatch.log + exit_watcher + mv rescript.json.watch-recovery.bak rescript.json + exit 1 +fi + +node -e ' + const fs = require("fs"); + const config = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); + config.suffix = ".recovered.mjs"; + fs.writeFileSync(process.argv[2], `${JSON.stringify(config, null, 2)}\n`); +' rescript.json.watch-recovery.bak rescript.json.watch-recovery.next +mv rescript.json.watch-recovery.next rescript.json + +expected_completed=$((initial_completed + 1)) +if ! wait_for_pattern_count rewatch.log "Finished compilation" "$expected_completed" 30; then + error "Watcher did not recover after restoring a valid config" + cat rewatch.log + exit_watcher + mv rescript.json.watch-recovery.bak rescript.json + exit 1 +fi + +echo '// watch-recovery-test' >> src/Test.res +if ! wait_for_file "./src/Test.recovered.mjs" 20; then + error "Recovered watcher did not compile with the restored config" + cat rewatch.log + exit_watcher + mv rescript.json.watch-recovery.bak rescript.json + git checkout -- src/Test.res + exit 1 +fi + +exit_watcher +rewatch clean > /dev/null 2>&1 +mv rescript.json.watch-recovery.bak rescript.json +git checkout -- src/Test.res +rm -f rewatch.log +rewatch build > /dev/null 2>&1 + +if git diff --exit-code . > /dev/null 2>&1 && [ -z "$(git ls-files --others --exclude-standard .)" ]; then + success "Watcher recovered from invalid config" +else + error "Watcher recovery test left repository changes" + git diff . + git ls-files --others --exclude-standard . + exit 1 +fi