From 38594a8160922d834e8ec08555ad787e9c03c0f3 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Tue, 17 Mar 2026 15:42:54 +0200 Subject: [PATCH 1/4] Add --prod flag to rewatch build, watch, and clean commands Skip dev-dependencies and dev source folders (type: "dev") when --prod is passed, enabling production builds without dev tooling installed. Signed-Off-By: Jono Prest --- rewatch/src/build.rs | 5 ++++- rewatch/src/build/clean.rs | 4 ++-- rewatch/src/build/packages.rs | 17 +++++++++++------ rewatch/src/cli.rs | 13 +++++++++++++ rewatch/src/format.rs | 2 +- rewatch/src/main.rs | 6 ++++-- rewatch/src/watcher.rs | 6 ++++++ 7 files changed, 41 insertions(+), 12 deletions(-) diff --git a/rewatch/src/build.rs b/rewatch/src/build.rs index f85a911e12a..88394eddda2 100644 --- a/rewatch/src/build.rs +++ b/rewatch/src/build.rs @@ -134,12 +134,13 @@ pub fn initialize_build( path: &Path, plain_output: bool, warn_error: Option, + prod: bool, ) -> Result { let project_context = ProjectContext::new(path)?; let compiler = get_compiler_info(&project_context)?; let timing_clean_start = Instant::now(); - let packages = packages::make(filter, &project_context, show_progress)?; + let packages = packages::make(filter, &project_context, show_progress, prod)?; let compiler_check = verify_compiler_info(&packages, &compiler); @@ -489,6 +490,7 @@ pub fn build( create_sourcedirs: bool, plain_output: bool, warn_error: Option, + prod: bool, ) -> Result { let default_timing: Option = if no_timing { Some(std::time::Duration::new(0.0 as u64, 0.0 as u32)) @@ -503,6 +505,7 @@ pub fn build( path, plain_output, warn_error, + prod, ) .with_context(|| "Could not initialize build")?; diff --git a/rewatch/src/build/clean.rs b/rewatch/src/build/clean.rs index f114dca46c9..73e77987dd7 100644 --- a/rewatch/src/build/clean.rs +++ b/rewatch/src/build/clean.rs @@ -332,10 +332,10 @@ pub fn cleanup_after_build(build_state: &BuildCommandState) { }); } -pub fn clean(path: &Path, show_progress: bool, plain_output: bool) -> Result<()> { +pub fn clean(path: &Path, show_progress: bool, plain_output: bool, prod: bool) -> Result<()> { let project_context = ProjectContext::new(path)?; let compiler_info = build::get_compiler_info(&project_context)?; - let packages = packages::make(&None, &project_context, show_progress)?; + let packages = packages::make(&None, &project_context, show_progress, prod)?; let timing_clean_compiler_assets = Instant::now(); if !plain_output && show_progress { diff --git a/rewatch/src/build/packages.rs b/rewatch/src/build/packages.rs index c1f22d8ed3a..159797a62bb 100644 --- a/rewatch/src/build/packages.rs +++ b/rewatch/src/build/packages.rs @@ -285,11 +285,12 @@ fn read_dependencies( package_config: &Config, show_progress: bool, is_local_dep: bool, + prod: bool, ) -> Vec { let mut dependencies = package_config.dependencies.to_owned().unwrap_or_default(); - // Concatenate dev dependencies if is_local_dep is true - if is_local_dep && let Some(dev_deps) = package_config.dev_dependencies.to_owned() { + // Concatenate dev dependencies if is_local_dep is true and not in prod mode + if is_local_dep && !prod && let Some(dev_deps) = package_config.dev_dependencies.to_owned() { dependencies.extend(dev_deps); } @@ -395,6 +396,7 @@ fn read_dependencies( &config, show_progress, is_local_dep, + prod, ); Dependency { @@ -509,7 +511,7 @@ This inconsistency will cause issues with package resolution.\n", }) } -fn read_packages(project_context: &ProjectContext, show_progress: bool) -> Result> { +fn read_packages(project_context: &ProjectContext, show_progress: bool, prod: bool) -> Result> { // Store all packages and completely deduplicate them let mut map: AHashMap = AHashMap::new(); @@ -531,6 +533,7 @@ fn read_packages(project_context: &ProjectContext, show_progress: bool) -> Resul &project_context.current_config, show_progress, /* is local dep */ true, + prod, )); for d in dependencies.iter() { @@ -594,6 +597,7 @@ pub fn get_source_files( fn extend_with_children( filter: &Option, mut build: AHashMap, + prod: bool, ) -> AHashMap { for (_key, package) in build.iter_mut() { let mut map: AHashMap = AHashMap::new(); @@ -606,7 +610,7 @@ fn extend_with_children( Path::new(&package.path), filter, source, - package.is_local_dep, + package.is_local_dep && !prod, ) }) .collect::>>() @@ -649,12 +653,13 @@ pub fn make( filter: &Option, project_context: &ProjectContext, show_progress: bool, + prod: bool, ) -> Result> { - let map = read_packages(project_context, show_progress)?; + let map = read_packages(project_context, show_progress, prod)?; /* Once we have the deduplicated packages, we can add the source files for each - to minimize * the IO */ - let result = extend_with_children(filter, map); + let result = extend_with_children(filter, map, prod); Ok(result) } diff --git a/rewatch/src/cli.rs b/rewatch/src/cli.rs index 2de59bce94e..96c1c2dd521 100644 --- a/rewatch/src/cli.rs +++ b/rewatch/src/cli.rs @@ -219,6 +219,10 @@ pub struct BuildArgs { /// Disable output timing #[arg(short, long, default_value_t = false, num_args = 0..=1)] pub no_timing: bool, + + /// Skip dev-dependencies and dev sources (type: "dev") + #[arg(long, default_value_t = false)] + pub prod: bool, } #[cfg(test)] @@ -372,6 +376,10 @@ pub struct WatchArgs { #[command(flatten)] pub warn_error: WarnErrorArg, + + /// Skip dev-dependencies and dev sources (type: "dev") + #[arg(long, default_value_t = false)] + pub prod: bool, } impl From for WatchArgs { @@ -381,6 +389,7 @@ impl From for WatchArgs { filter: build_args.filter, after_build: build_args.after_build, warn_error: build_args.warn_error, + prod: build_args.prod, } } } @@ -395,6 +404,10 @@ pub enum Command { Clean { #[command(flatten)] folder: FolderArg, + + /// Skip dev-dependencies and dev sources (type: "dev") + #[arg(long, default_value_t = false)] + prod: bool, }, /// Format ReScript files. Format { diff --git a/rewatch/src/format.rs b/rewatch/src/format.rs index 16ed4eadb35..c4ea876e451 100644 --- a/rewatch/src/format.rs +++ b/rewatch/src/format.rs @@ -36,7 +36,7 @@ fn get_files_in_scope() -> Result> { let current_dir = std::env::current_dir()?; let project_context = project_context::ProjectContext::new(¤t_dir)?; - let packages = packages::make(&None, &project_context, false)?; + let packages = packages::make(&None, &project_context, false, false)?; let mut files: Vec = Vec::new(); let packages_to_format = project_context.get_scoped_local_packages(); diff --git a/rewatch/src/main.rs b/rewatch/src/main.rs index 10be746d04a..9d3a2354355 100644 --- a/rewatch/src/main.rs +++ b/rewatch/src/main.rs @@ -54,6 +54,7 @@ fn main() -> Result<()> { true, // create_sourcedirs is now always enabled plain_output, (*build_args.warn_error).clone(), + build_args.prod, ) { Err(e) => { eprintln!("{:#}", e); @@ -78,6 +79,7 @@ fn main() -> Result<()> { true, // create_sourcedirs is now always enabled plain_output, (*watch_args.warn_error).clone(), + watch_args.prod, ) { Err(e) => { eprintln!("{:#}", e); @@ -86,9 +88,9 @@ fn main() -> Result<()> { Ok(_) => Ok(()), } } - cli::Command::Clean { folder } => { + cli::Command::Clean { folder, prod } => { let _lock = get_lock_or_exit(LockKind::Build, &folder); - let result = build::clean::clean(Path::new(&folder as &str), show_progress, plain_output); + let result = build::clean::clean(Path::new(&folder as &str), show_progress, plain_output, prod); let _lock = drop_lock(LockKind::Build, &folder); result diff --git a/rewatch/src/watcher.rs b/rewatch/src/watcher.rs index 8a37cdaf618..efcf39767eb 100644 --- a/rewatch/src/watcher.rs +++ b/rewatch/src/watcher.rs @@ -151,6 +151,7 @@ struct AsyncWatchArgs<'a> { after_build: Option, create_sourcedirs: bool, plain_output: bool, + prod: bool, } async fn async_watch( @@ -165,6 +166,7 @@ async fn async_watch( after_build, create_sourcedirs, plain_output, + prod, }: AsyncWatchArgs<'_>, ) -> Result<()> { let mut build_state = initial_build_state; @@ -381,6 +383,7 @@ async fn async_watch( path, plain_output, build_state.get_warn_error_override(), + prod, ) .expect("Could not initialize build"); @@ -434,6 +437,7 @@ pub fn start( create_sourcedirs: bool, plain_output: bool, warn_error: Option, + prod: bool, ) -> Result<()> { futures::executor::block_on(async { let queue = Arc::new(FifoQueue::>::new()); @@ -453,6 +457,7 @@ pub fn start( path, plain_output, warn_error.clone(), + prod, ) .with_context(|| "Could not initialize build")?; @@ -471,6 +476,7 @@ pub fn start( after_build, create_sourcedirs, plain_output, + prod, }) .await }) From 4e27541342169849556302414e5e1ec7705dcb53 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 17 Apr 2026 09:15:43 +0200 Subject: [PATCH 2/4] Add tests --- rewatch/src/cli.rs | 51 +++++++++++++++ rewatch/tests/compile/17-prod-flag.sh | 94 +++++++++++++++++++++++++++ rewatch/tests/suite.sh | 1 + 3 files changed, 146 insertions(+) create mode 100755 rewatch/tests/compile/17-prod-flag.sh diff --git a/rewatch/src/cli.rs b/rewatch/src/cli.rs index 96c1c2dd521..07c3cdfb9bb 100644 --- a/rewatch/src/cli.rs +++ b/rewatch/src/cli.rs @@ -352,6 +352,57 @@ mod tests { assert_eq!(err.kind(), ErrorKind::DisplayVersion); } + // --prod flag tests. + #[test] + fn build_prod_flag_is_parsed() { + let cli = parse(&["rescript", "build", "--prod"]).expect("expected build command"); + + match cli.command { + Command::Build(build_args) => assert!(build_args.prod), + other => panic!("expected build command, got {other:?}"), + } + } + + #[test] + fn build_prod_flag_defaults_to_false() { + let cli = parse(&["rescript", "build"]).expect("expected build command"); + + match cli.command { + Command::Build(build_args) => assert!(!build_args.prod), + other => panic!("expected build command, got {other:?}"), + } + } + + #[test] + fn watch_prod_flag_is_parsed() { + let cli = parse(&["rescript", "watch", "--prod"]).expect("expected watch command"); + + match cli.command { + Command::Watch(watch_args) => assert!(watch_args.prod), + other => panic!("expected watch command, got {other:?}"), + } + } + + #[test] + fn clean_prod_flag_is_parsed() { + let cli = parse(&["rescript", "clean", "--prod"]).expect("expected clean command"); + + match cli.command { + Command::Clean { prod, .. } => assert!(prod), + other => panic!("expected clean command, got {other:?}"), + } + } + + #[test] + fn prod_flag_defaults_to_build_command() { + let cli = parse(&["rescript", "--prod"]).expect("expected default build command"); + + match cli.command { + Command::Build(build_args) => assert!(build_args.prod), + other => panic!("expected build command, got {other:?}"), + } + } + #[cfg(unix)] #[test] fn non_utf_argument_returns_error() { diff --git a/rewatch/tests/compile/17-prod-flag.sh b/rewatch/tests/compile/17-prod-flag.sh new file mode 100755 index 00000000000..c3b5572deaa --- /dev/null +++ b/rewatch/tests/compile/17-prod-flag.sh @@ -0,0 +1,94 @@ +#!/bin/bash +cd $(dirname $0) +source "../utils.sh" +cd ../../testrepo + +bold "Test: --prod flag skips dev-dependencies and dev sources" + +rewatch clean &> /dev/null + +# Test 1: A nonexistent dev-dependency should cause a normal build to fail +# but succeed with --prod +replace "s|@testrepo/pure-dev\"]|@testrepo/pure-dev\",\"@testrepo/nonexistent-pkg\"]|" rescript.json + +rewatch build &> /dev/null +if [ $? -ne 0 ]; +then + success "Build without --prod correctly failed with nonexistent dev-dependency" +else + error "Build without --prod should have failed with nonexistent dev-dependency" + git checkout -- rescript.json + exit 1 +fi + +rewatch build --prod &> /dev/null +if [ $? -eq 0 ]; +then + success "Build with --prod succeeded despite nonexistent dev-dependency" +else + error "Build with --prod should have succeeded by skipping dev-dependencies" + git checkout -- rescript.json + exit 1 +fi + +rewatch clean &> /dev/null +git checkout -- rescript.json + +# Test 2: --prod should skip dev source files +rewatch clean &> /dev/null +rewatch build --prod &> /dev/null +if [ $? -ne 0 ]; +then + error "Build with --prod failed" + exit 1 +fi + +# Dev sources in with-dev-deps/test/ should NOT be compiled +file_count=$(find ./packages/with-dev-deps/test -name "*.mjs" 2>/dev/null | wc -l | tr -d ' ') +if [ "$file_count" -eq 0 ]; +then + success "--prod skipped dev sources in with-dev-deps/test" +else + error "Expected 0 compiled dev source files in with-dev-deps/test, found $file_count" + exit 1 +fi + +# Dev sources in pure-dev/dev/ should NOT be compiled +file_count=$(find ./packages/pure-dev/dev -name "*.mjs" 2>/dev/null | wc -l | tr -d ' ') +if [ "$file_count" -eq 0 ]; +then + success "--prod skipped dev sources in pure-dev/dev" +else + error "Expected 0 compiled dev source files in pure-dev/dev, found $file_count" + exit 1 +fi + +rewatch clean &> /dev/null + +# Test 3: Without --prod, dev sources should be compiled +rewatch build &> /dev/null +if [ $? -ne 0 ]; +then + error "Build without --prod failed" + exit 1 +fi + +file_count=$(find ./packages/with-dev-deps/test -name "*.mjs" 2>/dev/null | wc -l | tr -d ' ') +if [ "$file_count" -eq 1 ]; +then + success "Without --prod, dev sources in with-dev-deps/test are compiled" +else + error "Expected 1 compiled dev source file in with-dev-deps/test, found $file_count" + exit 1 +fi + +file_count=$(find ./packages/pure-dev/dev -name "*.mjs" 2>/dev/null | wc -l | tr -d ' ') +if [ "$file_count" -eq 1 ]; +then + success "Without --prod, dev sources in pure-dev/dev are compiled" +else + error "Expected 1 compiled dev source file in pure-dev/dev, found $file_count" + exit 1 +fi + +rewatch clean &> /dev/null diff --git a/rewatch/tests/suite.sh b/rewatch/tests/suite.sh index 14f9330b885..08d49a2a5d5 100755 --- a/rewatch/tests/suite.sh +++ b/rewatch/tests/suite.sh @@ -79,6 +79,7 @@ fi ./compile/11-dev-dependency-non-dev-source.sh && ./compile/12-compile-dev-dependencies.sh && ./compile/13-no-infinite-loop-with-cycle.sh && +./compile/17-prod-flag.sh && ./compile/14-no-testrepo-changes.sh && ./compile/15-no-new-files.sh && ./compile/16-snapshots-unchanged.sh && From 5893670c9746af870bdcf9c66bfddeb12388b798 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 17 Apr 2026 15:13:07 +0200 Subject: [PATCH 3/4] Run cargo fmt --- rewatch/src/build/packages.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rewatch/src/build/packages.rs b/rewatch/src/build/packages.rs index 159797a62bb..a936b415f65 100644 --- a/rewatch/src/build/packages.rs +++ b/rewatch/src/build/packages.rs @@ -290,7 +290,10 @@ fn read_dependencies( let mut dependencies = package_config.dependencies.to_owned().unwrap_or_default(); // Concatenate dev dependencies if is_local_dep is true and not in prod mode - if is_local_dep && !prod && let Some(dev_deps) = package_config.dev_dependencies.to_owned() { + if is_local_dep + && !prod + && let Some(dev_deps) = package_config.dev_dependencies.to_owned() + { dependencies.extend(dev_deps); } @@ -511,7 +514,11 @@ This inconsistency will cause issues with package resolution.\n", }) } -fn read_packages(project_context: &ProjectContext, show_progress: bool, prod: bool) -> Result> { +fn read_packages( + project_context: &ProjectContext, + show_progress: bool, + prod: bool, +) -> Result> { // Store all packages and completely deduplicate them let mut map: AHashMap = AHashMap::new(); From 43caff096f6ffcf9065b06ae203cb84caaedfa62 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 17 Apr 2026 15:54:20 +0200 Subject: [PATCH 4/4] Update cli help test --- tests/build_tests/cli_help/input.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/build_tests/cli_help/input.js b/tests/build_tests/cli_help/input.js index 5a1dd513daa..88e2ac8a0ad 100755 --- a/tests/build_tests/cli_help/input.js +++ b/tests/build_tests/cli_help/input.js @@ -45,6 +45,7 @@ const buildHelp = " -q, --quiet... Decrease logging verbosity\n" + ' --warn-error Override warning configuration from rescript.json. Example: --warn-error "+3+8+11+12+26+27+31+32+33+34+35+39+44+45+110"\n' + " -n, --no-timing [] Disable output timing [default: false] [possible values: true, false]\n" + + ' --prod Skip dev-dependencies and dev sources (type: "dev")\n' + " -h, --help Print help\n"; const cleanHelp = @@ -56,6 +57,7 @@ const cleanHelp = " [FOLDER] Path to the project or subproject. This folder must contain a rescript.json file [default: .]\n" + "\n" + "Options:\n" + + ' --prod Skip dev-dependencies and dev sources (type: "dev")\n' + " -v, --verbose... Increase logging verbosity\n" + " -q, --quiet... Decrease logging verbosity\n" + " -h, --help Print help\n";