use anyhow::{Context, Result};
use std::{fs, path::PathBuf, sync::Mutex};
pub(crate) static TYPE_REGISTRY_CACHE_PATH: Mutex<Option<String>> = Mutex::new(None);
pub(crate) static DECLARATIVE_REGISTRY_CACHE_PATH: Mutex<Option<String>> = Mutex::new(None);
pub(crate) fn ensure_type_registry_cache() {
let mut cached = TYPE_REGISTRY_CACHE_PATH.lock().unwrap();
if cached.is_some() {
return;
}
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let cache_dir = cwd.join(".macroforge");
let _ = fs::create_dir_all(&cache_dir);
let registry_path = cache_dir.join("type-registry.json");
let declarative_path = cache_dir.join("declarative-registry.json");
use macroforge_ts::host::scanner::{ProjectScanner, ScanConfig};
let config = ScanConfig {
root_dir: cwd.clone(),
..ScanConfig::default()
};
let scanner = ProjectScanner::new(config);
match scanner.scan() {
Ok(output) => {
let types_found = output.registry.len();
let macros_found = output.declarative_registry.macro_count();
match serde_json::to_string(&output.registry) {
Ok(json) => {
if let Err(e) = fs::write(®istry_path, &json) {
eprintln!("[macroforge] Failed to write type registry: {e}");
*cached = None;
return;
}
eprintln!(
"[macroforge] Type scan: {} types from {} files",
types_found, output.files_scanned
);
*cached = Some(registry_path.to_string_lossy().to_string());
}
Err(e) => {
eprintln!("[macroforge] Failed to serialize type registry: {e}");
*cached = None;
}
}
match output.declarative_registry.to_json() {
Ok(json) => {
if let Err(e) = fs::write(&declarative_path, &json) {
eprintln!("[macroforge] Failed to write declarative macro registry: {e}");
} else {
eprintln!(
"[macroforge] Declarative scan: {} macros across {} files",
macros_found,
output.declarative_registry.file_count()
);
*DECLARATIVE_REGISTRY_CACHE_PATH.lock().unwrap() =
Some(declarative_path.to_string_lossy().to_string());
}
}
Err(e) => {
eprintln!("[macroforge] Failed to serialize declarative macro registry: {e}");
}
}
}
Err(e) => {
eprintln!("[macroforge] Type scan failed: {e}");
*cached = None;
}
}
}
pub fn run_tsc_wrapper(project: Option<PathBuf>) -> Result<()> {
ensure_type_registry_cache();
let registry_path = TYPE_REGISTRY_CACHE_PATH.lock().unwrap().clone();
let declarative_registry_path = DECLARATIVE_REGISTRY_CACHE_PATH.lock().unwrap().clone();
let script = include_str!("../../../js/cli/tsc-wrapper.js");
let mut temp_dir = std::env::temp_dir();
temp_dir.push("macroforge-cli");
fs::create_dir_all(&temp_dir)?;
let script_path = temp_dir.join("tsc-wrapper.js");
fs::write(&script_path, script)?;
let project_arg = project
.unwrap_or_else(|| PathBuf::from("tsconfig.json"))
.to_string_lossy()
.to_string();
let mut cmd = std::process::Command::new("node");
cmd.arg(script_path).arg(project_arg);
if let Some(ref rp) = registry_path {
cmd.env("MACROFORGE_TYPE_REGISTRY_PATH", rp);
}
if let Some(ref drp) = declarative_registry_path {
cmd.env("MACROFORGE_DECLARATIVE_REGISTRY_PATH", drp);
}
let status = cmd.status().context("failed to run node tsc wrapper")?;
if !status.success() {
anyhow::bail!("tsc wrapper exited with status {}", status);
}
Ok(())
}
pub fn run_svelte_check_wrapper(
workspace: Option<PathBuf>,
tsconfig: Option<PathBuf>,
output: Option<String>,
fail_on_warnings: bool,
) -> Result<()> {
ensure_type_registry_cache();
let registry_path = TYPE_REGISTRY_CACHE_PATH.lock().unwrap().clone();
let declarative_registry_path = DECLARATIVE_REGISTRY_CACHE_PATH.lock().unwrap().clone();
let script = include_str!("../../../js/cli/svelte-check-wrapper.js");
let mut temp_dir = std::env::temp_dir();
temp_dir.push("macroforge-cli");
fs::create_dir_all(&temp_dir)?;
let script_path = temp_dir.join("svelte-check-wrapper.js");
fs::write(&script_path, script)?;
let mut cmd = std::process::Command::new("node");
cmd.arg(&script_path);
if let Some(ref rp) = registry_path {
cmd.env("MACROFORGE_TYPE_REGISTRY_PATH", rp);
}
if let Some(ref drp) = declarative_registry_path {
cmd.env("MACROFORGE_DECLARATIVE_REGISTRY_PATH", drp);
}
if let Some(ref ws) = workspace {
cmd.arg("--workspace").arg(ws);
}
if let Some(ref ts) = tsconfig {
cmd.arg("--tsconfig").arg(ts);
}
if let Some(ref out) = output {
cmd.arg("--output").arg(out);
}
if fail_on_warnings {
cmd.arg("--fail-on-warnings");
}
let status = cmd
.status()
.context("failed to run node svelte-check wrapper")?;
if !status.success() {
std::process::exit(status.code().unwrap_or(1));
}
Ok(())
}