use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use crate::project::ProjectConfig;
use super::{ChildGuard, ProcessError, ProcessSpec, spawn};
const VITE_IPC_HARNESS: &str = r#"import http from 'node:http';
import { createServer } from 'vite';
const ipcPath = process.env.ARCATURE_VITE_IPC;
if (!ipcPath) { console.error('ARCATURE_VITE_IPC is not set'); process.exit(1); }
const httpServer = http.createServer();
const vite = await createServer({
server: { middlewareMode: true, ws: { server: httpServer } },
appType: 'custom',
logLevel: 'info',
});
httpServer.on('request', (req, res) => {
vite.middlewares(req, res, () => { res.statusCode = 404; res.end('not found'); });
});
httpServer.listen(ipcPath, () => console.log(`vite-ipc ready on ${ipcPath}`));
const shutdown = async () => { await vite.close(); httpServer.close(); process.exit(0); };
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
"#;
#[must_use]
pub(crate) fn ipc_path() -> PathBuf {
let pid = std::process::id();
#[cfg(unix)]
{
std::env::temp_dir().join(format!("arcature-vite-{pid}.sock"))
}
#[cfg(windows)]
{
PathBuf::from(format!(r"\\.\pipe\arcature-vite-{pid}"))
}
}
pub(crate) fn start_vite_ipc(
project: &ProjectConfig,
ipc_path: &Path,
) -> Result<(ChildGuard, PathBuf), ProcessError> {
let frontend_root = project.frontend_root();
let harness_dir = frontend_root.join(".arcature");
std::fs::create_dir_all(&harness_dir).map_err(|source| ProcessError::Spawn {
program: "node".to_owned(),
directory: harness_dir.clone(),
source,
})?;
let harness = harness_dir.join("vite-ipc-harness.mjs");
std::fs::write(&harness, VITE_IPC_HARNESS).map_err(|source| ProcessError::Spawn {
program: "node".to_owned(),
directory: harness_dir,
source,
})?;
let child = spawn(
&ProcessSpec::new("node", frontend_root)
.arg(harness.as_os_str().to_os_string())
.env("ARCATURE_VITE_IPC", ipc_path.display().to_string())
.new_process_group(true),
)
.map(|child| ChildGuard::new(child, "vite-ipc"))?;
Ok((child, harness))
}
pub(crate) fn wait_ipc_ready(
ipc_path: &Path,
child: &mut ChildGuard,
stopping: &AtomicBool,
) -> Result<(), ProcessError> {
for _ in 0..100 {
if stopping.load(Ordering::SeqCst) {
return Ok(());
}
#[cfg(unix)]
if ipc_path.exists() {
return Ok(());
}
#[cfg(windows)]
if std::fs::File::open(ipc_path).is_ok() {
return Ok(());
}
if let Some(status) = child.try_wait()? {
if stopping.load(Ordering::SeqCst) {
return Ok(());
}
return Err(ProcessError::Failed {
program: "node (Vite)".to_owned(),
directory: PathBuf::from("."),
status,
});
}
thread::sleep(Duration::from_millis(100));
}
Err(ProcessError::Readiness {
service: "vite-ipc",
address: ipc_path.display().to_string(),
})
}
pub(crate) fn cleanup(ipc_path: &Path, harness: &Path) {
#[cfg(unix)]
{
let _ = std::fs::remove_file(ipc_path);
}
#[cfg(windows)]
{
let _ = ipc_path;
}
let _ = std::fs::remove_file(harness);
let _ = std::fs::remove_dir(harness.parent().unwrap_or(Path::new("")));
}