diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ca7e0c858..d2b1ec7c3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Reanalyze: add reactive incremental analysis (`-reactive`, `-runs`, `-churn`) and Mermaid pipeline dumping (`-mermaid`). https://github.com/rescript-lang/rescript/pull/8092 - Reanalyze: add `reanalyze-server` (long-lived server) with transparent delegation for `rescript-tools reanalyze -json`. https://github.com/rescript-lang/rescript/pull/8127 +- Add support of `js-post-build` in Rewatch. Note that `${file}` is now an absolute path. https://github.com/rescript-lang/rescript/pull/8151 #### :bug: Bug fix diff --git a/rewatch/CompilerConfigurationSpec.md b/rewatch/CompilerConfigurationSpec.md index e8e77df335..3e01b7b561 100644 --- a/rewatch/CompilerConfigurationSpec.md +++ b/rewatch/CompilerConfigurationSpec.md @@ -22,7 +22,7 @@ This document contains a list of all bsconfig parameters with remarks, and wheth | warnings | Warnings | | [x] | | ppx-flags | array of string | | [x] | | pp-flags | array of string | | [_] | -| js-post-build | Js-Post-Build | | [_] | +| js-post-build | Js-Post-Build | `${file}` is now an absolute path | [x] | | package-specs | array of Module-Format | | [_] | | package-specs | array of Package-Spec | | [x] | | entries | array of Target-Item | | [_] | @@ -135,7 +135,7 @@ Currently supported features: | Parameter | JSON type | Remark | Implemented? | | --------- | --------- | ------ | :----------: | -| cmd | string | | [_] | +| cmd | string | `${file}` is now an absolute path | [x] | ### Package-Spec diff --git a/rewatch/src/build/compile.rs b/rewatch/src/build/compile.rs index a0ec288437..f9e7baf36d 100644 --- a/rewatch/src/build/compile.rs +++ b/rewatch/src/build/compile.rs @@ -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, @@ -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 diff --git a/rewatch/src/config.rs b/rewatch/src/config.rs index cca0e51a0f..fae38fd0d8 100644 --- a/rewatch/src/config.rs +++ b/rewatch/src/config.rs @@ -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, @@ -299,6 +306,8 @@ pub struct Config { pub experimental_features: Option>, #[serde(rename = "gentypeconfig")] pub gentype_config: Option, + #[serde(rename = "js-post-build")] + pub js_post_build: Option, // 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, @@ -707,7 +716,6 @@ impl Config { "generators", "cut-generators", "pp-flags", - "js-post-build", "entries", "use-stdlib", "external-stdlib", @@ -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![], diff --git a/tests/build_tests/post-build/input.js b/tests/build_tests/post-build/input.js index 078bbb811a..83fd43098f 100644 --- a/tests/build_tests/post-build/input.js +++ b/tests/build_tests/post-build/input.js @@ -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);