Skip to content

Rewatch cli refactor #7551

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Jun 30, 2025
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
* text=auto eol=lf

*.ml linguist-language=OCaml
*.mli linguist-language=OCaml
*.res linguist-language=ReScript
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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. Argument `--compiler-args` is now a subcommand `compiler-args`. 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
33 changes: 30 additions & 3 deletions cli/rewatch.js
Original file line number Diff line number Diff line change
@@ -7,10 +7,37 @@ import { rewatch_exe, bsc_exe } from "./common/bins.js";

const args = process.argv.slice(2);

const firstPositionalArgIndex = args.findIndex((arg) => !arg.startsWith("-"));

try {
child_process.execFileSync(rewatch_exe, [...args, "--bsc-path", bsc_exe], {
stdio: "inherit",
});
if (firstPositionalArgIndex !== -1) {
const subcommand = args[firstPositionalArgIndex];
const subcommandWithArgs = args.slice(firstPositionalArgIndex);

if (
subcommand === "build" ||
subcommand === "watch" ||
subcommand === "clean" ||
subcommand === "compiler-args"
) {
child_process.execFileSync(
rewatch_exe,
[...subcommandWithArgs, "--bsc-path", bsc_exe],
{
stdio: "inherit",
}
);
} else {
child_process.execFileSync(rewatch_exe, [...args], {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this branch? can we not just pass --bsc-path in both cases?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bsc-path is not a root level arg anymore

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be an option to use an env var BSC_PATH instead to avoid this problem?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this idea. Either that, or we promote --bsc-path to a root level argument again. If we decide for the env var would this be in addition to the arg?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was that it would replace the arg. What's your opinion @jfrolich?

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) {
process.exit(err.status); // Pass through the exit code
30 changes: 27 additions & 3 deletions rewatch/src/build.rs
Original file line number Diff line number Diff line change
@@ -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;
@@ -57,7 +59,7 @@ pub struct CompilerArgs {
pub fn get_compiler_args(
path: &Path,
rescript_version: Option<String>,
bsc_path: &Option<PathBuf>,
bsc_path: Option<PathBuf>,
build_dev_deps: bool,
) -> Result<String> {
let filename = &helpers::get_abs_path(path);
@@ -499,7 +501,7 @@ pub fn build(
show_progress: bool,
no_timing: bool,
create_sourcedirs: bool,
bsc_path: &Option<PathBuf>,
bsc_path: Option<PathBuf>,
build_dev_deps: bool,
snapshot_output: bool,
) -> Result<BuildState> {
@@ -514,7 +516,7 @@ pub fn build(
filter,
show_progress,
path,
bsc_path,
&bsc_path,
build_dev_deps,
snapshot_output,
)
@@ -551,3 +553,25 @@ pub fn build(
}
}
}

pub fn pass_through_legacy(mut args: Vec<OsString>) -> 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);

args.insert(0, rescript_legacy_path.into());
let status = std::process::Command::new("node")
.args(args)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status();

match status {
Ok(s) => s.code().unwrap_or(0),
Err(err) => {
eprintln!("Error running the legacy build system: {err}");
1
}
}
}
8 changes: 4 additions & 4 deletions rewatch/src/build/clean.rs
Original file line number Diff line number Diff line change
@@ -334,8 +334,7 @@ pub fn cleanup_after_build(build_state: &BuildState) {
pub fn clean(
path: &Path,
show_progress: bool,
bsc_path: &Option<PathBuf>,
build_dev_deps: bool,
bsc_path: Option<PathBuf>,
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 {
257 changes: 257 additions & 0 deletions rewatch/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
use std::{ffi::OsString, ops::Deref};

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<InfoLevel>,

/// The command to run. If not provided it will default to build.
#[command(subcommand)]
pub command: Option<Command>,

#[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,
}

#[derive(Args, Debug, Clone)]
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<String>,
}

#[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
/// 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<String>,
}

#[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
/// 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,
}

#[derive(Args, Debug, Clone)]
pub struct BscPathArg {
/// Custom path to bsc
#[arg(long)]
pub bsc_path: Option<String>,
}

#[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,
}

#[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 {
#[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,

#[command(flatten)]
pub snapshot_output: SnapshotOutputArg,

#[command(flatten)]
pub bsc_path: BscPathArg,
}

#[derive(Subcommand, Clone, Debug)]
pub enum Command {
/// Build using Rewatch
Build(BuildArgs),
/// Build, then start a watcher
Watch(WatchArgs),
/// Clean the build artifacts
Clean {
#[command(flatten)]
folder: FolderArg,

#[command(flatten)]
bsc_path: BscPathArg,

#[command(flatten)]
snapshot_output: SnapshotOutputArg,
},
/// Alias to `legacy format`.
#[command(disable_help_flag = true)]
Format {
#[arg(allow_hyphen_values = true, num_args = 0..)]
format_args: Vec<OsString>,
},
/// Alias to `legacy dump`.
#[command(disable_help_flag = true)]
Dump {
#[arg(allow_hyphen_values = true, num_args = 0..)]
dump_args: Vec<OsString>,
},
/// This prints the compiler arguments. It expects the path to a rescript.json file.
CompilerArgs {
/// Path to a rescript.json file
#[command()]
path: String,

#[command(flatten)]
dev: DevArg,

/// To be used in conjunction with compiler_args
#[arg(long)]
rescript_version: Option<String>,

#[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, external_subcommand = true)]
Legacy {
#[arg(allow_hyphen_values = true, num_args = 0..)]
legacy_args: Vec<OsString>,
},
}

impl Deref for FolderArg {
type Target = str;

fn deref(&self) -> &Self::Target {
&self.folder
}
}

impl Deref for FilterArg {
type Target = Option<String>;

fn deref(&self) -> &Self::Target {
&self.filter
}
}

impl Deref for AfterBuildArg {
type Target = Option<String>;

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<String>;

fn deref(&self) -> &Self::Target {
&self.bsc_path
}
}

impl Deref for SnapshotOutputArg {
type Target = bool;

fn deref(&self) -> &Self::Target {
&self.snapshot_output
}
}
46 changes: 36 additions & 10 deletions rewatch/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -181,32 +181,35 @@ 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>) -> PathBuf {
fn get_bin_dir() -> 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",
("windows", _) => "win32-x64",
_ => panic!("Unsupported architecture"),
};

Path::new("node_modules")
.join("@rescript")
.join(subfolder)
.join("bin")
}

pub fn get_bsc(root_path: &Path, workspace_root: &Option<PathBuf>) -> 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)
@@ -218,6 +221,29 @@ pub fn get_bsc(root_path: &Path, workspace_root: &Option<PathBuf>) -> PathBuf {
}
}

pub fn get_rescript_legacy(root_path: &Path, workspace_root: Option<PathBuf>) -> PathBuf {
let bin_dir = Path::new("node_modules").join("rescript").join("cli");

match (
root_path
.join(&bin_dir)
.join("rescript.js")
.canonicalize()
.map(StrippedVerbatimPath::to_stripped_verbatim_path),
workspace_root.map(|workspace_root| {
workspace_root
.join(&bin_dir)
.join("rescript.js")
.canonicalize()
.map(StrippedVerbatimPath::to_stripped_verbatim_path)
}),
) {
(Ok(path), _) => path,
(_, Some(Ok(path))) => path,
_ => panic!("Could not find rescript.exe"),
}
}

pub fn string_ends_with_any(s: &Path, suffixes: &[&str]) -> bool {
suffixes
.iter()
1 change: 1 addition & 0 deletions rewatch/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod build;
pub mod cli;
pub mod cmd;
pub mod config;
pub mod helpers;
253 changes: 106 additions & 147 deletions rewatch/src/main.rs
Original file line number Diff line number Diff line change
@@ -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<Command>,

/// The relative path to where the main rescript.json resides. IE - the root of your project.
folder: Option<String>,

/// 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<String>,

/// 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<String>,

// 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<InfoLevel>,

/// 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<String>,

/// 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<String>,

/// A custom path to bsc
#[arg(long)]
bsc_path: Option<String>,
}
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,117 @@ 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));

// 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 args.compiler_args {
None => (),
Some(path) => {
match command.clone() {
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.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(&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),
match build::build(
&filter,
Path::new(&build_args.folder as &str),
show_progress,
&args.bsc_path.map(PathBuf::from),
args.dev,
args.snapshot_output,
),
Command::Build => {
match build::build(
&filter,
Path::new(&folder),
show_progress,
args.no_timing,
args.create_sourcedirs,
&args.bsc_path.map(PathBuf::from),
args.dev,
args.snapshot_output,
) {
Err(e) => {
println!("{e}");
std::process::exit(1)
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)
}
Ok(_) => {
if let Some(args_after_build) = args.after_build {
cmd::run(args_after_build)
}
std::process::exit(0)
}
};
}
Command::Watch => {
watcher::start(
&filter,
show_progress,
&folder,
args.after_build,
args.create_sourcedirs,
args.dev,
args.bsc_path,
args.snapshot_output,
);
std::process::exit(0)
}
};
}
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,
&watch_args.folder,
(*watch_args.after_build).clone(),
*watch_args.create_sourcedirs,
*watch_args.dev,
(*watch_args.bsc_path).clone(),
*watch_args.snapshot_output,
);

Ok(())
}
},
Ok(())
}
cli::Command::Clean {
folder,
bsc_path,
snapshot_output,
} => {
let _lock = get_lock(&folder);

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,
}
}
6 changes: 4 additions & 2 deletions rewatch/testrepo/bsconfig.json
Original file line number Diff line number Diff line change
@@ -20,15 +20,17 @@
"@testrepo/dep02",
"@testrepo/new-namespace",
"@testrepo/namespace-casing",
"@testrepo/with-dev-deps"
"@testrepo/with-dev-deps",
"@testrepo/compiled-by-legacy"
],
"bs-dependencies": [
"@testrepo/main",
"@testrepo/dep01",
"@testrepo/dep02",
"@testrepo/new-namespace",
"@testrepo/namespace-casing",
"@testrepo/with-dev-deps"
"@testrepo/with-dev-deps",
"@testrepo/compiled-by-legacy"
],
"reason": {
"react-jsx": 3
3 changes: 2 additions & 1 deletion rewatch/testrepo/package.json
Original file line number Diff line number Diff line change
@@ -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": {
9 changes: 9 additions & 0 deletions rewatch/testrepo/packages/compiled-by-legacy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@testrepo/compiled-by-legacy",
"version": "0.0.1",
"keywords": [
"rescript"
],
"author": "",
"license": "MIT"
}
13 changes: 13 additions & 0 deletions rewatch/testrepo/packages/compiled-by-legacy/rescript.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "@testrepo/compiled-by-legacy",
"namespace": true,
"sources": {
"dir": "src",
"subdirs": true
},
"package-specs": {
"module": "esmodule",
"in-source": true
},
"suffix": ".res.js"
}
9 changes: 9 additions & 0 deletions rewatch/testrepo/packages/compiled-by-legacy/src/Main.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Generated by ReScript, PLEASE EDIT WITH CARE


let x = 1;

export {
x,
}
/* No side effect */
1 change: 1 addition & 0 deletions rewatch/testrepo/packages/compiled-by-legacy/src/Main.res
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let x = 1
9 changes: 9 additions & 0 deletions rewatch/testrepo/packages/compiled-by-legacy/src/Main.res.js
1 change: 1 addition & 0 deletions rewatch/testrepo/packages/main/package.json
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
"author": "",
"license": "MIT",
"dependencies": {
"@testrepo/compiled-by-legacy": "*",
"@testrepo/dep01": "*"
}
}
7 changes: 7 additions & 0 deletions rewatch/testrepo/yarn.lock
Original file line number Diff line number Diff line change
@@ -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
22 changes: 12 additions & 10 deletions rewatch/tests/compile.sh
Original file line number Diff line number Diff line change
@@ -33,48 +33,48 @@ 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 --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 --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 --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 --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 --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
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 --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
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 --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
@@ -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 &> /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

3 changes: 3 additions & 0 deletions rewatch/tests/legacy-utils.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source "utils.sh"

rewatch_legacy() { RUST_BACKTRACE=1 "../../$REWATCH_EXECUTABLE" legacy $@; }
52 changes: 52 additions & 0 deletions rewatch/tests/legacy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
source "./legacy-utils.sh"
cd ../testrepo/packages/compiled-by-legacy

bold "Test: It should use the legacy build system"

error_output=$(rewatch_legacy 2>&1 >/dev/null)
if [ $? -ne 0 ];
then
error "Error running rewatch legacy"
echo $error_output
exit 1
fi

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. File count was $file_count."
echo $error_output
exit 1
fi

error_output=$(rewatch_legacy build 2>&1 >/dev/null)
if [ $? -eq 0 ];
then
success "Test package built"
else
error "Error building test package"
echo $error_output
exit 1
fi

if git diff --exit-code ./;
then
success "Test package has no changes"
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"
echo $error_output
exit 1
fi
2 changes: 1 addition & 1 deletion rewatch/tests/snapshots/dependency-cycle.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Cleaned 0/14
Cleaned 0/15
Parsed 1 source files
Compiled 0 modules

2 changes: 1 addition & 1 deletion rewatch/tests/snapshots/remove-file.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Cleaned 1/14
Cleaned 1/15
Parsed 0 source files
Compiled 1 modules

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Cleaned 2/14
Cleaned 2/15
Parsed 2 source files
Compiled 3 modules

2 changes: 1 addition & 1 deletion rewatch/tests/snapshots/rename-file-internal-dep.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Cleaned 2/14
Cleaned 2/15
Parsed 2 source files
Compiled 2 modules

2 changes: 1 addition & 1 deletion rewatch/tests/snapshots/rename-file-with-interface.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
 No implementation file found for interface file (skipping): src/ModuleWithInterface.resi
Cleaned 2/14
Cleaned 2/15
Parsed 1 source files
Compiled 2 modules
2 changes: 1 addition & 1 deletion rewatch/tests/snapshots/rename-file.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Cleaned 1/14
Cleaned 1/15
Parsed 1 source files
Compiled 1 modules
2 changes: 1 addition & 1 deletion rewatch/tests/snapshots/rename-interface-file.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
 No implementation file found for interface file (skipping): src/ModuleWithInterface2.resi
Cleaned 1/14
Cleaned 1/15
Parsed 1 source files
Compiled 2 modules
2 changes: 1 addition & 1 deletion rewatch/tests/suffix.sh
Original file line number Diff line number Diff line change
@@ -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 26 ];
then
success "Found files with correct suffix"
else
7 changes: 5 additions & 2 deletions rewatch/tests/suite-ci.sh
Original file line number Diff line number Diff line change
@@ -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

@@ -37,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
4 changes: 2 additions & 2 deletions rewatch/tests/utils.sh
Original file line number Diff line number Diff line change
@@ -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 $@ $BSC_PATH; }
rewatch_bg() { RUST_BACKTRACE=1 nohup $REWATCH_EXECUTABLE $@ $BSC_PATH; }

# Detect if running on Windows
is_windows() {