use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=scripts/auto-setup-hooks.sh");
println!("cargo:rerun-if-changed=front-end/src");
println!("cargo:rerun-if-changed=front-end/package.json");
println!("cargo:rerun-if-changed=.git/hooks/pre-commit");
build_frontend_if_needed();
if should_skip_hooks_setup() {
return;
}
if !is_git_repository() {
return;
}
if hooks_already_installed() {
if let Some(marker) = get_marker_file() {
if !marker.exists() {
create_marker_file(&marker);
}
}
return;
}
if let Some(marker) = get_marker_file() {
install_git_hooks(&marker);
}
}
fn build_frontend_if_needed() {
let static_dir = Path::new("static");
let index_html = static_dir.join("index.html");
let front_end_dir = Path::new("front-end");
if !front_end_dir.exists() {
return;
}
if index_html.exists() {
return;
}
if is_ci_environment() {
return;
}
println!("cargo:warning=📦 Building frontend (static/ not found)...");
let npm_cmd = if cfg!(windows) { "npm.cmd" } else { "npm" };
let npm_ci = Command::new(npm_cmd)
.args(["ci"])
.current_dir(front_end_dir)
.output();
match npm_ci {
Ok(output) if output.status.success() => {
let npm_build = Command::new(npm_cmd)
.args(["run", "build"])
.current_dir(front_end_dir)
.output();
match npm_build {
Ok(output) if output.status.success() => {
println!("cargo:warning=✅ Frontend built successfully!");
},
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
eprintln!("cargo:warning=⚠️ npm run build failed: {}", stderr.trim());
eprintln!("cargo:warning=Run 'cd front-end && npm run build' manually.");
},
Err(e) => {
eprintln!("cargo:warning=⚠️ Could not run npm build: {}", e);
},
}
},
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
eprintln!("cargo:warning=⚠️ npm ci failed: {}", stderr.trim());
eprintln!("cargo:warning=Run 'cd front-end && npm ci && npm run build' manually.");
},
Err(e) => {
eprintln!("cargo:warning=⚠️ npm not found: {}", e);
eprintln!(
"cargo:warning=Install Node.js and run 'cd front-end && npm ci && npm run build'."
);
},
}
}
fn is_ci_environment() -> bool {
env::var("CI").is_ok()
|| env::var("GITHUB_ACTIONS").is_ok()
|| env::var("GITLAB_CI").is_ok()
|| env::var("CIRCLECI").is_ok()
|| env::var("TRAVIS").is_ok()
}
fn should_skip_hooks_setup() -> bool {
if is_ci_environment() {
return true;
}
if let Ok(profile) = env::var("PROFILE") {
if profile == "release" {
return true;
}
}
if env::var("SKIP_GIT_HOOKS_SETUP").is_ok() {
return true;
}
false
}
fn is_git_repository() -> bool {
Path::new(".git").exists()
}
fn get_marker_file() -> Option<PathBuf> {
let target_dir = Path::new("target");
if !target_dir.exists() {
if let Err(e) = fs::create_dir_all(target_dir) {
eprintln!("cargo:warning=Could not create target directory: {}", e);
return None;
}
}
Some(target_dir.join(".git-hooks-installed"))
}
fn hooks_already_installed() -> bool {
let hook_path = Path::new(".git/hooks/pre-commit");
if !hook_path.exists() {
return false;
}
match fs::read_to_string(hook_path) {
Ok(content) => content.contains("cargo fmt"),
Err(_) => false,
}
}
fn create_marker_file(marker_path: &Path) {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let content = format!(
"Git hooks installed by build.rs\nTimestamp: {}\n",
timestamp
);
if let Err(e) = fs::write(marker_path, content) {
eprintln!("cargo:warning=Could not create marker file: {}", e);
}
}
fn install_git_hooks(marker_path: &Path) {
let setup_script = Path::new("scripts/auto-setup-hooks.sh");
if !setup_script.exists() {
eprintln!(
"cargo:warning=Setup script not found: {}",
setup_script.display()
);
return;
}
println!("cargo:warning=🔧 Setting up git pre-commit hooks for auto-formatting...");
let bash_cmd = find_bash_command();
match Command::new(&bash_cmd)
.arg(setup_script)
.current_dir(".")
.output()
{
Ok(output) => {
if output.status.success() {
create_marker_file(marker_path);
println!("cargo:warning=✅ Git hooks configured! Commits will be auto-formatted.");
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
eprintln!(
"cargo:warning=⚠️ Hook installation failed: {}",
stderr.trim()
);
eprintln!(
"cargo:warning=Run ./scripts/setup-git-hooks.sh manually to install hooks."
);
}
},
Err(e) => {
eprintln!("cargo:warning=⚠️ Could not execute setup script: {}", e);
eprintln!("cargo:warning=Bash may not be available on your system.");
eprintln!("cargo:warning=Run ./scripts/setup-git-hooks.sh manually to install hooks.");
},
}
}
fn find_bash_command() -> String {
let bash_candidates = vec![
"bash", "/bin/bash", "/usr/bin/bash", "sh", ];
for bash in bash_candidates {
if Command::new(bash).arg("--version").output().is_ok() {
return bash.to_string();
}
}
"bash".to_string()
}