use std::path::PathBuf;
use eyre::Result;
use indexmap::IndexMap;
use super::driver::{self, Action, DriverOpts};
use crate::config::config_file::ConfigFile;
use crate::config::config_file::mise_toml::MiseToml;
use crate::config::{ConfigPathOptions, resolve_target_config_path};
use crate::file::display_path;
use crate::system;
use crate::system::history::OperationScope;
use crate::system::packages::PackageRequest;
#[derive(Debug, usage_rs::Args)]
#[usage(
visible_alias = "u",
verbatim_doc_comment,
example(
r###"mise bootstrap packages use brew:jq brew-cask:firefox
mise bootstrap packages use -g brew:postgresql@17
mise bootstrap packages use apt:curl@8.5.0-2"###
)
)]
pub(crate) struct SystemUse {
#[usage(value_name = "PACKAGE", required = true)]
packages: Vec<String>,
#[usage(long, short, value_name = "ENV", conflicts = ["global", "path"])]
env: Option<String>,
#[usage(long, short)]
global: bool,
#[usage(long, short = 'n')]
dry_run: bool,
#[usage(
long,
short,
visible_alias = "file",
value_name = "PATH",
conflicts = "global"
)]
path: Option<PathBuf>,
#[usage(long, short)]
yes: bool,
}
impl SystemUse {
pub(crate) async fn run(self) -> Result<()> {
OperationScope::wrap("bootstrap packages use", self.dry_run, self.run_inner()).await
}
async fn run_inner(self) -> Result<()> {
let config = crate::config::Config::get().await?;
let mut by_mgr: IndexMap<String, Vec<PackageRequest>> = IndexMap::new();
let mut entries: Vec<(String, String)> = vec![];
for spec in &self.packages {
let (mgr, request) = system::parse_use_spec(spec)?;
let key = format!("{mgr}:{}", request.name);
let version = request.version.clone().unwrap_or_else(|| "latest".into());
match entries.iter_mut().find(|(k, _)| k == &key) {
Some(entry) => entry.1 = version,
None => entries.push((key, version)),
}
let requests = by_mgr.entry(mgr).or_default();
match requests.iter_mut().find(|r| r.name == request.name) {
Some(r) => *r = request,
None => requests.push(request),
}
}
system::attach_brew_tap_urls(&config, &mut by_mgr);
let mgrs = system::packages_from_requests(by_mgr)?;
let path = resolve_target_config_path(ConfigPathOptions {
global: self.global,
path: self.path.clone(),
env: self.env.clone(),
cwd: None,
prefer_toml: true, prevent_home_local: true, })?;
if self.dry_run {
for (key, version) in &entries {
miseprintln!("{}: \"{key}\" = \"{version}\"", display_path(&path));
}
} else {
let mut cf = if path.exists() {
MiseToml::from_file(&path)?
} else {
MiseToml::init(&path)
};
for (key, version) in &entries {
cf.update_bootstrap_package(key, version)?;
}
cf.save()?;
info!(
"{}: added {}",
display_path(&path),
entries
.iter()
.map(|(k, _)| k.as_str())
.collect::<Vec<_>>()
.join(", ")
);
}
if !self.dry_run {
for mp in &mgrs {
if !mp.disabled
&& let Some(reason) = mp.manager.unavailable_reason_async().await
{
info!(
"{}: {} — added to config without installing",
mp.manager.name(),
reason
);
}
}
}
let opts = DriverOpts {
manager: None,
explicit: true,
allow_unavailable_manager: true,
dry_run: self.dry_run,
update: false,
yes: self.yes,
};
driver::run(mgrs, Action::Install, &opts).await
}
}