use blueprint_std::{
collections::VecDeque,
io::{BufRead, BufReader},
process::{Command, Stdio},
sync::mpsc,
thread,
};
use color_eyre::eyre::Result;
use dialoguer::console::style;
use indicatif::{ProgressBar, ProgressStyle};
pub struct FoundryToolchain {
pub forge: Forge,
}
trait CommandInstalled {
fn is_installed(&self) -> bool;
}
impl CommandInstalled for Command {
fn is_installed(&self) -> bool {
let cmd = self.get_program();
if cfg!(target_os = "windows") {
Command::new("where")
.arg(cmd)
.output()
.is_ok_and(|v| v.status.success())
} else {
Command::new("which")
.arg(cmd)
.output()
.is_ok_and(|v| v.status.success())
}
}
}
impl Default for FoundryToolchain {
fn default() -> Self {
Self::new()
}
}
impl FoundryToolchain {
pub fn new() -> Self {
Self {
forge: Forge::new(),
}
}
pub fn check_installed_or_exit(&self) {
fn foundry_installation_instructions() {
eprintln!("Please install Foundry, follow https://getfoundry.sh/ for instructions.");
}
if !self.forge.is_installed() {
eprintln!("Forge is not installed.");
foundry_installation_instructions();
}
}
}
pub struct Forge {
pub default_template: String,
pub default_commit: String,
}
impl Default for Forge {
fn default() -> Self {
Self::new()
}
}
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}")
.unwrap(),
);
spinner.set_message("Building contracts...");
let mut child = Command::new("forge")
.arg("build")
.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(())
}
}