use std::path::{Path, PathBuf};
use std::process::Command;
use anyhow::{anyhow, bail, Context, Result};
use super::{gh, monorepo::MonorepoHost, DaemonHandle, DaemonHost};
use crate::tui::{self, LogTx};
const CLONE_REPO: &str = "econ-v1/node";
pub struct CloneHost {
socket_override: Option<PathBuf>,
dev_dir_override: Option<PathBuf>,
log_tx: Option<LogTx>,
}
impl CloneHost {
pub fn new(
socket_override: Option<&Path>,
dev_dir_override: Option<&Path>,
log_tx: Option<LogTx>,
) -> Self {
Self {
socket_override: socket_override.map(PathBuf::from),
dev_dir_override: dev_dir_override.map(PathBuf::from),
log_tx,
}
}
fn syslog(&self, msg: impl Into<String>) {
tui::sys_log(self.log_tx.as_ref(), msg);
}
}
impl DaemonHost for CloneHost {
fn pre_start_dev_dir(&self) -> Option<PathBuf> {
let monorepo_path = cache_root().ok()?.join("monorepo");
MonorepoHost::new(
monorepo_path,
super::InstanceProfile::alice(),
self.socket_override.clone(),
self.dev_dir_override.clone(),
None, )
.pre_start_dev_dir()
}
fn ensure_running(&self) -> Result<DaemonHandle> {
gh::ensure_gh_auth_and_access(CLONE_REPO).map_err(|e| {
anyhow!(
"{e}\n\
Otherwise use `--daemon docker` (no private repo access required)."
)
})?;
let cache_root = cache_root()?;
std::fs::create_dir_all(&cache_root).ok();
let monorepo_path = cache_root.join("monorepo");
if monorepo_path.exists() && monorepo_path.join(".git").exists() {
self.syslog(format!(
"→ updating cached monorepo at {}…",
monorepo_path.display()
));
let status = Command::new("git")
.args(["pull", "--ff-only"])
.current_dir(&monorepo_path)
.status()
.with_context(|| format!("git pull in {}", monorepo_path.display()))?;
if !status.success() {
self.syslog(format!(
"⚠ git pull failed — proceeding with the cached version. \
You may want to delete {} for a fresh clone.",
monorepo_path.display()
));
}
} else {
self.syslog(format!(
"→ cloning {} to {} (first run, ~30s)…",
CLONE_REPO,
monorepo_path.display()
));
let status = Command::new("gh")
.args(["repo", "clone", CLONE_REPO])
.arg(&monorepo_path)
.status()
.with_context(|| format!("gh repo clone {}", CLONE_REPO))?;
if !status.success() {
bail!(
"gh repo clone failed (exit {}). Cache state at {} may be partial.",
status.code().unwrap_or(-1),
monorepo_path.display()
);
}
}
let monorepo_host = MonorepoHost::new(
monorepo_path,
super::InstanceProfile::alice(),
self.socket_override.clone(),
self.dev_dir_override.clone(),
self.log_tx.clone(),
);
monorepo_host.ensure_running()
}
}
fn cache_root() -> Result<PathBuf> {
if let Ok(c) = std::env::var("XDG_CACHE_HOME") {
if !c.is_empty() {
return Ok(PathBuf::from(c).join("node-app"));
}
}
let home = std::env::var_os("HOME").ok_or_else(|| anyhow!("$HOME not set"))?;
Ok(PathBuf::from(home).join(".cache/node-app"))
}