From f49037fe124f1d187d1d586655cb17fd928464d5 Mon Sep 17 00:00:00 2001
From: Paul Buschmann
Date: Sat, 26 Jul 2025 15:49:50 +0200
Subject: [PATCH 1/2] refactor: use PathBuf for folder arg
---
rewatch/src/cli.rs | 10 +++++++---
rewatch/src/lock.rs | 4 ++--
rewatch/src/main.rs | 11 +++--------
rewatch/src/watcher.rs | 8 +++-----
4 files changed, 15 insertions(+), 18 deletions(-)
diff --git a/rewatch/src/cli.rs b/rewatch/src/cli.rs
index 5a66fe0df1..68fa44a0a9 100644
--- a/rewatch/src/cli.rs
+++ b/rewatch/src/cli.rs
@@ -1,4 +1,8 @@
-use std::{ffi::OsString, ops::Deref};
+use std::{
+ ffi::OsString,
+ ops::Deref,
+ path::{Path, PathBuf},
+};
use clap::{Args, Parser, Subcommand};
use clap_verbosity_flag::InfoLevel;
@@ -45,7 +49,7 @@ pub struct Cli {
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,
+ pub folder: PathBuf,
}
#[derive(Args, Debug, Clone)]
@@ -219,7 +223,7 @@ pub enum Command {
}
impl Deref for FolderArg {
- type Target = str;
+ type Target = Path;
fn deref(&self) -> &Self::Target {
&self.folder
diff --git a/rewatch/src/lock.rs b/rewatch/src/lock.rs
index e12306e96f..0af693d256 100644
--- a/rewatch/src/lock.rs
+++ b/rewatch/src/lock.rs
@@ -59,8 +59,8 @@ fn create(lockfile_location: &Path, pid: u32) -> Lock {
.unwrap_or_else(|e| Lock::Error(Error::WritingLockfile(e)))
}
-pub fn get(folder: &str) -> Lock {
- let location = Path::new(folder).join("lib").join(LOCKFILE);
+pub fn get(folder: &Path) -> Lock {
+ let location = folder.join("lib").join(LOCKFILE);
let pid = process::id();
match fs::read_to_string(&location) {
diff --git a/rewatch/src/main.rs b/rewatch/src/main.rs
index 5fc08d4250..4d05ca5344 100644
--- a/rewatch/src/main.rs
+++ b/rewatch/src/main.rs
@@ -39,7 +39,7 @@ fn main() -> Result<()> {
match build::build(
&build_args.filter,
- Path::new(&build_args.folder as &str),
+ &build_args.folder,
show_progress,
build_args.no_timing,
*build_args.create_sourcedirs,
@@ -80,12 +80,7 @@ fn main() -> Result<()> {
} => {
let _lock = get_lock(&folder);
- build::clean::clean(
- Path::new(&folder as &str),
- show_progress,
- *snapshot_output,
- dev.dev,
- )
+ build::clean::clean(&folder, show_progress, *snapshot_output, dev.dev)
}
cli::Command::Legacy { legacy_args } => {
let code = build::pass_through_legacy(legacy_args);
@@ -100,7 +95,7 @@ fn main() -> Result<()> {
}
}
-fn get_lock(folder: &str) -> lock::Lock {
+fn get_lock(folder: &Path) -> lock::Lock {
match lock::get(folder) {
lock::Lock::Error(error) => {
println!("Could not start ReScript build: {error}");
diff --git a/rewatch/src/watcher.rs b/rewatch/src/watcher.rs
index 09b1c6bc15..0039d89abc 100644
--- a/rewatch/src/watcher.rs
+++ b/rewatch/src/watcher.rs
@@ -311,7 +311,7 @@ async fn async_watch(
pub fn start(
filter: &Option,
show_progress: bool,
- folder: &str,
+ folder: &Path,
after_build: Option,
create_sourcedirs: bool,
build_dev_deps: bool,
@@ -325,17 +325,15 @@ pub fn start(
let mut watcher = RecommendedWatcher::new(move |res| producer.push(res), Config::default())
.expect("Could not create watcher");
- log::debug!("watching {folder}");
+ log::debug!("watching {}", folder.display());
watcher
.watch(folder.as_ref(), RecursiveMode::Recursive)
.expect("Could not start watcher");
- let path = Path::new(folder);
-
if let Err(e) = async_watch(AsyncWatchArgs {
q: consumer,
- path,
+ path: folder,
show_progress,
filter,
after_build,
From db4344e0a7dd72cbc6de661edd053f11db197775 Mon Sep 17 00:00:00 2001
From: Paul Buschmann
Date: Sat, 26 Jul 2025 17:05:39 +0200
Subject: [PATCH 2/2] feat: clean on build_metadata change
---
rewatch/src/build_metadata.rs | 68 +++++++++++++++++++++++++++++++++++
rewatch/src/config.rs | 20 +++++------
rewatch/src/lib.rs | 1 +
rewatch/src/main.rs | 64 +++++++++++++++++++++++++++++----
rewatch/src/watcher.rs | 7 ++--
5 files changed, 139 insertions(+), 21 deletions(-)
create mode 100644 rewatch/src/build_metadata.rs
diff --git a/rewatch/src/build_metadata.rs b/rewatch/src/build_metadata.rs
new file mode 100644
index 0000000000..a5fb3e6896
--- /dev/null
+++ b/rewatch/src/build_metadata.rs
@@ -0,0 +1,68 @@
+use std::{
+ fs,
+ hash::{DefaultHasher, Hash, Hasher},
+ path::Path,
+};
+
+use crate::{config::Config, helpers, project_context::ProjectContext};
+
+pub struct BuildMetadata {
+ pub version: String,
+ pub config_hash: String,
+}
+
+impl BuildMetadata {
+ pub fn new(version: &str, config: &Config) -> Self {
+ let mut default_hasher = DefaultHasher::new();
+ config.hash(&mut default_hasher);
+ let config_hash = default_hasher.finish().to_string();
+ Self {
+ version: version.to_string(),
+ config_hash,
+ }
+ }
+}
+
+const BUILD_METADATA_FILE_NAME: &str = ".build-metadata";
+
+pub fn read_build_metadata(path: &Path) -> Option {
+ let file_path = path.join(BUILD_METADATA_FILE_NAME);
+ let file_content = fs::read_to_string(file_path).ok()?;
+ let file_content = file_content.trim();
+
+ let mut lines = file_content.lines();
+ let version = lines.next()?.to_owned();
+ let config_hash = lines.next()?.to_owned();
+
+ Some(BuildMetadata { version, config_hash })
+}
+
+pub fn current_build_metadata(path: &Path, version: &str) -> BuildMetadata {
+ let config = helpers::get_nearest_config(&path).expect("Couldn't find package root");
+ let project_context = ProjectContext::new(&config).expect("Couldn't create project context");
+
+ BuildMetadata::new(version, project_context.get_root_config())
+}
+
+pub fn is_build_metadata_different(
+ last_build_metadata: Option<&BuildMetadata>,
+ current_build_metadata: &BuildMetadata,
+) -> bool {
+ match last_build_metadata {
+ Some(last_metadata) => {
+ last_metadata.version != current_build_metadata.version
+ || last_metadata.config_hash != current_build_metadata.config_hash
+ }
+ None => true,
+ }
+}
+
+pub fn write_build_metadata(path: &Path, build_metadata: &BuildMetadata) {
+ let file_path = path.join(BUILD_METADATA_FILE_NAME);
+ let mut file_content = String::from("");
+
+ file_content.push_str(&format!("{}\n", build_metadata.version));
+ file_content.push_str(&format!("{}\n", build_metadata.config_hash));
+
+ fs::write(file_path, file_content).unwrap();
+}
diff --git a/rewatch/src/config.rs b/rewatch/src/config.rs
index 3435859e4b..b3555de2a6 100644
--- a/rewatch/src/config.rs
+++ b/rewatch/src/config.rs
@@ -6,7 +6,7 @@ use serde::Deserialize;
use std::fs;
use std::path::{Path, PathBuf};
-#[derive(Deserialize, Debug, Clone)]
+#[derive(Deserialize, Debug, Clone, Hash)]
#[serde(untagged)]
pub enum OneOrMore {
Multiple(Vec),
@@ -141,7 +141,7 @@ impl Source {
impl Eq for Source {}
-#[derive(Deserialize, Debug, Clone)]
+#[derive(Deserialize, Debug, Clone, Hash)]
pub struct PackageSpec {
pub module: String,
#[serde(rename = "in-source", default = "default_true")]
@@ -167,34 +167,34 @@ impl PackageSpec {
}
}
-#[derive(Deserialize, Debug, Clone)]
+#[derive(Deserialize, Debug, Clone, Hash)]
#[serde(untagged)]
pub enum Error {
Catchall(bool),
Qualified(String),
}
-#[derive(Deserialize, Debug, Clone)]
+#[derive(Deserialize, Debug, Clone, Hash)]
pub struct Warnings {
pub number: Option,
pub error: Option,
}
-#[derive(Deserialize, Debug, Clone)]
+#[derive(Deserialize, Debug, Clone, Hash)]
#[serde(untagged)]
pub enum NamespaceConfig {
Bool(bool),
String(String),
}
-#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
+#[derive(Deserialize, Debug, Clone, Eq, PartialEq, Hash)]
#[serde(rename_all = "camelCase")]
pub enum JsxMode {
Classic,
Automatic,
}
-#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
+#[derive(Deserialize, Debug, Clone, Eq, PartialEq, Hash)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum JsxModule {
@@ -202,7 +202,7 @@ pub enum JsxModule {
Other(String),
}
-#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
+#[derive(Deserialize, Debug, Clone, Eq, PartialEq, Hash)]
pub struct JsxSpecs {
pub version: Option,
pub module: Option,
@@ -215,7 +215,7 @@ pub struct JsxSpecs {
/// We do not care about the internal structure because the gentype config is loaded by bsc.
pub type GenTypeConfig = serde_json::Value;
-#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Hash)]
pub enum DeprecationWarning {
BsDependencies,
BsDevDependencies,
@@ -224,7 +224,7 @@ pub enum DeprecationWarning {
/// # rescript.json representation
/// This is tricky, there is a lot of ambiguity. This is probably incomplete.
-#[derive(Deserialize, Debug, Clone, Default)]
+#[derive(Deserialize, Debug, Clone, Hash, Default)]
pub struct Config {
pub name: String,
// In the case of monorepos, the root source won't necessarily have to have sources. It can
diff --git a/rewatch/src/lib.rs b/rewatch/src/lib.rs
index a389e8172e..734a50e246 100644
--- a/rewatch/src/lib.rs
+++ b/rewatch/src/lib.rs
@@ -1,4 +1,5 @@
pub mod build;
+pub mod build_metadata;
pub mod cli;
pub mod cmd;
pub mod config;
diff --git a/rewatch/src/main.rs b/rewatch/src/main.rs
index 4d05ca5344..cb0b1c30e4 100644
--- a/rewatch/src/main.rs
+++ b/rewatch/src/main.rs
@@ -1,11 +1,13 @@
use anyhow::Result;
-use clap::Parser;
+use clap::{CommandFactory, Parser};
use log::LevelFilter;
use std::{io::Write, path::Path};
-use rescript::{build, cli, cmd, format, lock, watcher};
+use rescript::{build, build_metadata, cli, cmd, format, lock, watcher};
fn main() -> Result<()> {
+ let cli_as_command = cli::Cli::command();
+ let version = cli_as_command.get_version().expect("version is set");
let args = cli::Cli::parse();
let log_level_filter = args.verbose.log_level_filter();
@@ -37,6 +39,21 @@ fn main() -> Result<()> {
cli::Command::Build(build_args) => {
let _lock = get_lock(&build_args.folder);
+ if is_clean_build_needed(&build_args.folder, version) {
+ match build::clean::clean(
+ &build_args.folder,
+ show_progress,
+ *build_args.snapshot_output,
+ *build_args.dev,
+ ) {
+ Ok(_) => {}
+ Err(e) => {
+ println!("{e}");
+ std::process::exit(1);
+ }
+ }
+ }
+
match build::build(
&build_args.filter,
&build_args.folder,
@@ -54,6 +71,10 @@ fn main() -> Result<()> {
if let Some(args_after_build) = (*build_args.after_build).clone() {
cmd::run(args_after_build)
}
+ build_metadata::write_build_metadata(
+ &build_args.folder.join("lib"),
+ &build_metadata::current_build_metadata(&build_args.folder, version),
+ );
std::process::exit(0)
}
};
@@ -61,7 +82,22 @@ fn main() -> Result<()> {
cli::Command::Watch(watch_args) => {
let _lock = get_lock(&watch_args.folder);
- watcher::start(
+ if is_clean_build_needed(&watch_args.folder, version) {
+ match build::clean::clean(
+ &watch_args.folder,
+ show_progress,
+ *watch_args.snapshot_output,
+ *watch_args.dev,
+ ) {
+ Ok(_) => {}
+ Err(e) => {
+ println!("{e}");
+ std::process::exit(1);
+ }
+ }
+ }
+
+ match watcher::start(
&watch_args.filter,
show_progress,
&watch_args.folder,
@@ -69,9 +105,19 @@ fn main() -> Result<()> {
*watch_args.create_sourcedirs,
*watch_args.dev,
*watch_args.snapshot_output,
- );
-
- Ok(())
+ ) {
+ Ok(_) => {
+ build_metadata::write_build_metadata(
+ &watch_args.folder.join("lib"),
+ &build_metadata::current_build_metadata(&watch_args.folder, version),
+ );
+ std::process::exit(0)
+ }
+ Err(e) => {
+ println!("{e:?}");
+ std::process::exit(1)
+ }
+ }
}
cli::Command::Clean {
folder,
@@ -95,6 +141,12 @@ fn main() -> Result<()> {
}
}
+fn is_clean_build_needed(path: &Path, version: &str) -> bool {
+ let last_build_metadata = build_metadata::read_build_metadata(path);
+ let current_build_metadata = build_metadata::current_build_metadata(path, version);
+ build_metadata::is_build_metadata_different(last_build_metadata.as_ref(), ¤t_build_metadata)
+}
+
fn get_lock(folder: &Path) -> lock::Lock {
match lock::get(folder) {
lock::Lock::Error(error) => {
diff --git a/rewatch/src/watcher.rs b/rewatch/src/watcher.rs
index 0039d89abc..c72df01037 100644
--- a/rewatch/src/watcher.rs
+++ b/rewatch/src/watcher.rs
@@ -316,7 +316,7 @@ pub fn start(
create_sourcedirs: bool,
build_dev_deps: bool,
snapshot_output: bool,
-) {
+) -> Result<(), Error> {
futures::executor::block_on(async {
let queue = Arc::new(FifoQueue::>::new());
let producer = queue.clone();
@@ -331,7 +331,7 @@ pub fn start(
.watch(folder.as_ref(), RecursiveMode::Recursive)
.expect("Could not start watcher");
- if let Err(e) = async_watch(AsyncWatchArgs {
+ async_watch(AsyncWatchArgs {
q: consumer,
path: folder,
show_progress,
@@ -342,8 +342,5 @@ pub fn start(
snapshot_output,
})
.await
- {
- println!("{e:?}")
- }
})
}