use super::CommandInstalled;
use color_eyre::eyre::Result;
use dialoguer::console::style;
use indicatif::{ProgressBar, ProgressStyle};
use std::{
collections::VecDeque,
io::{BufRead, BufReader},
process::{Command, Stdio},
sync::mpsc,
thread,
};
pub struct Forge {
pub default_template: String,
pub default_commit: String,
}
impl Default for Forge {
fn default() -> Self {
Self::new()
}
}
#[expect(clippy::unused_self)]
impl Forge {
pub fn new() -> Self {
Self {
default_template: "foundry-rs/forge-template".to_string(),
default_commit: "main".to_string(),
}
}
pub fn version(&self) -> Result<String> {
let output = Command::new("forge").arg("--version").output()?;
Ok(String::from_utf8(output.stdout)?
.trim()
.replace("forge", ""))
}
pub fn is_installed(&self) -> bool {
Command::new("forge").is_installed()
}
pub fn install_dependencies(&self) -> Result<()> {
let status = Command::new("forge")
.args(["soldeer", "update", "-d"])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()?;
if !status.success() {
return Err(color_eyre::eyre::eyre!(
"Failed to install dependencies. Check the output above for details."
));
}
Ok(())
}
pub fn build(&self) -> Result<()> {
let spinner = ProgressBar::new_spinner();
spinner.set_style(
ProgressStyle::default_spinner()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈")
.template("{spinner:.green} {msg}")?,
);
spinner.set_message("Building contracts...");
let mut child = Command::new("forge")
.arg("build")
.arg("--via-ir")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let stdout = child.stdout.take().expect("Failed to capture stdout");
let stderr = child.stderr.take().expect("Failed to capture stderr");
let stdout_reader = BufReader::new(stdout);
let stderr_reader = BufReader::new(stderr);
let (tx, rx) = mpsc::channel();
let tx_stderr = tx.clone();
let mut output_buffer = VecDeque::with_capacity(100);
thread::spawn(move || {
for line in stdout_reader.lines().map_while(Result::ok) {
let _ = tx.send(line);
}
});
thread::spawn(move || {
for line in stderr_reader.lines().map_while(Result::ok) {
let _ = tx_stderr.send(line);
}
});
while child.try_wait()?.is_none() {
spinner.tick();
while let Ok(line) = rx.try_recv() {
if !output_buffer.contains(&line) {
output_buffer.push_back(line);
if output_buffer.len() > 100 {
output_buffer.pop_front();
}
spinner.suspend(|| {
println!("{}", style(&output_buffer[output_buffer.len() - 1]).dim());
});
}
}
thread::sleep(std::time::Duration::from_millis(100));
}
let status = child.wait()?;
if !status.success() {
spinner.finish_with_message("❌ Failed to build contracts");
return Err(color_eyre::eyre::eyre!(
"Failed to build contracts. Check the output above for details."
));
}
spinner.finish_with_message("✨ Contracts built successfully!");
Ok(())
}
}