diff --git a/frork-cli/src/assertions.rs b/frork-cli/src/assertions.rs
index d6db6a0..95c0387 100644
--- a/frork-cli/src/assertions.rs
+++ b/frork-cli/src/assertions.rs
@@ -468,3 +468,92 @@ impl AssertionType for Brew {
Ok(())
}
}
+
+pub struct BrewBundle {
+ pub brewfile: ExpandedPath,
+}
+
+impl FromLuaMulti for BrewBundle {
+ fn from_lua_multi(args: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {
+ let brewfile = ExpandedPath::from_lua_multi(args, lua)?;
+ Ok(Self { brewfile })
+ }
+}
+
+impl std::fmt::Display for BrewBundle {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "brew-bundle {}", self.brewfile)
+ }
+}
+
+impl AssertionType for BrewBundle {
+ fn status(&self) -> Result<Status> {
+ // Assert platform is Darwin (macOS)
+ #[cfg(not(target_os = "macos"))]
+ return Err(eyre!("brew-bundle only supported on Darwin/macOS"));
+
+ // Assert brew binary exists
+ Utils::assert_bin("brew")?;
+
+ // First check: brew bundle check --no-upgrade
+ let (_output, exit_code) = Utils::sh_with_envs(
+ "brew",
+ &[
+ "bundle".to_string(),
+ "check".to_string(),
+ "--no-upgrade".to_string(),
+ format!("--file={}", self.brewfile),
+ ],
+ &[("HOMEBREW_NO_AUTO_UPDATE", "true")],
+ )?;
+
+ if exit_code != 0 {
+ return Ok(Status::Missing);
+ }
+
+ // Second check: brew bundle check (without --no-upgrade)
+ let (_output, exit_code) = Utils::sh_with_envs(
+ "brew",
+ &[
+ "bundle".to_string(),
+ "check".to_string(),
+ format!("--file={}", self.brewfile),
+ ],
+ &[("HOMEBREW_NO_AUTO_UPDATE", "true")],
+ )?;
+
+ if exit_code != 0 {
+ return Ok(Status::ConflictUpgrade(Conflict {
+ expected: "up-to-date packages".to_string(),
+ actual: "packages need upgrade".to_string(),
+ }));
+ }
+
+ Ok(Status::Ok)
+ }
+
+ fn install(&self) -> Result<()> {
+ // Assert platform is Darwin (macOS)
+ #[cfg(not(target_os = "macos"))]
+ return Err(eyre!("brew-bundle only supported on Darwin/macOS"));
+
+ // Assert brew binary exists
+ Utils::assert_bin("brew")?;
+
+ let (_output, exit_code) = Utils::sh(
+ "brew",
+ &[
+ "bundle".to_string(),
+ "install".to_string(),
+ format!("--file={}", self.brewfile),
+ ],
+ )?;
+
+ if exit_code != 0 {
+ return Err(eyre!("Failed to install brew bundle"));
+ }
+
+ debug!("installed: {}", self);
+ Ok(())
+ }
+}
diff --git a/frork-cli/src/main.rs b/frork-cli/src/main.rs
index 5428a8f..a6d7486 100644
--- a/frork-cli/src/main.rs
+++ b/frork-cli/src/main.rs
@@ -3,7 +3,7 @@ mod errors;
mod utils;
use assertions::{
- AssertionType, AssertionTypeFactory, Brew, Debug, Directory, Git, LuaAssertion, LuaAssertionType,
+ AssertionType, AssertionTypeFactory, Brew, BrewBundle, Debug, Directory, Git, LuaAssertion, LuaAssertionType,
Status, Symlink, TypedFactory,
};
use clap::{Parser, Subcommand};
@@ -70,6 +70,7 @@ impl Registry {
// Return factory for built-in types
match assertion_type {
"brew" => Ok(Box::new(TypedFactory::<Brew>::new())),
+ "brew-bundle" => Ok(Box::new(TypedFactory::<BrewBundle>::new())),
"debug" => Ok(Box::new(TypedFactory::<Debug>::new())),
"directory" => Ok(Box::new(TypedFactory::<Directory>::new())),
"git" => Ok(Box::new(TypedFactory::<Git>::new())),
diff --git a/frork-cli/src/utils.rs b/frork-cli/src/utils.rs
index b864998..79659a8 100644
--- a/frork-cli/src/utils.rs
+++ b/frork-cli/src/utils.rs
@@ -145,10 +145,17 @@ impl Utils {
}
pub fn sh(cmd: &str, args: &[String]) -> Result<(String, i32)> {
- debug!("Executing command: {} with args: {:?}", cmd, args);
+ Self::sh_with_envs(cmd, args, &[])
+ }
+
+ pub fn sh_with_envs(cmd: &str, args: &[String], env_vars: &[(&str, &str)]) -> Result<(String, i32)> {
+ debug!("Executing command: {} with args: {:?} and envs: {:?}", cmd, args, env_vars);
+
+ let mut command = Command::new(cmd);
+ command.args(args);
+ command.envs(env_vars.iter().map(|(k, v)| (*k, *v)));
- let output = Command::new(cmd)
- .args(args)
+ let output = command
.output()
.map_err(|e| eyre!("Failed to execute command '{}': {}", cmd, e))?;