From 61a4b50c01ad148212eab9fcb3fee7bf506273ea Mon Sep 17 00:00:00 2001
From: Paul Buschmann 
Date: Sun, 15 Jun 2025 20:14:25 +0200
Subject: [PATCH 01/22] chore: force LF on windows
---
 .gitattributes | 2 ++
 1 file changed, 2 insertions(+)
diff --git a/.gitattributes b/.gitattributes
index 873ddfb587..666eddc88d 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,5 @@
+* text=auto eol=lf
+
 *.ml linguist-language=OCaml
 *.mli linguist-language=OCaml
 *.res linguist-language=ReScript
From 475f49dbe31177e99d9fedd433f7b44a56261a6e Mon Sep 17 00:00:00 2001
From: Paul Buschmann 
Date: Sun, 15 Jun 2025 21:14:43 +0200
Subject: [PATCH 02/22] feat: upstream changes of rescript-lang/rewatch#162
---
 rewatch/src/build.rs       |  17 ++++
 rewatch/src/build/clean.rs |   6 +-
 rewatch/src/cli.rs         | 178 +++++++++++++++++++++++++++++++++++
 rewatch/src/helpers.rs     |  35 +++++++
 rewatch/src/lib.rs         |   1 +
 rewatch/src/main.rs        | 187 ++++++++++++++-----------------------
 rewatch/tests/compile.sh   |  16 ++--
 rewatch/tests/utils.sh     |   4 +-
 8 files changed, 315 insertions(+), 129 deletions(-)
 create mode 100644 rewatch/src/cli.rs
diff --git a/rewatch/src/build.rs b/rewatch/src/build.rs
index c76120064e..ab00db8aad 100644
--- a/rewatch/src/build.rs
+++ b/rewatch/src/build.rs
@@ -18,10 +18,12 @@ use console::style;
 use indicatif::{ProgressBar, ProgressStyle};
 use log::log_enabled;
 use serde::Serialize;
+use std::ffi::OsString;
 use std::fmt;
 use std::fs::File;
 use std::io::{stdout, Write};
 use std::path::{Path, PathBuf};
+use std::process::Stdio;
 use std::time::{Duration, Instant};
 
 use self::compile::compiler_args;
@@ -551,3 +553,18 @@ pub fn build(
         }
     }
 }
+
+pub fn pass_through_legacy(args: Vec) -> i32 {
+    let project_root = helpers::get_abs_path(Path::new("."));
+    let workspace_root = helpers::get_workspace_root(&project_root);
+
+    let bsb_path = helpers::get_rescript_legacy(&project_root, workspace_root);
+
+    let status = std::process::Command::new(bsb_path)
+        .args(args)
+        .stdout(Stdio::inherit())
+        .stderr(Stdio::inherit())
+        .status();
+
+    status.map(|s| s.code().unwrap_or(1)).unwrap_or(1)
+}
diff --git a/rewatch/src/build/clean.rs b/rewatch/src/build/clean.rs
index 5624e6ac33..0057437997 100644
--- a/rewatch/src/build/clean.rs
+++ b/rewatch/src/build/clean.rs
@@ -335,7 +335,6 @@ pub fn clean(
     path: &Path,
     show_progress: bool,
     bsc_path: &Option,
-    build_dev_deps: bool,
     snapshot_output: bool,
 ) -> Result<()> {
     let project_root = helpers::get_abs_path(path);
@@ -345,8 +344,9 @@ pub fn clean(
         &project_root,
         &workspace_root,
         show_progress,
-        // Always clean dev dependencies
-        build_dev_deps,
+        // Build the package tree with dev dependencies.
+        // They should always be cleaned if they are there.
+        true,
     )?;
     let root_config_name = packages::read_package_name(&project_root)?;
     let bsc_path = match bsc_path {
diff --git a/rewatch/src/cli.rs b/rewatch/src/cli.rs
new file mode 100644
index 0000000000..87ec1efb09
--- /dev/null
+++ b/rewatch/src/cli.rs
@@ -0,0 +1,178 @@
+use std::ffi::OsString;
+
+use clap::{Args, Parser, Subcommand};
+use clap_verbosity_flag::InfoLevel;
+
+/// Rewatch is an alternative build system for the Rescript Compiler bsb (which uses Ninja internally). It strives
+/// to deliver consistent and faster builds in monorepo setups with multiple packages, where the
+/// default build system fails to pick up changed interfaces across multiple packages.
+#[derive(Parser, Debug)]
+#[command(version)]
+#[command(args_conflicts_with_subcommands = true)]
+pub struct Cli {
+    /// Verbosity:
+    /// -v -> Debug
+    /// -vv -> Trace
+    /// -q -> Warn
+    /// -qq -> Error
+    /// -qqq -> Off.
+    /// Default (/ no argument given): 'info'
+    #[command(flatten)]
+    pub verbose: clap_verbosity_flag::Verbosity,
+
+    /// The command to run. If not provided it will default to build.
+    #[command(subcommand)]
+    pub command: Option,
+
+    /// The relative path to where the main rescript.json resides. IE - the root of your project.
+    #[arg(default_value = ".")]
+    pub folder: String,
+
+    #[command(flatten)]
+    pub build_args: BuildArgs,
+}
+
+#[derive(Args, Debug, Clone)]
+pub struct BuildArgs {
+    /// Filter files by regex
+    ///
+    /// Filter allows for a regex to be supplied which will filter the files to be compiled. For
+    /// instance, to filter out test files for compilation while doing feature work.
+    #[arg(short, long)]
+    pub filter: Option,
+
+    /// Action after build
+    ///
+    /// This allows one to pass an additional command to the watcher, which allows it to run when
+    /// finished. For instance, to play a sound when done compiling, or to run a test suite.
+    /// NOTE - You may need to add '--color=always' to your subcommand in case you want to output
+    /// color as well
+    #[arg(short, long)]
+    pub after_build: Option,
+
+    /// Create source_dirs.json
+    ///
+    /// This creates a source_dirs.json file at the root of the monorepo, which is needed when you
+    /// want to use Reanalyze
+    #[arg(short, long, default_value_t = false, num_args = 0..=1)]
+    pub create_sourcedirs: bool,
+
+    /// Build development dependencies
+    ///
+    /// This is the flag to also compile development dependencies
+    /// It's important to know that we currently do not discern between project src, and
+    /// dependencies. So enabling this flag will enable building _all_ development dependencies of
+    /// _all_ packages
+    #[arg(long, default_value_t = false, num_args = 0..=1)]
+    pub dev: bool,
+
+    /// Disable timing on the output
+    #[arg(short, long, default_value_t = false, num_args = 0..=1)]
+    pub no_timing: bool,
+
+    /// simple output for snapshot testing
+    #[arg(short, long, default_value = "false", num_args = 0..=1)]
+    pub snapshot_output: bool,
+
+    /// Path to bsc
+    #[arg(long)]
+    pub bsc_path: Option,
+}
+
+#[derive(Args, Clone, Debug)]
+pub struct WatchArgs {
+    /// Filter files by regex
+    ///
+    /// Filter allows for a regex to be supplied which will filter the files to be compiled. For
+    /// instance, to filter out test files for compilation while doing feature work.
+    #[arg(short, long)]
+    pub filter: Option,
+
+    /// Action after build
+    ///
+    /// This allows one to pass an additional command to the watcher, which allows it to run when
+    /// finished. For instance, to play a sound when done compiling, or to run a test suite.
+    /// NOTE - You may need to add '--color=always' to your subcommand in case you want to output
+    /// color as well
+    #[arg(short, long)]
+    pub after_build: Option,
+
+    /// Create source_dirs.json
+    ///
+    /// This creates a source_dirs.json file at the root of the monorepo, which is needed when you
+    /// want to use Reanalyze
+    #[arg(short, long, default_value_t = false, num_args = 0..=1)]
+    pub create_sourcedirs: bool,
+
+    /// Build development dependencies
+    ///
+    /// This is the flag to also compile development dependencies
+    /// It's important to know that we currently do not discern between project src, and
+    /// dependencies. So enabling this flag will enable building _all_ development dependencies of
+    /// _all_ packages
+    #[arg(long, default_value_t = false, num_args = 0..=1)]
+    pub dev: bool,
+
+    /// simple output for snapshot testing
+    #[arg(short, long, default_value = "false", num_args = 0..=1)]
+    pub snapshot_output: bool,
+
+    /// Path to bsc
+    #[arg(long)]
+    pub bsc_path: Option,
+}
+
+#[derive(Subcommand, Clone, Debug)]
+pub enum Command {
+    /// Build using Rewatch
+    Build(BuildArgs),
+    /// Build, then start a watcher
+    Watch(WatchArgs),
+    /// Clean the build artifacts
+    Clean {
+        /// Path to bsc
+        #[arg(long)]
+        bsc_path: Option,
+
+        /// simple output for snapshot testing
+        #[arg(short, long, default_value = "false", num_args = 0..=1)]
+        snapshot_output: bool,
+    },
+    /// Alias to `legacy format`.
+    #[command(disable_help_flag = true)]
+    Format {
+        #[arg(allow_hyphen_values = true, num_args = 0..)]
+        format_args: Vec,
+    },
+    /// Alias to `legacy dump`.
+    #[command(disable_help_flag = true)]
+    Dump {
+        #[arg(allow_hyphen_values = true, num_args = 0..)]
+        dump_args: Vec,
+    },
+    /// This prints the compiler arguments. It expects the path to a rescript.json file.
+    CompilerArgs {
+        /// Path to a rescript.json file
+        #[command()]
+        path: String,
+
+        #[arg(long, default_value_t = false, num_args = 0..=1)]
+        dev: bool,
+
+        /// To be used in conjunction with compiler_args
+        #[arg(long)]
+        rescript_version: Option,
+
+        /// A custom path to bsc
+        #[arg(long)]
+        bsc_path: Option,
+    },
+    /// Use the legacy build system.
+    ///
+    /// After this command is encountered, the rest of the arguments are passed to the legacy build system.
+    #[command(disable_help_flag = true)]
+    Legacy {
+        #[arg(allow_hyphen_values = true, num_args = 0..)]
+        legacy_args: Vec,
+    },
+}
diff --git a/rewatch/src/helpers.rs b/rewatch/src/helpers.rs
index a91dee77f5..b05dc9b2ad 100644
--- a/rewatch/src/helpers.rs
+++ b/rewatch/src/helpers.rs
@@ -218,6 +218,41 @@ pub fn get_bsc(root_path: &Path, workspace_root: &Option) -> PathBuf {
     }
 }
 
+pub fn get_rescript_legacy(root_path: &Path, workspace_root: Option) -> PathBuf {
+    let subfolder = match (std::env::consts::OS, std::env::consts::ARCH) {
+        ("macos", "aarch64") => "darwin-arm64",
+        ("macos", _) => "darwin-x64",
+        ("linux", "aarch64") => "linux-arm64",
+        ("linux", _) => "linux-x64",
+        ("windows", "aarch64") => "win-arm64",
+        ("windows", _) => "win-x64",
+        _ => panic!("Unsupported architecture"),
+    };
+
+    let legacy_path_fragment = Path::new("node_modules")
+        .join("@rescript")
+        .join(subfolder)
+        .join("bin")
+        .join("rescript-legacy");
+
+    match (
+        root_path
+            .join(&legacy_path_fragment)
+            .canonicalize()
+            .map(StrippedVerbatimPath::to_stripped_verbatim_path),
+        workspace_root.map(|workspace_root| {
+            workspace_root
+                .join(&legacy_path_fragment)
+                .canonicalize()
+                .map(StrippedVerbatimPath::to_stripped_verbatim_path)
+        }),
+    ) {
+        (Ok(path), _) => path,
+        (_, Some(Ok(path))) => path,
+        _ => panic!("Could not find rescript-legacy"),
+    }
+}
+
 pub fn string_ends_with_any(s: &Path, suffixes: &[&str]) -> bool {
     suffixes
         .iter()
diff --git a/rewatch/src/lib.rs b/rewatch/src/lib.rs
index 9dc6f5591c..2df92a48f3 100644
--- a/rewatch/src/lib.rs
+++ b/rewatch/src/lib.rs
@@ -1,4 +1,5 @@
 pub mod build;
+pub mod cli;
 pub mod cmd;
 pub mod config;
 pub mod helpers;
diff --git a/rewatch/src/main.rs b/rewatch/src/main.rs
index 5c27a4d02b..dcebec5945 100644
--- a/rewatch/src/main.rs
+++ b/rewatch/src/main.rs
@@ -1,93 +1,17 @@
 use anyhow::Result;
-use clap::{Parser, ValueEnum};
-use clap_verbosity_flag::InfoLevel;
+use clap::Parser;
 use log::LevelFilter;
 use regex::Regex;
-use std::io::Write;
-use std::path::{Path, PathBuf};
+use std::{
+    io::Write,
+    path::{Path, PathBuf},
+};
 
-use rewatch::{build, cmd, lock, watcher};
-
-#[derive(Debug, Clone, ValueEnum)]
-enum Command {
-    /// Build using Rewatch
-    Build,
-    /// Build, then start a watcher
-    Watch,
-    /// Clean the build artifacts
-    Clean,
-}
-
-/// Rewatch is an alternative build system for the Rescript Compiler bsb (which uses Ninja internally). It strives
-/// to deliver consistent and faster builds in monorepo setups with multiple packages, where the
-/// default build system fails to pick up changed interfaces across multiple packages.
-#[derive(Parser, Debug)]
-#[command(version)]
-struct Args {
-    #[arg(value_enum)]
-    command: Option,
-
-    /// The relative path to where the main rescript.json resides. IE - the root of your project.
-    folder: Option,
-
-    /// Filter allows for a regex to be supplied which will filter the files to be compiled. For
-    /// instance, to filter out test files for compilation while doing feature work.
-    #[arg(short, long)]
-    filter: Option,
-
-    /// This allows one to pass an additional command to the watcher, which allows it to run when
-    /// finished. For instance, to play a sound when done compiling, or to run a test suite.
-    /// NOTE - You may need to add '--color=always' to your subcommand in case you want to output
-    /// colour as well
-    #[arg(short, long)]
-    after_build: Option,
-
-    // Disable timing on the output
-    #[arg(short, long, default_value = "false", num_args = 0..=1)]
-    no_timing: bool,
-
-    // simple output for snapshot testing
-    #[arg(short, long, default_value = "false", num_args = 0..=1)]
-    snapshot_output: bool,
-
-    /// Verbosity:
-    /// -v -> Debug
-    /// -vv -> Trace
-    /// -q -> Warn
-    /// -qq -> Error
-    /// -qqq -> Off.
-    /// Default (/ no argument given): 'info'
-    #[command(flatten)]
-    verbose: clap_verbosity_flag::Verbosity,
-
-    /// This creates a source_dirs.json file at the root of the monorepo, which is needed when you
-    /// want to use Reanalyze
-    #[arg(short, long, default_value_t = false, num_args = 0..=1)]
-    create_sourcedirs: bool,
-
-    /// This prints the compiler arguments. It expects the path to a rescript.json file.
-    /// This also requires --bsc-path and --rescript-version to be present
-    #[arg(long)]
-    compiler_args: Option,
-
-    /// This is the flag to also compile development dependencies
-    /// It's important to know that we currently do not discern between project src, and
-    /// dependencies. So enabling this flag will enable building _all_ development dependencies of
-    /// _all_ packages
-    #[arg(long, default_value_t = false, num_args = 0..=1)]
-    dev: bool,
-
-    /// To be used in conjunction with compiler_args
-    #[arg(long)]
-    rescript_version: Option,
-
-    /// A custom path to bsc
-    #[arg(long)]
-    bsc_path: Option,
-}
+use rewatch::{build, cli, cmd, lock, watcher};
 
 fn main() -> Result<()> {
-    let args = Args::parse();
+    let args = cli::Cli::parse();
+
     let log_level_filter = args.verbose.log_level_filter();
 
     env_logger::Builder::new()
@@ -96,82 +20,113 @@ fn main() -> Result<()> {
         .target(env_logger::fmt::Target::Stdout)
         .init();
 
-    let command = args.command.unwrap_or(Command::Build);
-    let folder = args.folder.unwrap_or(".".to_string());
-    let filter = args
-        .filter
-        .map(|filter| Regex::new(filter.as_ref()).expect("Could not parse regex"));
+    let command = args.command.unwrap_or(cli::Command::Build(args.build_args));
 
-    match args.compiler_args {
-        None => (),
-        Some(path) => {
+    // handle those commands early, because we don't need a lock for them
+    match command.clone() {
+        cli::Command::Legacy { legacy_args } => {
+            let code = build::pass_through_legacy(legacy_args);
+            std::process::exit(code);
+        }
+        cli::Command::Format { mut format_args } => {
+            format_args.insert(0, "format".into());
+            let code = build::pass_through_legacy(format_args);
+            std::process::exit(code);
+        }
+        cli::Command::Dump { mut dump_args } => {
+            dump_args.insert(0, "dump".into());
+            let code = build::pass_through_legacy(dump_args);
+            std::process::exit(code);
+        }
+        cli::Command::CompilerArgs {
+            path,
+            dev,
+            rescript_version,
+            bsc_path,
+        } => {
             println!(
                 "{}",
                 build::get_compiler_args(
                     Path::new(&path),
-                    args.rescript_version,
-                    &args.bsc_path.map(PathBuf::from),
-                    args.dev
+                    rescript_version,
+                    &bsc_path.map(PathBuf::from),
+                    dev
                 )?
             );
             std::process::exit(0);
         }
+        _ => (),
     }
 
     // The 'normal run' mode will show the 'pretty' formatted progress. But if we turn off the log
     // level, we should never show that.
     let show_progress = log_level_filter == LevelFilter::Info;
 
-    match lock::get(&folder) {
+    match lock::get(&args.folder) {
         lock::Lock::Error(ref e) => {
             println!("Could not start Rewatch: {e}");
             std::process::exit(1)
         }
         lock::Lock::Aquired(_) => match command {
-            Command::Clean => build::clean::clean(
-                Path::new(&folder),
+            cli::Command::Clean {
+                bsc_path,
+                snapshot_output,
+            } => build::clean::clean(
+                Path::new(&args.folder),
                 show_progress,
-                &args.bsc_path.map(PathBuf::from),
-                args.dev,
-                args.snapshot_output,
+                &bsc_path.map(PathBuf::from),
+                snapshot_output,
             ),
-            Command::Build => {
+            cli::Command::Build(build_args) => {
+                let filter = build_args
+                    .filter
+                    .map(|filter| Regex::new(filter.as_ref()).expect("Could not parse regex"));
+
                 match build::build(
                     &filter,
-                    Path::new(&folder),
+                    Path::new(&args.folder),
                     show_progress,
-                    args.no_timing,
-                    args.create_sourcedirs,
-                    &args.bsc_path.map(PathBuf::from),
-                    args.dev,
-                    args.snapshot_output,
+                    build_args.no_timing,
+                    build_args.create_sourcedirs,
+                    &build_args.bsc_path.map(PathBuf::from),
+                    build_args.dev,
+                    build_args.snapshot_output,
                 ) {
                     Err(e) => {
                         println!("{e}");
                         std::process::exit(1)
                     }
                     Ok(_) => {
-                        if let Some(args_after_build) = args.after_build {
+                        if let Some(args_after_build) = build_args.after_build {
                             cmd::run(args_after_build)
                         }
                         std::process::exit(0)
                     }
                 };
             }
-            Command::Watch => {
+            cli::Command::Watch(watch_args) => {
+                let filter = watch_args
+                    .filter
+                    .map(|filter| Regex::new(filter.as_ref()).expect("Could not parse regex"));
                 watcher::start(
                     &filter,
                     show_progress,
-                    &folder,
-                    args.after_build,
-                    args.create_sourcedirs,
-                    args.dev,
-                    args.bsc_path,
-                    args.snapshot_output,
+                    &args.folder,
+                    watch_args.after_build,
+                    watch_args.create_sourcedirs,
+                    watch_args.dev,
+                    watch_args.bsc_path,
+                    watch_args.snapshot_output,
                 );
 
                 Ok(())
             }
+            cli::Command::CompilerArgs { .. }
+            | cli::Command::Legacy { .. }
+            | cli::Command::Format { .. }
+            | cli::Command::Dump { .. } => {
+                unreachable!("command already handled")
+            }
         },
     }
 }
diff --git a/rewatch/tests/compile.sh b/rewatch/tests/compile.sh
index fd035bf902..9fc6ebb4bc 100755
--- a/rewatch/tests/compile.sh
+++ b/rewatch/tests/compile.sh
@@ -33,32 +33,32 @@ fi
 node ./packages/main/src/Main.mjs > ./packages/main/src/output.txt
 
 mv ./packages/main/src/Main.res ./packages/main/src/Main2.res
-rewatch build &> ../tests/snapshots/rename-file.txt
+rewatch build --no-timing --snapshot-output &> ../tests/snapshots/rename-file.txt
 mv ./packages/main/src/Main2.res ./packages/main/src/Main.res
 
 # Rename a file with a dependent - this should trigger an error
 mv ./packages/main/src/InternalDep.res ./packages/main/src/InternalDep2.res
-rewatch build &> ../tests/snapshots/rename-file-internal-dep.txt
+rewatch build --no-timing --snapshot-output &> ../tests/snapshots/rename-file-internal-dep.txt
 # normalize paths so the snapshot is the same on all machines
 normalize_paths ../tests/snapshots/rename-file-internal-dep.txt
 mv ./packages/main/src/InternalDep2.res ./packages/main/src/InternalDep.res
 
 # Rename a file with a dependent in a namespaced package - this should trigger an error (regression)
 mv ./packages/new-namespace/src/Other_module.res ./packages/new-namespace/src/Other_module2.res
-rewatch build &> ../tests/snapshots/rename-file-internal-dep-namespace.txt
+rewatch build --no-timing --snapshot-output &> ../tests/snapshots/rename-file-internal-dep-namespace.txt
 # normalize paths so the snapshot is the same on all machines
 normalize_paths ../tests/snapshots/rename-file-internal-dep-namespace.txt
 mv ./packages/new-namespace/src/Other_module2.res ./packages/new-namespace/src/Other_module.res
 
 rewatch build &>  /dev/null
 mv ./packages/main/src/ModuleWithInterface.resi ./packages/main/src/ModuleWithInterface2.resi
-rewatch build &> ../tests/snapshots/rename-interface-file.txt
+rewatch build --no-timing --snapshot-output &> ../tests/snapshots/rename-interface-file.txt
 # normalize paths so the snapshot is the same on all machines
 normalize_paths ../tests/snapshots/rename-interface-file.txt
 mv ./packages/main/src/ModuleWithInterface2.resi ./packages/main/src/ModuleWithInterface.resi
 rewatch build &> /dev/null
 mv ./packages/main/src/ModuleWithInterface.res ./packages/main/src/ModuleWithInterface2.res
-rewatch build &> ../tests/snapshots/rename-file-with-interface.txt
+rewatch build --no-timing --snapshot-output &> ../tests/snapshots/rename-file-with-interface.txt
 # normalize paths so the snapshot is the same on all machines
 normalize_paths ../tests/snapshots/rename-file-with-interface.txt
 mv ./packages/main/src/ModuleWithInterface2.res ./packages/main/src/ModuleWithInterface.res
@@ -66,7 +66,7 @@ rewatch build &> /dev/null
 
 # when deleting a file that other files depend on, the compile should fail
 rm packages/dep02/src/Dep02.res
-rewatch build &> ../tests/snapshots/remove-file.txt
+rewatch build --no-timing --snapshot-output &> ../tests/snapshots/remove-file.txt
 # normalize paths so the snapshot is the same on all machines
 normalize_paths ../tests/snapshots/remove-file.txt
 git checkout -- packages/dep02/src/Dep02.res
@@ -74,7 +74,7 @@ rewatch build &> /dev/null
 
 # it should show an error when we have a dependency cycle
 echo 'Dep01.log()' >> packages/new-namespace/src/NS_alias.res
-rewatch build &> ../tests/snapshots/dependency-cycle.txt
+rewatch build --no-timing --snapshot-output &> ../tests/snapshots/dependency-cycle.txt
 git checkout -- packages/new-namespace/src/NS_alias.res
 
 # it should compile dev dependencies with the --dev flag
@@ -95,7 +95,7 @@ else
   exit 1
 fi
 
-rewatch clean --dev &> /dev/null
+rewatch clean &> /dev/null
 file_count=$(find ./packages/with-dev-deps -name *.mjs | wc -l)
 if [ "$file_count" -eq 0 ];
 then
diff --git a/rewatch/tests/utils.sh b/rewatch/tests/utils.sh
index 1dffbe8a8d..bef51f9fce 100644
--- a/rewatch/tests/utils.sh
+++ b/rewatch/tests/utils.sh
@@ -3,8 +3,8 @@ overwrite() { echo -e "\r\033[1A\033[0K$@"; }
 success() { echo -e "- ✅ \033[32m$1\033[0m"; }
 error() { echo -e "- 🛑 \033[31m$1\033[0m"; }
 bold() { echo -e "\033[1m$1\033[0m"; }
-rewatch() { RUST_BACKTRACE=1 $REWATCH_EXECUTABLE --no-timing=true --snapshot-output=true $@; }
-rewatch_bg() { RUST_BACKTRACE=1 nohup $REWATCH_EXECUTABLE --no-timing=true --snapshot-output=true $@; }
+rewatch() { RUST_BACKTRACE=1 $REWATCH_EXECUTABLE $@; }
+rewatch_bg() { RUST_BACKTRACE=1 nohup $REWATCH_EXECUTABLE $@; }
 
 # Detect if running on Windows
 is_windows() {
From 110ba77a28fa0a61677d4f2de5017af917705896 Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Mon, 16 Jun 2025 21:47:10 +0200
Subject: [PATCH 03/22] test: update cli arg ordering
---
 rewatch/tests/compile.sh  | 14 +++++++-------
 rewatch/tests/suite-ci.sh |  5 ++++-
 rewatch/tests/utils.sh    |  4 ++--
 3 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/rewatch/tests/compile.sh b/rewatch/tests/compile.sh
index 9fc6ebb4bc..f694b3b0bb 100755
--- a/rewatch/tests/compile.sh
+++ b/rewatch/tests/compile.sh
@@ -33,32 +33,32 @@ fi
 node ./packages/main/src/Main.mjs > ./packages/main/src/output.txt
 
 mv ./packages/main/src/Main.res ./packages/main/src/Main2.res
-rewatch build --no-timing --snapshot-output &> ../tests/snapshots/rename-file.txt
+rewatch build --snapshot-output &> ../tests/snapshots/rename-file.txt
 mv ./packages/main/src/Main2.res ./packages/main/src/Main.res
 
 # Rename a file with a dependent - this should trigger an error
 mv ./packages/main/src/InternalDep.res ./packages/main/src/InternalDep2.res
-rewatch build --no-timing --snapshot-output &> ../tests/snapshots/rename-file-internal-dep.txt
+rewatch build --snapshot-output &> ../tests/snapshots/rename-file-internal-dep.txt
 # normalize paths so the snapshot is the same on all machines
 normalize_paths ../tests/snapshots/rename-file-internal-dep.txt
 mv ./packages/main/src/InternalDep2.res ./packages/main/src/InternalDep.res
 
 # Rename a file with a dependent in a namespaced package - this should trigger an error (regression)
 mv ./packages/new-namespace/src/Other_module.res ./packages/new-namespace/src/Other_module2.res
-rewatch build --no-timing --snapshot-output &> ../tests/snapshots/rename-file-internal-dep-namespace.txt
+rewatch build --snapshot-output &> ../tests/snapshots/rename-file-internal-dep-namespace.txt
 # normalize paths so the snapshot is the same on all machines
 normalize_paths ../tests/snapshots/rename-file-internal-dep-namespace.txt
 mv ./packages/new-namespace/src/Other_module2.res ./packages/new-namespace/src/Other_module.res
 
 rewatch build &>  /dev/null
 mv ./packages/main/src/ModuleWithInterface.resi ./packages/main/src/ModuleWithInterface2.resi
-rewatch build --no-timing --snapshot-output &> ../tests/snapshots/rename-interface-file.txt
+rewatch build --snapshot-output &> ../tests/snapshots/rename-interface-file.txt
 # normalize paths so the snapshot is the same on all machines
 normalize_paths ../tests/snapshots/rename-interface-file.txt
 mv ./packages/main/src/ModuleWithInterface2.resi ./packages/main/src/ModuleWithInterface.resi
 rewatch build &> /dev/null
 mv ./packages/main/src/ModuleWithInterface.res ./packages/main/src/ModuleWithInterface2.res
-rewatch build --no-timing --snapshot-output &> ../tests/snapshots/rename-file-with-interface.txt
+rewatch build --snapshot-output &> ../tests/snapshots/rename-file-with-interface.txt
 # normalize paths so the snapshot is the same on all machines
 normalize_paths ../tests/snapshots/rename-file-with-interface.txt
 mv ./packages/main/src/ModuleWithInterface2.res ./packages/main/src/ModuleWithInterface.res
@@ -66,7 +66,7 @@ rewatch build &> /dev/null
 
 # when deleting a file that other files depend on, the compile should fail
 rm packages/dep02/src/Dep02.res
-rewatch build --no-timing --snapshot-output &> ../tests/snapshots/remove-file.txt
+rewatch build --snapshot-output &> ../tests/snapshots/remove-file.txt
 # normalize paths so the snapshot is the same on all machines
 normalize_paths ../tests/snapshots/remove-file.txt
 git checkout -- packages/dep02/src/Dep02.res
@@ -74,7 +74,7 @@ rewatch build &> /dev/null
 
 # it should show an error when we have a dependency cycle
 echo 'Dep01.log()' >> packages/new-namespace/src/NS_alias.res
-rewatch build --no-timing --snapshot-output &> ../tests/snapshots/dependency-cycle.txt
+rewatch build --snapshot-output &> ../tests/snapshots/dependency-cycle.txt
 git checkout -- packages/new-namespace/src/NS_alias.res
 
 # it should compile dev dependencies with the --dev flag
diff --git a/rewatch/tests/suite-ci.sh b/rewatch/tests/suite-ci.sh
index d36319b988..9e5eefe1a8 100755
--- a/rewatch/tests/suite-ci.sh
+++ b/rewatch/tests/suite-ci.sh
@@ -8,10 +8,13 @@ cd $(dirname $0)
 # Get rewatch executable location from the first argument or use default
 if [ -n "$1" ]; then
   REWATCH_EXECUTABLE="$1"
+  BSC_PATH=""
 else
-  REWATCH_EXECUTABLE="../target/release/rewatch --bsc-path ../../_build/install/default/bin/bsc"
+  REWATCH_EXECUTABLE="../target/release/rewatch"
+  BSC_PATH="--bsc-path ../../_build/install/default/bin/bsc"
 fi
 export REWATCH_EXECUTABLE
+export BSC_PATH
 
 source ./utils.sh
 
diff --git a/rewatch/tests/utils.sh b/rewatch/tests/utils.sh
index bef51f9fce..44a22ba0e3 100644
--- a/rewatch/tests/utils.sh
+++ b/rewatch/tests/utils.sh
@@ -3,8 +3,8 @@ overwrite() { echo -e "\r\033[1A\033[0K$@"; }
 success() { echo -e "- ✅ \033[32m$1\033[0m"; }
 error() { echo -e "- 🛑 \033[31m$1\033[0m"; }
 bold() { echo -e "\033[1m$1\033[0m"; }
-rewatch() { RUST_BACKTRACE=1 $REWATCH_EXECUTABLE $@; }
-rewatch_bg() { RUST_BACKTRACE=1 nohup $REWATCH_EXECUTABLE $@; }
+rewatch() { RUST_BACKTRACE=1 $REWATCH_EXECUTABLE $@ $BSC_PATH; }
+rewatch_bg() { RUST_BACKTRACE=1 nohup $REWATCH_EXECUTABLE $@ $BSC_PATH; }
 
 # Detect if running on Windows
 is_windows() {
From 9859117877e9ac4418db33ee9cbd28ab3272e139 Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Wed, 18 Jun 2025 20:39:14 +0200
Subject: [PATCH 04/22] fix: look for rescript.exe not rescript-legacy
---
 rewatch/src/helpers.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rewatch/src/helpers.rs b/rewatch/src/helpers.rs
index b05dc9b2ad..358c4c6ef1 100644
--- a/rewatch/src/helpers.rs
+++ b/rewatch/src/helpers.rs
@@ -233,7 +233,7 @@ pub fn get_rescript_legacy(root_path: &Path, workspace_root: Option) ->
         .join("@rescript")
         .join(subfolder)
         .join("bin")
-        .join("rescript-legacy");
+        .join("rescript.exe");
 
     match (
         root_path
From 46c8454987039499c979f0af3b962482ff04ea41 Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Wed, 18 Jun 2025 23:55:36 +0200
Subject: [PATCH 05/22] test: add legacy test repo
---
 .../packages/compiled-by-legacy/package.json         |  9 +++++++++
 .../packages/compiled-by-legacy/rescript.json        | 12 ++++++++++++
 .../packages/compiled-by-legacy/src/Main.res         |  1 +
 .../packages/compiled-by-legacy/src/Main.res.js      |  9 +++++++++
 4 files changed, 31 insertions(+)
 create mode 100644 rewatch/testrepo/packages/compiled-by-legacy/package.json
 create mode 100644 rewatch/testrepo/packages/compiled-by-legacy/rescript.json
 create mode 100644 rewatch/testrepo/packages/compiled-by-legacy/src/Main.res
 create mode 100644 rewatch/testrepo/packages/compiled-by-legacy/src/Main.res.js
diff --git a/rewatch/testrepo/packages/compiled-by-legacy/package.json b/rewatch/testrepo/packages/compiled-by-legacy/package.json
new file mode 100644
index 0000000000..34d940f905
--- /dev/null
+++ b/rewatch/testrepo/packages/compiled-by-legacy/package.json
@@ -0,0 +1,9 @@
+{
+  "name": "@testrepo/compiled-by-legacy",
+  "version": "0.0.1",
+  "keywords": [
+    "rescript"
+  ],
+  "author": "",
+  "license": "MIT"
+}
diff --git a/rewatch/testrepo/packages/compiled-by-legacy/rescript.json b/rewatch/testrepo/packages/compiled-by-legacy/rescript.json
new file mode 100644
index 0000000000..f94702ab3e
--- /dev/null
+++ b/rewatch/testrepo/packages/compiled-by-legacy/rescript.json
@@ -0,0 +1,12 @@
+{
+  "name": "@testrepo/compiled-by-legacy",
+  "sources": {
+    "dir": "src",
+    "subdirs": true
+  },
+  "package-specs": {
+    "module": "es6",
+    "in-source": true
+  },
+  "suffix": ".res.js"
+}
diff --git a/rewatch/testrepo/packages/compiled-by-legacy/src/Main.res b/rewatch/testrepo/packages/compiled-by-legacy/src/Main.res
new file mode 100644
index 0000000000..3b0c82647f
--- /dev/null
+++ b/rewatch/testrepo/packages/compiled-by-legacy/src/Main.res
@@ -0,0 +1 @@
+let x = 1
\ No newline at end of file
diff --git a/rewatch/testrepo/packages/compiled-by-legacy/src/Main.res.js b/rewatch/testrepo/packages/compiled-by-legacy/src/Main.res.js
new file mode 100644
index 0000000000..618d7a44e8
--- /dev/null
+++ b/rewatch/testrepo/packages/compiled-by-legacy/src/Main.res.js
@@ -0,0 +1,9 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+let x = 1;
+
+export {
+  x,
+}
+/* No side effect */
From b4a1dbb4a8357f593a83588a12cbd854cad75fbc Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Thu, 19 Jun 2025 00:10:37 +0200
Subject: [PATCH 06/22] test: add legacy build system compile tests
---
 rewatch/tests/legacy-utils.sh |  3 +++
 rewatch/tests/legacy.sh       | 28 ++++++++++++++++++++++++++++
 rewatch/tests/suffix.sh       |  2 +-
 rewatch/tests/suite-ci.sh     |  2 +-
 4 files changed, 33 insertions(+), 2 deletions(-)
 create mode 100644 rewatch/tests/legacy-utils.sh
 create mode 100755 rewatch/tests/legacy.sh
diff --git a/rewatch/tests/legacy-utils.sh b/rewatch/tests/legacy-utils.sh
new file mode 100644
index 0000000000..3899df52c8
--- /dev/null
+++ b/rewatch/tests/legacy-utils.sh
@@ -0,0 +1,3 @@
+source "utils.sh"
+
+rewatch_legacy() { RUST_BACKTRACE=1 "../../$REWATCH_EXECUTABLE" legacy $@; }
\ No newline at end of file
diff --git a/rewatch/tests/legacy.sh b/rewatch/tests/legacy.sh
new file mode 100755
index 0000000000..231029534c
--- /dev/null
+++ b/rewatch/tests/legacy.sh
@@ -0,0 +1,28 @@
+source "./legacy-utils.sh"
+cd ../testrepo/packages/compiled-by-legacy
+
+bold "Test: It should use the legacy build system"
+
+if rewatch_legacy clean &> /dev/null;
+then
+    success "Test package cleaned"
+else
+    error "Error cleaning test package"
+    exit 1
+fi
+
+if rewatch_legacy build &> /dev/null;
+then
+    success "Test package built"
+else
+    error "Error building test package"
+    exit 1
+fi
+
+if git diff --exit-code ./;
+then
+  success "Test package has no changes"
+else
+  error "Build has changed"
+  exit 1
+fi
diff --git a/rewatch/tests/suffix.sh b/rewatch/tests/suffix.sh
index 3768005297..f51ca93306 100755
--- a/rewatch/tests/suffix.sh
+++ b/rewatch/tests/suffix.sh
@@ -27,7 +27,7 @@ fi
 # Count files with new extension
 file_count=$(find ./packages -name *.res.js | wc -l)
 
-if [ "$file_count" -eq 24 ];
+if [ "$file_count" -eq 25 ];
 then
   success "Found files with correct suffix"
 else
diff --git a/rewatch/tests/suite-ci.sh b/rewatch/tests/suite-ci.sh
index 9e5eefe1a8..fd5f11544b 100755
--- a/rewatch/tests/suite-ci.sh
+++ b/rewatch/tests/suite-ci.sh
@@ -40,4 +40,4 @@ else
   exit 1
 fi
 
-./compile.sh && ./watch.sh && ./lock.sh && ./suffix.sh
+./compile.sh && ./watch.sh && ./lock.sh && ./suffix.sh && ./legacy.sh
From aaed46593732a85b158c6f3b425b37a05c98608c Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Thu, 19 Jun 2025 22:48:01 +0200
Subject: [PATCH 07/22] test: print stderr output on failure
---
 rewatch/tests/legacy.sh | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/rewatch/tests/legacy.sh b/rewatch/tests/legacy.sh
index 231029534c..b3d5c3e029 100755
--- a/rewatch/tests/legacy.sh
+++ b/rewatch/tests/legacy.sh
@@ -3,19 +3,25 @@ cd ../testrepo/packages/compiled-by-legacy
 
 bold "Test: It should use the legacy build system"
 
-if rewatch_legacy clean &> /dev/null;
+error_output=$(rewatch_legacy clean 2>&1 >/dev/null)
+
+if [ $? -eq 0 ];
 then
     success "Test package cleaned"
 else
     error "Error cleaning test package"
+    printf "%s\n" "$error_output" >&2
     exit 1
 fi
 
-if rewatch_legacy build &> /dev/null;
+error_output=$(rewatch_legacy build 2>&1 >/dev/null)
+
+if [ $? -eq 0 ];
 then
     success "Test package built"
 else
     error "Error building test package"
+    printf "%s\n" "$error_output" >&2
     exit 1
 fi
 
From 107e5106ee3824459dbe67c0dff293b0c7c06ae1 Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Fri, 20 Jun 2025 01:05:36 +0200
Subject: [PATCH 08/22] refactor: extract fn to get binary dir
---
 rewatch/src/helpers.rs | 45 +++++++++++++++++-------------------------
 1 file changed, 18 insertions(+), 27 deletions(-)
diff --git a/rewatch/src/helpers.rs b/rewatch/src/helpers.rs
index 358c4c6ef1..61f68272ed 100644
--- a/rewatch/src/helpers.rs
+++ b/rewatch/src/helpers.rs
@@ -181,7 +181,7 @@ pub fn create_path_for_path(path: &Path) {
     fs::DirBuilder::new().recursive(true).create(path).unwrap();
 }
 
-pub fn get_bsc(root_path: &Path, workspace_root: &Option) -> PathBuf {
+fn get_bin_dir() -> PathBuf {
     let subfolder = match (std::env::consts::OS, std::env::consts::ARCH) {
         ("macos", "aarch64") => "darwin-arm64",
         ("macos", _) => "darwin-x64",
@@ -192,21 +192,24 @@ pub fn get_bsc(root_path: &Path, workspace_root: &Option) -> PathBuf {
         _ => panic!("Unsupported architecture"),
     };
 
+    Path::new("node_modules")
+        .join("@rescript")
+        .join(subfolder)
+        .join("bin")
+}
+
+pub fn get_bsc(root_path: &Path, workspace_root: &Option) -> PathBuf {
+    let bin_dir = get_bin_dir();
+
     match (
         root_path
-            .join("node_modules")
-            .join("@rescript")
-            .join(subfolder)
-            .join("bin")
+            .join(&bin_dir)
             .join("bsc.exe")
             .canonicalize()
             .map(StrippedVerbatimPath::to_stripped_verbatim_path),
         workspace_root.as_ref().map(|workspace_root| {
             workspace_root
-                .join("node_modules")
-                .join("@rescript")
-                .join(subfolder)
-                .join("bin")
+                .join(&bin_dir)
                 .join("bsc.exe")
                 .canonicalize()
                 .map(StrippedVerbatimPath::to_stripped_verbatim_path)
@@ -219,37 +222,25 @@ pub fn get_bsc(root_path: &Path, workspace_root: &Option) -> PathBuf {
 }
 
 pub fn get_rescript_legacy(root_path: &Path, workspace_root: Option) -> PathBuf {
-    let subfolder = match (std::env::consts::OS, std::env::consts::ARCH) {
-        ("macos", "aarch64") => "darwin-arm64",
-        ("macos", _) => "darwin-x64",
-        ("linux", "aarch64") => "linux-arm64",
-        ("linux", _) => "linux-x64",
-        ("windows", "aarch64") => "win-arm64",
-        ("windows", _) => "win-x64",
-        _ => panic!("Unsupported architecture"),
-    };
-
-    let legacy_path_fragment = Path::new("node_modules")
-        .join("@rescript")
-        .join(subfolder)
-        .join("bin")
-        .join("rescript.exe");
+    let bin_dir = get_bin_dir();
 
     match (
         root_path
-            .join(&legacy_path_fragment)
+            .join(&bin_dir)
+            .join("rescript.exe")
             .canonicalize()
             .map(StrippedVerbatimPath::to_stripped_verbatim_path),
         workspace_root.map(|workspace_root| {
             workspace_root
-                .join(&legacy_path_fragment)
+                .join(&bin_dir)
+                .join("rescript.exe")
                 .canonicalize()
                 .map(StrippedVerbatimPath::to_stripped_verbatim_path)
         }),
     ) {
         (Ok(path), _) => path,
         (_, Some(Ok(path))) => path,
-        _ => panic!("Could not find rescript-legacy"),
+        _ => panic!("Could not find rescript.exe"),
     }
 }
 
From 18dddc3c177a76707fb9d411a0bb6bf47ca6d66c Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Fri, 20 Jun 2025 23:13:11 +0200
Subject: [PATCH 09/22] fix: windows binary path
---
 rewatch/src/helpers.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rewatch/src/helpers.rs b/rewatch/src/helpers.rs
index 61f68272ed..5b9c93dc61 100644
--- a/rewatch/src/helpers.rs
+++ b/rewatch/src/helpers.rs
@@ -188,7 +188,7 @@ fn get_bin_dir() -> PathBuf {
         ("linux", "aarch64") => "linux-arm64",
         ("linux", _) => "linux-x64",
         ("windows", "aarch64") => "win-arm64",
-        ("windows", _) => "win-x64",
+        ("windows", _) => "win32-x64",
         _ => panic!("Unsupported architecture"),
     };
 
From 66974f9b7ee4254ab95652d3eb639ba40313bb5b Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Sat, 21 Jun 2025 19:22:23 +0200
Subject: [PATCH 10/22] fix: pass bsc-path arg only to specific subcommands
---
 cli/rewatch.js | 36 +++++++++++++++++++++++++++++++++---
 1 file changed, 33 insertions(+), 3 deletions(-)
diff --git a/cli/rewatch.js b/cli/rewatch.js
index 6df646ed4b..fed7289d45 100755
--- a/cli/rewatch.js
+++ b/cli/rewatch.js
@@ -7,6 +7,36 @@ import { rewatch_exe, bsc_exe } from "./common/bins.js";
 
 const args = process.argv.slice(2);
 
-child_process.spawnSync(rewatch_exe, [...args, "--bsc-path", bsc_exe], {
-  stdio: "inherit",
-});
+const firstPositionalArgIndex = args.findIndex((arg) => !arg.startsWith("-"));
+
+try {
+  if (firstPositionalArgIndex !== -1) {
+    const subcommand = args[firstPositionalArgIndex];
+    const subcommandArgs = args.slice(firstPositionalArgIndex + 1);
+
+    if (
+      subcommand === "build" ||
+      subcommand === "watch" ||
+      subcommand === "clean" ||
+      subcommand === "compiler-args"
+    ) {
+      child_process.execFileSync(
+        rewatch_exe,
+        [...subcommandArgs, "--bsc-path", bsc_exe],
+        {
+          stdio: "inherit",
+        }
+      );
+    } else {
+      child_process.execFileSync(rewatch_exe, [...args], {
+        stdio: "inherit",
+      });
+    }
+  }
+} catch (err) {
+  if (err.status !== undefined) {
+    process.exit(err.status); // Pass through the exit code
+  } else {
+    process.exit(1); // Generic error
+  }
+}
From d2c561de8835aee97a39a9fa05b98a41357245e5 Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Sun, 22 Jun 2025 19:27:34 +0200
Subject: [PATCH 11/22] test: add package to testrepo deps
---
 rewatch/testrepo/bsconfig.json                           | 6 ++++--
 rewatch/testrepo/package.json                            | 3 ++-
 .../testrepo/packages/compiled-by-legacy/rescript.json   | 1 +
 .../testrepo/packages/compiled-by-legacy/src/Main.mjs    | 9 +++++++++
 rewatch/testrepo/packages/main/package.json              | 1 +
 rewatch/testrepo/yarn.lock                               | 7 +++++++
 6 files changed, 24 insertions(+), 3 deletions(-)
 create mode 100644 rewatch/testrepo/packages/compiled-by-legacy/src/Main.mjs
diff --git a/rewatch/testrepo/bsconfig.json b/rewatch/testrepo/bsconfig.json
index 706ee9066a..95e49a9f45 100644
--- a/rewatch/testrepo/bsconfig.json
+++ b/rewatch/testrepo/bsconfig.json
@@ -20,7 +20,8 @@
     "@testrepo/dep02",
     "@testrepo/new-namespace",
     "@testrepo/namespace-casing",
-    "@testrepo/with-dev-deps"
+    "@testrepo/with-dev-deps",
+    "@testrepo/compiled-by-legacy"
   ],
   "bs-dependencies": [
     "@testrepo/main",
@@ -28,7 +29,8 @@
     "@testrepo/dep02",
     "@testrepo/new-namespace",
     "@testrepo/namespace-casing",
-    "@testrepo/with-dev-deps"
+    "@testrepo/with-dev-deps",
+    "@testrepo/compiled-by-legacy"
   ],
   "reason": {
     "react-jsx": 3
diff --git a/rewatch/testrepo/package.json b/rewatch/testrepo/package.json
index 3436184038..2ed3978be5 100644
--- a/rewatch/testrepo/package.json
+++ b/rewatch/testrepo/package.json
@@ -8,7 +8,8 @@
       "packages/dep02",
       "packages/new-namespace",
       "packages/namespace-casing",
-      "packages/with-dev-deps"
+      "packages/with-dev-deps",
+      "packages/compiled-by-legacy"
     ]
   },
   "dependencies": {
diff --git a/rewatch/testrepo/packages/compiled-by-legacy/rescript.json b/rewatch/testrepo/packages/compiled-by-legacy/rescript.json
index f94702ab3e..d62afdf93c 100644
--- a/rewatch/testrepo/packages/compiled-by-legacy/rescript.json
+++ b/rewatch/testrepo/packages/compiled-by-legacy/rescript.json
@@ -1,5 +1,6 @@
 {
   "name": "@testrepo/compiled-by-legacy",
+  "namespace": true,
   "sources": {
     "dir": "src",
     "subdirs": true
diff --git a/rewatch/testrepo/packages/compiled-by-legacy/src/Main.mjs b/rewatch/testrepo/packages/compiled-by-legacy/src/Main.mjs
new file mode 100644
index 0000000000..618d7a44e8
--- /dev/null
+++ b/rewatch/testrepo/packages/compiled-by-legacy/src/Main.mjs
@@ -0,0 +1,9 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+let x = 1;
+
+export {
+  x,
+}
+/* No side effect */
diff --git a/rewatch/testrepo/packages/main/package.json b/rewatch/testrepo/packages/main/package.json
index 10954e0797..1813de299d 100644
--- a/rewatch/testrepo/packages/main/package.json
+++ b/rewatch/testrepo/packages/main/package.json
@@ -12,6 +12,7 @@
   "author": "",
   "license": "MIT",
   "dependencies": {
+    "@testrepo/compiled-by-legacy": "*",
     "@testrepo/dep01": "*"
   }
 }
diff --git a/rewatch/testrepo/yarn.lock b/rewatch/testrepo/yarn.lock
index ac41b3d2c1..beb6bf912d 100644
--- a/rewatch/testrepo/yarn.lock
+++ b/rewatch/testrepo/yarn.lock
@@ -40,6 +40,12 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@testrepo/compiled-by-legacy@npm:*, @testrepo/compiled-by-legacy@workspace:packages/compiled-by-legacy":
+  version: 0.0.0-use.local
+  resolution: "@testrepo/compiled-by-legacy@workspace:packages/compiled-by-legacy"
+  languageName: unknown
+  linkType: soft
+
 "@testrepo/dep01@npm:*, @testrepo/dep01@workspace:packages/dep01":
   version: 0.0.0-use.local
   resolution: "@testrepo/dep01@workspace:packages/dep01"
@@ -58,6 +64,7 @@ __metadata:
   version: 0.0.0-use.local
   resolution: "@testrepo/main@workspace:packages/main"
   dependencies:
+    "@testrepo/compiled-by-legacy": "npm:*"
     "@testrepo/dep01": "npm:*"
   languageName: unknown
   linkType: soft
From 7742361212e86e724887bf9f58f3154ee9f2ef83 Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Sun, 22 Jun 2025 19:46:58 +0200
Subject: [PATCH 12/22] test: update snapshots
---
 rewatch/tests/snapshots/dependency-cycle.txt                   | 2 +-
 rewatch/tests/snapshots/remove-file.txt                        | 2 +-
 rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt | 2 +-
 rewatch/tests/snapshots/rename-file-internal-dep.txt           | 2 +-
 rewatch/tests/snapshots/rename-file-with-interface.txt         | 2 +-
 rewatch/tests/snapshots/rename-file.txt                        | 2 +-
 rewatch/tests/snapshots/rename-interface-file.txt              | 2 +-
 rewatch/tests/suffix.sh                                        | 2 +-
 8 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/rewatch/tests/snapshots/dependency-cycle.txt b/rewatch/tests/snapshots/dependency-cycle.txt
index c949d4005c..c6d7f793e8 100644
--- a/rewatch/tests/snapshots/dependency-cycle.txt
+++ b/rewatch/tests/snapshots/dependency-cycle.txt
@@ -1,4 +1,4 @@
-Cleaned 0/14
+Cleaned 0/15
 Parsed 1 source files
 Compiled 0 modules
 
diff --git a/rewatch/tests/snapshots/remove-file.txt b/rewatch/tests/snapshots/remove-file.txt
index 921f9d4246..8d68cdd418 100644
--- a/rewatch/tests/snapshots/remove-file.txt
+++ b/rewatch/tests/snapshots/remove-file.txt
@@ -1,4 +1,4 @@
-Cleaned 1/14
+Cleaned 1/15
 Parsed 0 source files
 Compiled 1 modules
 
diff --git a/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt b/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt
index 1950bad9e3..3263c0fe88 100644
--- a/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt
+++ b/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt
@@ -1,4 +1,4 @@
-Cleaned 2/14
+Cleaned 2/15
 Parsed 2 source files
 Compiled 3 modules
 
diff --git a/rewatch/tests/snapshots/rename-file-internal-dep.txt b/rewatch/tests/snapshots/rename-file-internal-dep.txt
index f81a6dece4..829a187871 100644
--- a/rewatch/tests/snapshots/rename-file-internal-dep.txt
+++ b/rewatch/tests/snapshots/rename-file-internal-dep.txt
@@ -1,4 +1,4 @@
-Cleaned 2/14
+Cleaned 2/15
 Parsed 2 source files
 Compiled 2 modules
 
diff --git a/rewatch/tests/snapshots/rename-file-with-interface.txt b/rewatch/tests/snapshots/rename-file-with-interface.txt
index 257fba3570..e784d71b9e 100644
--- a/rewatch/tests/snapshots/rename-file-with-interface.txt
+++ b/rewatch/tests/snapshots/rename-file-with-interface.txt
@@ -1,4 +1,4 @@
 [2K
 No implementation file found for interface file (skipping): src/ModuleWithInterface.resi
-Cleaned 2/14
+Cleaned 2/15
 Parsed 1 source files
 Compiled 2 modules
diff --git a/rewatch/tests/snapshots/rename-file.txt b/rewatch/tests/snapshots/rename-file.txt
index 5b20d86fd7..5458530d74 100644
--- a/rewatch/tests/snapshots/rename-file.txt
+++ b/rewatch/tests/snapshots/rename-file.txt
@@ -1,3 +1,3 @@
-Cleaned 1/14
+Cleaned 1/15
 Parsed 1 source files
 Compiled 1 modules
diff --git a/rewatch/tests/snapshots/rename-interface-file.txt b/rewatch/tests/snapshots/rename-interface-file.txt
index e2d7fd9752..26d7ffe3e6 100644
--- a/rewatch/tests/snapshots/rename-interface-file.txt
+++ b/rewatch/tests/snapshots/rename-interface-file.txt
@@ -1,4 +1,4 @@
 [2K
 No implementation file found for interface file (skipping): src/ModuleWithInterface2.resi
-Cleaned 1/14
+Cleaned 1/15
 Parsed 1 source files
 Compiled 2 modules
diff --git a/rewatch/tests/suffix.sh b/rewatch/tests/suffix.sh
index f51ca93306..d644a34e99 100755
--- a/rewatch/tests/suffix.sh
+++ b/rewatch/tests/suffix.sh
@@ -27,7 +27,7 @@ fi
 # Count files with new extension
 file_count=$(find ./packages -name *.res.js | wc -l)
 
-if [ "$file_count" -eq 25 ];
+if [ "$file_count" -eq 26 ];
 then
   success "Found files with correct suffix"
 else
From fb9400cedcb5aa48d2203a9e671b4fb4fdd68c55 Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Sun, 22 Jun 2025 20:03:07 +0200
Subject: [PATCH 13/22] test: improve test output
---
 rewatch/tests/compile.sh | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/rewatch/tests/compile.sh b/rewatch/tests/compile.sh
index f694b3b0bb..a465933c9e 100755
--- a/rewatch/tests/compile.sh
+++ b/rewatch/tests/compile.sh
@@ -87,21 +87,23 @@ then
 fi
 
 file_count=$(find ./packages/with-dev-deps/test -name *.mjs | wc -l)
-if [ "$file_count" -eq 1 ];
+expected_file_count=1
+if [ "$file_count" -eq $expected_file_count ];
 then
   success "Compiled dev dependencies successfully"
 else
-  error "Expected 2 files to be compiled with the --dev flag, found $file_count"
+  error "Expected $expected_file_count files to be compiled with the --dev flag, found $file_count"
   exit 1
 fi
 
-rewatch clean &> /dev/null
+error_output=$(rewatch clean 2>&1 >/dev/null)
 file_count=$(find ./packages/with-dev-deps -name *.mjs | wc -l)
 if [ "$file_count" -eq 0 ];
 then
   success "Cleaned dev dependencies successfully"
 else
   error "Expected 0 files remaining after cleaning, found $file_count"
+  printf "%s\n" "$error_output" >&2
   exit 1
 fi
 
From 9bf9d43048543ea6fd86e43b590ce364b3e4f9a8 Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Mon, 23 Jun 2025 09:24:25 +0200
Subject: [PATCH 14/22] fix: args passing in cli wrapper
---
 cli/rewatch.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cli/rewatch.js b/cli/rewatch.js
index fed7289d45..d363ebe477 100755
--- a/cli/rewatch.js
+++ b/cli/rewatch.js
@@ -12,7 +12,7 @@ const firstPositionalArgIndex = args.findIndex((arg) => !arg.startsWith("-"));
 try {
   if (firstPositionalArgIndex !== -1) {
     const subcommand = args[firstPositionalArgIndex];
-    const subcommandArgs = args.slice(firstPositionalArgIndex + 1);
+    const subcommandWithArgs = args.slice(firstPositionalArgIndex);
 
     if (
       subcommand === "build" ||
@@ -22,7 +22,7 @@ try {
     ) {
       child_process.execFileSync(
         rewatch_exe,
-        [...subcommandArgs, "--bsc-path", bsc_exe],
+        [...subcommandWithArgs, "--bsc-path", bsc_exe],
         {
           stdio: "inherit",
         }
From 7faf311279d3eafc91666b1662cb4409df9be008 Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Mon, 23 Jun 2025 21:09:35 +0200
Subject: [PATCH 15/22] fix: pass `--bsc-path` to implicit build command
---
 cli/rewatch.js | 5 +++++
 1 file changed, 5 insertions(+)
diff --git a/cli/rewatch.js b/cli/rewatch.js
index d363ebe477..d7e65e077e 100755
--- a/cli/rewatch.js
+++ b/cli/rewatch.js
@@ -32,6 +32,11 @@ try {
         stdio: "inherit",
       });
     }
+  } else {
+    // no subcommand means build subcommand
+    child_process.execFileSync(rewatch_exe, [...args, "--bsc-path", bsc_exe], {
+      stdio: "inherit",
+    });
   }
 } catch (err) {
   if (err.status !== undefined) {
From b730d9f1419645507a0cd7c9f4d1c8409d07d4ef Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Sat, 28 Jun 2025 23:21:12 +0200
Subject: [PATCH 16/22] fix: wrong path to `rescript` executable
refactor `cli.rs` to remove duplication of docstrings
---
 rewatch/src/build.rs                          |  10 +-
 rewatch/src/build/clean.rs                    |   2 +-
 rewatch/src/cli.rs                            | 193 ++++++++++++------
 rewatch/src/helpers.rs                        |   6 +-
 rewatch/src/main.rs                           | 172 ++++++++--------
 .../packages/compiled-by-legacy/rescript.json |   2 +-
 rewatch/tests/legacy.sh                       |  26 ++-
 7 files changed, 256 insertions(+), 155 deletions(-)
diff --git a/rewatch/src/build.rs b/rewatch/src/build.rs
index ab00db8aad..50ab16a216 100644
--- a/rewatch/src/build.rs
+++ b/rewatch/src/build.rs
@@ -59,7 +59,7 @@ pub struct CompilerArgs {
 pub fn get_compiler_args(
     path: &Path,
     rescript_version: Option,
-    bsc_path: &Option,
+    bsc_path: Option,
     build_dev_deps: bool,
 ) -> Result {
     let filename = &helpers::get_abs_path(path);
@@ -501,7 +501,7 @@ pub fn build(
     show_progress: bool,
     no_timing: bool,
     create_sourcedirs: bool,
-    bsc_path: &Option,
+    bsc_path: Option,
     build_dev_deps: bool,
     snapshot_output: bool,
 ) -> Result {
@@ -516,7 +516,7 @@ pub fn build(
         filter,
         show_progress,
         path,
-        bsc_path,
+        &bsc_path,
         build_dev_deps,
         snapshot_output,
     )
@@ -558,9 +558,9 @@ pub fn pass_through_legacy(args: Vec) -> i32 {
     let project_root = helpers::get_abs_path(Path::new("."));
     let workspace_root = helpers::get_workspace_root(&project_root);
 
-    let bsb_path = helpers::get_rescript_legacy(&project_root, workspace_root);
+    let rescript_legacy_path = helpers::get_rescript_legacy(&project_root, workspace_root);
 
-    let status = std::process::Command::new(bsb_path)
+    let status = std::process::Command::new(rescript_legacy_path)
         .args(args)
         .stdout(Stdio::inherit())
         .stderr(Stdio::inherit())
diff --git a/rewatch/src/build/clean.rs b/rewatch/src/build/clean.rs
index 0057437997..8a09bef035 100644
--- a/rewatch/src/build/clean.rs
+++ b/rewatch/src/build/clean.rs
@@ -334,7 +334,7 @@ pub fn cleanup_after_build(build_state: &BuildState) {
 pub fn clean(
     path: &Path,
     show_progress: bool,
-    bsc_path: &Option,
+    bsc_path: Option,
     snapshot_output: bool,
 ) -> Result<()> {
     let project_root = helpers::get_abs_path(path);
diff --git a/rewatch/src/cli.rs b/rewatch/src/cli.rs
index 87ec1efb09..d9a088d8ae 100644
--- a/rewatch/src/cli.rs
+++ b/rewatch/src/cli.rs
@@ -1,4 +1,4 @@
-use std::ffi::OsString;
+use std::{ffi::OsString, ops::Deref};
 
 use clap::{Args, Parser, Subcommand};
 use clap_verbosity_flag::InfoLevel;
@@ -24,23 +24,29 @@ pub struct Cli {
     #[command(subcommand)]
     pub command: Option,
 
+    #[command(flatten)]
+    pub build_args: BuildArgs,
+}
+
+#[derive(Args, Debug, Clone)]
+pub struct FolderArg {
     /// The relative path to where the main rescript.json resides. IE - the root of your project.
     #[arg(default_value = ".")]
     pub folder: String,
-
-    #[command(flatten)]
-    pub build_args: BuildArgs,
 }
 
 #[derive(Args, Debug, Clone)]
-pub struct BuildArgs {
+pub struct FilterArg {
     /// Filter files by regex
     ///
     /// Filter allows for a regex to be supplied which will filter the files to be compiled. For
     /// instance, to filter out test files for compilation while doing feature work.
     #[arg(short, long)]
     pub filter: Option,
+}
 
+#[derive(Args, Debug, Clone)]
+pub struct AfterBuildArg {
     /// Action after build
     ///
     /// This allows one to pass an additional command to the watcher, which allows it to run when
@@ -49,14 +55,20 @@ pub struct BuildArgs {
     /// color as well
     #[arg(short, long)]
     pub after_build: Option,
+}
 
+#[derive(Args, Debug, Clone, Copy)]
+pub struct CreateSourceDirsArg {
     /// Create source_dirs.json
     ///
     /// This creates a source_dirs.json file at the root of the monorepo, which is needed when you
     /// want to use Reanalyze
     #[arg(short, long, default_value_t = false, num_args = 0..=1)]
     pub create_sourcedirs: bool,
+}
 
+#[derive(Args, Debug, Clone, Copy)]
+pub struct DevArg {
     /// Build development dependencies
     ///
     /// This is the flag to also compile development dependencies
@@ -65,61 +77,72 @@ pub struct BuildArgs {
     /// _all_ packages
     #[arg(long, default_value_t = false, num_args = 0..=1)]
     pub dev: bool,
+}
 
-    /// Disable timing on the output
-    #[arg(short, long, default_value_t = false, num_args = 0..=1)]
-    pub no_timing: bool,
+#[derive(Args, Debug, Clone)]
+pub struct BscPathArg {
+    /// Custom path to bsc
+    #[arg(long)]
+    pub bsc_path: Option,
+}
 
+#[derive(Args, Debug, Clone, Copy)]
+pub struct SnapshotOutputArg {
     /// simple output for snapshot testing
     #[arg(short, long, default_value = "false", num_args = 0..=1)]
     pub snapshot_output: bool,
+}
 
-    /// Path to bsc
-    #[arg(long)]
-    pub bsc_path: Option,
+#[derive(Args, Debug, Clone)]
+pub struct BuildArgs {
+    #[command(flatten)]
+    pub folder: FolderArg,
+
+    #[command(flatten)]
+    pub filter: FilterArg,
+
+    #[command(flatten)]
+    pub after_build: AfterBuildArg,
+
+    #[command(flatten)]
+    pub create_sourcedirs: CreateSourceDirsArg,
+
+    #[command(flatten)]
+    pub dev: DevArg,
+
+    /// Disable timing on the output
+    #[arg(short, long, default_value_t = false, num_args = 0..=1)]
+    pub no_timing: bool,
+
+    #[command(flatten)]
+    pub snapshot_output: SnapshotOutputArg,
+
+    #[command(flatten)]
+    pub bsc_path: BscPathArg,
 }
 
 #[derive(Args, Clone, Debug)]
 pub struct WatchArgs {
-    /// Filter files by regex
-    ///
-    /// Filter allows for a regex to be supplied which will filter the files to be compiled. For
-    /// instance, to filter out test files for compilation while doing feature work.
-    #[arg(short, long)]
-    pub filter: Option,
+    #[command(flatten)]
+    pub folder: FolderArg,
 
-    /// Action after build
-    ///
-    /// This allows one to pass an additional command to the watcher, which allows it to run when
-    /// finished. For instance, to play a sound when done compiling, or to run a test suite.
-    /// NOTE - You may need to add '--color=always' to your subcommand in case you want to output
-    /// color as well
-    #[arg(short, long)]
-    pub after_build: Option,
+    #[command(flatten)]
+    pub filter: FilterArg,
 
-    /// Create source_dirs.json
-    ///
-    /// This creates a source_dirs.json file at the root of the monorepo, which is needed when you
-    /// want to use Reanalyze
-    #[arg(short, long, default_value_t = false, num_args = 0..=1)]
-    pub create_sourcedirs: bool,
+    #[command(flatten)]
+    pub after_build: AfterBuildArg,
 
-    /// Build development dependencies
-    ///
-    /// This is the flag to also compile development dependencies
-    /// It's important to know that we currently do not discern between project src, and
-    /// dependencies. So enabling this flag will enable building _all_ development dependencies of
-    /// _all_ packages
-    #[arg(long, default_value_t = false, num_args = 0..=1)]
-    pub dev: bool,
+    #[command(flatten)]
+    pub create_sourcedirs: CreateSourceDirsArg,
 
-    /// simple output for snapshot testing
-    #[arg(short, long, default_value = "false", num_args = 0..=1)]
-    pub snapshot_output: bool,
+    #[command(flatten)]
+    pub dev: DevArg,
 
-    /// Path to bsc
-    #[arg(long)]
-    pub bsc_path: Option,
+    #[command(flatten)]
+    pub snapshot_output: SnapshotOutputArg,
+
+    #[command(flatten)]
+    pub bsc_path: BscPathArg,
 }
 
 #[derive(Subcommand, Clone, Debug)]
@@ -130,13 +153,14 @@ pub enum Command {
     Watch(WatchArgs),
     /// Clean the build artifacts
     Clean {
-        /// Path to bsc
-        #[arg(long)]
-        bsc_path: Option,
+        #[command(flatten)]
+        folder: FolderArg,
+
+        #[command(flatten)]
+        bsc_path: BscPathArg,
 
-        /// simple output for snapshot testing
-        #[arg(short, long, default_value = "false", num_args = 0..=1)]
-        snapshot_output: bool,
+        #[command(flatten)]
+        snapshot_output: SnapshotOutputArg,
     },
     /// Alias to `legacy format`.
     #[command(disable_help_flag = true)]
@@ -156,23 +180,78 @@ pub enum Command {
         #[command()]
         path: String,
 
-        #[arg(long, default_value_t = false, num_args = 0..=1)]
-        dev: bool,
+        #[command(flatten)]
+        dev: DevArg,
 
         /// To be used in conjunction with compiler_args
         #[arg(long)]
         rescript_version: Option,
 
-        /// A custom path to bsc
-        #[arg(long)]
-        bsc_path: Option,
+        #[command(flatten)]
+        bsc_path: BscPathArg,
     },
     /// Use the legacy build system.
     ///
     /// After this command is encountered, the rest of the arguments are passed to the legacy build system.
-    #[command(disable_help_flag = true)]
+    #[command(disable_help_flag = true, external_subcommand = true)]
     Legacy {
         #[arg(allow_hyphen_values = true, num_args = 0..)]
         legacy_args: Vec,
     },
 }
+
+impl Deref for FolderArg {
+    type Target = str;
+
+    fn deref(&self) -> &Self::Target {
+        &self.folder
+    }
+}
+
+impl Deref for FilterArg {
+    type Target = Option;
+
+    fn deref(&self) -> &Self::Target {
+        &self.filter
+    }
+}
+
+impl Deref for AfterBuildArg {
+    type Target = Option;
+
+    fn deref(&self) -> &Self::Target {
+        &self.after_build
+    }
+}
+
+impl Deref for CreateSourceDirsArg {
+    type Target = bool;
+
+    fn deref(&self) -> &Self::Target {
+        &self.create_sourcedirs
+    }
+}
+
+impl Deref for DevArg {
+    type Target = bool;
+
+    fn deref(&self) -> &Self::Target {
+        &self.dev
+    }
+}
+
+impl Deref for BscPathArg {
+    type Target = Option;
+
+    fn deref(&self) -> &Self::Target {
+        &self.bsc_path
+    }
+}
+
+impl Deref for SnapshotOutputArg {
+    type Target = bool;
+
+    fn deref(&self) -> &Self::Target {
+        &self.snapshot_output
+    }
+}
diff --git a/rewatch/src/helpers.rs b/rewatch/src/helpers.rs
index 5b9c93dc61..9b6661c2e4 100644
--- a/rewatch/src/helpers.rs
+++ b/rewatch/src/helpers.rs
@@ -222,18 +222,18 @@ pub fn get_bsc(root_path: &Path, workspace_root: &Option) -> PathBuf {
 }
 
 pub fn get_rescript_legacy(root_path: &Path, workspace_root: Option) -> PathBuf {
-    let bin_dir = get_bin_dir();
+    let bin_dir = Path::new("node_modules").join("rescript").join("cli");
 
     match (
         root_path
             .join(&bin_dir)
-            .join("rescript.exe")
+            .join("rescript.js")
             .canonicalize()
             .map(StrippedVerbatimPath::to_stripped_verbatim_path),
         workspace_root.map(|workspace_root| {
             workspace_root
                 .join(&bin_dir)
-                .join("rescript.exe")
+                .join("rescript.js")
                 .canonicalize()
                 .map(StrippedVerbatimPath::to_stripped_verbatim_path)
         }),
diff --git a/rewatch/src/main.rs b/rewatch/src/main.rs
index dcebec5945..3cd1c5d084 100644
--- a/rewatch/src/main.rs
+++ b/rewatch/src/main.rs
@@ -22,22 +22,11 @@ fn main() -> Result<()> {
 
     let command = args.command.unwrap_or(cli::Command::Build(args.build_args));
 
-    // handle those commands early, because we don't need a lock for them
+    // The 'normal run' mode will show the 'pretty' formatted progress. But if we turn off the log
+    // level, we should never show that.
+    let show_progress = log_level_filter == LevelFilter::Info;
+
     match command.clone() {
-        cli::Command::Legacy { legacy_args } => {
-            let code = build::pass_through_legacy(legacy_args);
-            std::process::exit(code);
-        }
-        cli::Command::Format { mut format_args } => {
-            format_args.insert(0, "format".into());
-            let code = build::pass_through_legacy(format_args);
-            std::process::exit(code);
-        }
-        cli::Command::Dump { mut dump_args } => {
-            dump_args.insert(0, "dump".into());
-            let code = build::pass_through_legacy(dump_args);
-            std::process::exit(code);
-        }
         cli::Command::CompilerArgs {
             path,
             dev,
@@ -49,84 +38,99 @@ fn main() -> Result<()> {
                 build::get_compiler_args(
                     Path::new(&path),
                     rescript_version,
-                    &bsc_path.map(PathBuf::from),
-                    dev
+                    bsc_path.as_ref().map(PathBuf::from),
+                    *dev
                 )?
             );
             std::process::exit(0);
         }
-        _ => (),
-    }
+        cli::Command::Build(build_args) => {
+            let _lock = get_lock(&build_args.folder);
 
-    // The 'normal run' mode will show the 'pretty' formatted progress. But if we turn off the log
-    // level, we should never show that.
-    let show_progress = log_level_filter == LevelFilter::Info;
+            let filter = build_args
+                .filter
+                .as_ref()
+                .map(|filter| Regex::new(&filter).expect("Could not parse regex"));
 
-    match lock::get(&args.folder) {
-        lock::Lock::Error(ref e) => {
-            println!("Could not start Rewatch: {e}");
-            std::process::exit(1)
+            match build::build(
+                &filter,
+                Path::new(&build_args.folder as &str),
+                show_progress,
+                build_args.no_timing,
+                *build_args.create_sourcedirs,
+                build_args.bsc_path.as_ref().map(PathBuf::from),
+                *build_args.dev,
+                *build_args.snapshot_output,
+            ) {
+                Err(e) => {
+                    println!("{e}");
+                    std::process::exit(1)
+                }
+                Ok(_) => {
+                    if let Some(args_after_build) = (*build_args.after_build).clone() {
+                        cmd::run(args_after_build)
+                    }
+                    std::process::exit(0)
+                }
+            };
         }
-        lock::Lock::Aquired(_) => match command {
-            cli::Command::Clean {
-                bsc_path,
-                snapshot_output,
-            } => build::clean::clean(
-                Path::new(&args.folder),
+        cli::Command::Watch(watch_args) => {
+            let _lock = get_lock(&watch_args.folder);
+
+            let filter = watch_args
+                .filter
+                .as_ref()
+                .map(|filter| Regex::new(&filter).expect("Could not parse regex"));
+            watcher::start(
+                &filter,
                 show_progress,
-                &bsc_path.map(PathBuf::from),
-                snapshot_output,
-            ),
-            cli::Command::Build(build_args) => {
-                let filter = build_args
-                    .filter
-                    .map(|filter| Regex::new(filter.as_ref()).expect("Could not parse regex"));
+                &watch_args.folder,
+                (*watch_args.after_build).clone(),
+                *watch_args.create_sourcedirs,
+                *watch_args.dev,
+                (*watch_args.bsc_path).clone(),
+                *watch_args.snapshot_output,
+            );
 
-                match build::build(
-                    &filter,
-                    Path::new(&args.folder),
-                    show_progress,
-                    build_args.no_timing,
-                    build_args.create_sourcedirs,
-                    &build_args.bsc_path.map(PathBuf::from),
-                    build_args.dev,
-                    build_args.snapshot_output,
-                ) {
-                    Err(e) => {
-                        println!("{e}");
-                        std::process::exit(1)
-                    }
-                    Ok(_) => {
-                        if let Some(args_after_build) = build_args.after_build {
-                            cmd::run(args_after_build)
-                        }
-                        std::process::exit(0)
-                    }
-                };
-            }
-            cli::Command::Watch(watch_args) => {
-                let filter = watch_args
-                    .filter
-                    .map(|filter| Regex::new(filter.as_ref()).expect("Could not parse regex"));
-                watcher::start(
-                    &filter,
-                    show_progress,
-                    &args.folder,
-                    watch_args.after_build,
-                    watch_args.create_sourcedirs,
-                    watch_args.dev,
-                    watch_args.bsc_path,
-                    watch_args.snapshot_output,
-                );
+            Ok(())
+        }
+        cli::Command::Clean {
+            folder,
+            bsc_path,
+            snapshot_output,
+        } => {
+            let _lock = get_lock(&folder);
 
-                Ok(())
-            }
-            cli::Command::CompilerArgs { .. }
-            | cli::Command::Legacy { .. }
-            | cli::Command::Format { .. }
-            | cli::Command::Dump { .. } => {
-                unreachable!("command already handled")
-            }
-        },
+            build::clean::clean(
+                Path::new(&folder as &str),
+                show_progress,
+                bsc_path.as_ref().map(PathBuf::from),
+                *snapshot_output,
+            )
+        }
+        cli::Command::Legacy { legacy_args } => {
+            let code = build::pass_through_legacy(legacy_args);
+            std::process::exit(code);
+        }
+        cli::Command::Format { mut format_args } => {
+            format_args.insert(0, "format".into());
+            let code = build::pass_through_legacy(format_args);
+            std::process::exit(code);
+        }
+        cli::Command::Dump { mut dump_args } => {
+            dump_args.insert(0, "dump".into());
+            let code = build::pass_through_legacy(dump_args);
+            std::process::exit(code);
+        }
+    }
+}
+
+fn get_lock(folder: &str) -> lock::Lock {
+    match lock::get(folder) {
+        lock::Lock::Error(error) => {
+            println!("Could not start Rewatch: {error}");
+            std::process::exit(1);
+        }
+        acquired_lock => acquired_lock,
     }
 }
diff --git a/rewatch/testrepo/packages/compiled-by-legacy/rescript.json b/rewatch/testrepo/packages/compiled-by-legacy/rescript.json
index d62afdf93c..3722c661d5 100644
--- a/rewatch/testrepo/packages/compiled-by-legacy/rescript.json
+++ b/rewatch/testrepo/packages/compiled-by-legacy/rescript.json
@@ -6,7 +6,7 @@
     "subdirs": true
   },
   "package-specs": {
-    "module": "es6",
+    "module": "esmodule",
     "in-source": true
   },
   "suffix": ".res.js"
diff --git a/rewatch/tests/legacy.sh b/rewatch/tests/legacy.sh
index b3d5c3e029..a5e3de54cb 100755
--- a/rewatch/tests/legacy.sh
+++ b/rewatch/tests/legacy.sh
@@ -3,19 +3,26 @@ cd ../testrepo/packages/compiled-by-legacy
 
 bold "Test: It should use the legacy build system"
 
-error_output=$(rewatch_legacy clean 2>&1 >/dev/null)
+error_output=$(rewatch_legacy 2>&1 >/dev/null)
+if [ -n "$error_output" ];
+then
+    error "Error running rewatch"
+    printf "%s\n" "$error_output" >&2
+    exit 1
+fi
 
-if [ $? -eq 0 ];
+error_output=$(rewatch_legacy clean 2>&1 >/dev/null)
+file_count=$(find . -name "*.res.js" | wc -l)
+if [ $? -eq 0 ] && [ $file_count -eq 0 ];
 then
     success "Test package cleaned"
 else
-    error "Error cleaning test package"
+    error "Error cleaning test package. File count was $file_count."
     printf "%s\n" "$error_output" >&2
     exit 1
 fi
 
 error_output=$(rewatch_legacy build 2>&1 >/dev/null)
-
 if [ $? -eq 0 ];
 then
     success "Test package built"
@@ -32,3 +39,14 @@ else
   error "Build has changed"
   exit 1
 fi
+
+error_output=$(rewatch_legacy format -all 2>&1 >/dev/null)
+git_diff_file_count=$(git diff --name-only ./ | wc -l)
+if [ $? -eq 0 ] && [ $git_diff_file_count -eq 1 ];
+then
+    success "Test package formatted. Got $git_diff_file_count changed files."
+else
+    error "Error formatting test package"
+    printf "%s\n" "$error_output" >&2
+    exit 1
+fi
\ No newline at end of file
From f003508b6ee18f0af42f5488e266cfe209092e5a Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Sat, 28 Jun 2025 23:32:54 +0200
Subject: [PATCH 17/22] fix: legacy tests inconsistency
---
 rewatch/tests/legacy.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rewatch/tests/legacy.sh b/rewatch/tests/legacy.sh
index a5e3de54cb..19f80a029b 100755
--- a/rewatch/tests/legacy.sh
+++ b/rewatch/tests/legacy.sh
@@ -4,9 +4,9 @@ cd ../testrepo/packages/compiled-by-legacy
 bold "Test: It should use the legacy build system"
 
 error_output=$(rewatch_legacy 2>&1 >/dev/null)
-if [ -n "$error_output" ];
+if [ $? -ne 0 ];
 then
-    error "Error running rewatch"
+    error "Error running rewatch legacy"
     printf "%s\n" "$error_output" >&2
     exit 1
 fi
From c88b2d40fb66821af12f6479cd6b454388084d0e Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Sun, 29 Jun 2025 00:11:37 +0200
Subject: [PATCH 18/22] test: is error output printed correctly
---
 rewatch/tests/legacy.sh | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/rewatch/tests/legacy.sh b/rewatch/tests/legacy.sh
index 19f80a029b..3a350c2a3a 100755
--- a/rewatch/tests/legacy.sh
+++ b/rewatch/tests/legacy.sh
@@ -7,7 +7,7 @@ error_output=$(rewatch_legacy 2>&1 >/dev/null)
 if [ $? -ne 0 ];
 then
     error "Error running rewatch legacy"
-    printf "%s\n" "$error_output" >&2
+    echo $error_output
     exit 1
 fi
 
@@ -18,7 +18,7 @@ then
     success "Test package cleaned"
 else
     error "Error cleaning test package. File count was $file_count."
-    printf "%s\n" "$error_output" >&2
+    echo $error_output
     exit 1
 fi
 
@@ -28,7 +28,7 @@ then
     success "Test package built"
 else
     error "Error building test package"
-    printf "%s\n" "$error_output" >&2
+    echo $error_output
     exit 1
 fi
 
@@ -47,6 +47,6 @@ then
     success "Test package formatted. Got $git_diff_file_count changed files."
 else
     error "Error formatting test package"
-    printf "%s\n" "$error_output" >&2
+    echo $error_output
     exit 1
 fi
\ No newline at end of file
From 876b7971fcfa7c8ab038c538583e5f5fb917ca1e Mon Sep 17 00:00:00 2001
From: Bushuo 
Date: Sun, 29 Jun 2025 00:23:46 +0200
Subject: [PATCH 19/22] debug: print whole output of rewatch legacy
---
 rewatch/tests/legacy.sh | 1 +
 1 file changed, 1 insertion(+)
diff --git a/rewatch/tests/legacy.sh b/rewatch/tests/legacy.sh
index 3a350c2a3a..6a09359ef7 100755
--- a/rewatch/tests/legacy.sh
+++ b/rewatch/tests/legacy.sh
@@ -3,6 +3,7 @@ cd ../testrepo/packages/compiled-by-legacy
 
 bold "Test: It should use the legacy build system"
 
+rewatch_legacy
 error_output=$(rewatch_legacy 2>&1 >/dev/null)
 if [ $? -ne 0 ];
 then
From 635159bb7527d3d78aed41ce7e34501e5369a131 Mon Sep 17 00:00:00 2001
From: Paul Buschmann 
Date: Sun, 29 Jun 2025 12:19:44 +0200
Subject: [PATCH 20/22] fix: node script invocation on windows
---
 rewatch/src/build.rs    | 13 ++++++++++---
 rewatch/tests/legacy.sh |  1 -
 2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/rewatch/src/build.rs b/rewatch/src/build.rs
index 50ab16a216..7562872f02 100644
--- a/rewatch/src/build.rs
+++ b/rewatch/src/build.rs
@@ -554,17 +554,24 @@ pub fn build(
     }
 }
 
-pub fn pass_through_legacy(args: Vec) -> i32 {
+pub fn pass_through_legacy(mut args: Vec) -> i32 {
     let project_root = helpers::get_abs_path(Path::new("."));
     let workspace_root = helpers::get_workspace_root(&project_root);
 
     let rescript_legacy_path = helpers::get_rescript_legacy(&project_root, workspace_root);
 
-    let status = std::process::Command::new(rescript_legacy_path)
+    args.insert(0, rescript_legacy_path.into());
+    let status = std::process::Command::new("node")
         .args(args)
         .stdout(Stdio::inherit())
         .stderr(Stdio::inherit())
         .status();
 
-    status.map(|s| s.code().unwrap_or(1)).unwrap_or(1)
+    match status {
+        Ok(s) => s.code().unwrap_or(0),
+        Err(err) => {
+            eprintln!("Error running the legacy build system: {err}");
+            1
+        }
+    }
 }
diff --git a/rewatch/tests/legacy.sh b/rewatch/tests/legacy.sh
index 6a09359ef7..3a350c2a3a 100755
--- a/rewatch/tests/legacy.sh
+++ b/rewatch/tests/legacy.sh
@@ -3,7 +3,6 @@ cd ../testrepo/packages/compiled-by-legacy
 
 bold "Test: It should use the legacy build system"
 
-rewatch_legacy
 error_output=$(rewatch_legacy 2>&1 >/dev/null)
 if [ $? -ne 0 ];
 then
From 2006080116c8341622d6f01f1e3d53383bf0b8b8 Mon Sep 17 00:00:00 2001
From: Paul Buschmann 
Date: Mon, 30 Jun 2025 15:26:25 +0200
Subject: [PATCH 21/22] Update CHANGELOG.md
---
 CHANGELOG.md | 4 ++++
 1 file changed, 4 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd9695b714..6ac6282de1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,10 @@
 
 # 12.0.0-alpha.15 (Unreleased)
 
+#### :boom: Breaking Change
+
+- The legacy rescript cli can be called through rewatch via `rewatch legacy`. Arguments to rewatch need to be passed after the subcommand. https://github.com/rescript-lang/rescript/pull/7551
+
 #### :bug: Bug fix
 
 - Ignore inferred arity in functions inside `%raw` functions, leaving to `%ffi` the responsibility to check the arity since it gives an error in case of mismatch. https://github.com/rescript-lang/rescript/pull/7542
From 834f890546635bc677b8c92dd403a55876b4c9dc Mon Sep 17 00:00:00 2001
From: Paul Buschmann 
Date: Mon, 30 Jun 2025 16:18:51 +0200
Subject: [PATCH 22/22] Update CHANGELOG.md
---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ac6282de1..0b52d12ecf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,7 +14,7 @@
 
 #### :boom: Breaking Change
 
-- The legacy rescript cli can be called through rewatch via `rewatch legacy`. Arguments to rewatch need to be passed after the subcommand. https://github.com/rescript-lang/rescript/pull/7551
+- The legacy rescript cli can be called through rewatch via `rewatch legacy`. Arguments to rewatch need to be passed after the subcommand. Argument `--compiler-args` is now a subcommand `compiler-args`. https://github.com/rescript-lang/rescript/pull/7551
 
 #### :bug: Bug fix