Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
50 changes: 50 additions & 0 deletions rewatch/src/build/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,33 @@ use std::process::Command;
use std::sync::OnceLock;
use std::time::SystemTime;

/// Execute js-post-build command for a compiled JavaScript file.
/// Unlike bsb which passes relative paths, rewatch passes absolute paths for clarity.
fn execute_post_build_command(cmd: &str, js_file_path: &Path) -> Result<()> {
let full_command = format!("{} {}", cmd, js_file_path.display());

let output = if cfg!(target_os = "windows") {
Command::new("cmd").args(["/C", &full_command]).output()
} else {
Command::new("sh").args(["-c", &full_command]).output()
};

match output {
Ok(output) if !output.status.success() => {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
Err(anyhow!(
"js-post-build command failed for {}: {}{}",
js_file_path.display(),
stderr,
stdout
))
}
Err(e) => Err(anyhow!("Failed to execute js-post-build command: {}", e)),
Ok(_) => Ok(()),
}
}

pub fn compile(
build_state: &mut BuildCommandState,
show_progress: bool,
Expand Down Expand Up @@ -815,6 +842,29 @@ fn compile_file(
}
});

// Execute js-post-build command if configured
// Only run for implementation files (not interfaces)
if !is_interface
&& let Some(js_post_build) = &package.config.js_post_build
&& let SourceType::SourceFile(SourceFile {
implementation: Implementation { path, .. },
..
}) = &module.source_type
{
// Execute post-build command for each package spec (each output format)
for spec in root_config.get_package_specs() {
let js_file = helpers::get_source_file_from_rescript_file(
&package.get_build_path().join(path),
&root_config.get_suffix(&spec),
);

if js_file.exists() {
// Fail the build if post-build command fails (matches bsb behavior with &&)
execute_post_build_command(&js_post_build.cmd, &js_file)?;
}
}
}

if helpers::contains_ascii_characters(&err) {
if package.is_local_dep {
// suppress warnings of external deps
Expand Down
11 changes: 10 additions & 1 deletion rewatch/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,13 @@ 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;

/// Configuration for running a command after each JavaScript file is compiled.
/// Note: Unlike bsb, rewatch passes absolute paths to the command for clarity.
#[derive(Deserialize, Debug, Clone)]
pub struct JsPostBuild {
pub cmd: String,
}

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum DeprecationWarning {
BsDependencies,
Expand Down Expand Up @@ -299,6 +306,8 @@ pub struct Config {
pub experimental_features: Option<HashMap<ExperimentalFeature, bool>>,
#[serde(rename = "gentypeconfig")]
pub gentype_config: Option<GenTypeConfig>,
#[serde(rename = "js-post-build")]
pub js_post_build: Option<JsPostBuild>,
// Used by the VS Code extension; ignored by rewatch but should not emit warnings.
// Payload is not validated here, only in the VS Code extension.
pub editor: Option<serde_json::Value>,
Expand Down Expand Up @@ -707,7 +716,6 @@ impl Config {
"generators",
"cut-generators",
"pp-flags",
"js-post-build",
"entries",
"use-stdlib",
"external-stdlib",
Expand Down Expand Up @@ -800,6 +808,7 @@ pub mod tests {
namespace: None,
jsx: None,
gentype_config: None,
js_post_build: None,
editor: None,
namespace_entry: None,
deprecation_warnings: vec![],
Expand Down
4 changes: 2 additions & 2 deletions tests/build_tests/post-build/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import * as assert from "node:assert";
import { setup } from "#dev/process";

const { execBuildLegacy } = setup(import.meta.dirname);
const { execBuild } = setup(import.meta.dirname);

if (process.platform === "win32") {
console.log("Skipping test on Windows");
process.exit(0);
}

const out = await execBuildLegacy();
const out = await execBuild();

if (out.status !== 0) {
assert.fail(out.stdout + out.stderr);
Expand Down
Loading