use eyre::{Result, bail};
use crate::config::Config;
use crate::prepare::{PrepareEngine, PrepareOptions, PrepareStepResult};
use crate::toolset::{InstallOptions, ToolsetBuilder};
#[derive(Debug, clap::Args)]
#[clap(visible_alias = "prep", verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct Prepare {
pub provider: Option<String>,
#[clap(long)]
pub explain: bool,
#[clap(long, short)]
pub force: bool,
#[clap(long, short = 'n')]
pub dry_run: bool,
#[clap(long)]
pub list: bool,
#[clap(long)]
pub only: Option<Vec<String>>,
#[clap(long)]
pub skip: Option<Vec<String>>,
}
impl Prepare {
pub async fn run(self) -> Result<()> {
let mut config = Config::get().await?;
let engine = PrepareEngine::new(&config)?;
if self.list {
self.list_providers(&engine)?;
return Ok(());
}
if self.explain {
let Some(ref provider_id) = self.provider else {
bail!("--explain requires a provider argument, e.g.: mise prepare npm --explain");
};
return self.explain_provider(&engine, provider_id);
}
let mut ts = ToolsetBuilder::new()
.with_default_to_latest(true)
.build(&config)
.await?;
let install_opts = InstallOptions {
missing_args_only: false,
..Default::default()
};
ts.install_missing_versions(&mut config, &install_opts)
.await?;
let env = ts.env_with_path(&config).await?;
let only = match (&self.provider, &self.only) {
(Some(p), None) => Some(vec![p.clone()]),
(Some(p), Some(list)) => {
let mut combined = list.clone();
if !combined.contains(p) {
combined.push(p.clone());
}
Some(combined)
}
(None, only) => only.clone(),
};
let opts = PrepareOptions {
dry_run: self.dry_run,
force: self.force,
only,
skip: self.skip.unwrap_or_default(),
env,
..Default::default()
};
let result = engine.run(opts).await?;
for step in &result.steps {
match step {
PrepareStepResult::Ran(id) => {
info!("Prepared: {}", id);
}
PrepareStepResult::WouldRun(id, reason) => {
info!("[dry-run] Would prepare: {} ({})", id, reason);
}
PrepareStepResult::Fresh(id) => {
debug!("Fresh: {}", id);
}
PrepareStepResult::Skipped(id) => {
debug!("Skipped: {}", id);
}
PrepareStepResult::Failed(id) => {
error!("Failed: {}", id);
}
}
}
if !result.had_work() && !self.dry_run {
info!("All dependencies are up to date");
}
Ok(())
}
fn explain_provider(&self, engine: &PrepareEngine, provider_id: &str) -> Result<()> {
let Some(provider) = engine.find_provider(provider_id) else {
let available = engine
.list_providers()
.iter()
.map(|p| format!(" {}", p.id()))
.collect::<Vec<_>>()
.join("\n");
bail!("Provider '{provider_id}' not found.\n\nAvailable providers:\n{available}");
};
let freshness = engine.check_provider_freshness(provider)?;
miseprintln!("Provider: {}", provider.id());
miseprintln!("Auto: {}", if provider.is_auto() { "yes" } else { "no" });
let sources = provider.sources();
miseprintln!("Sources:");
for source in &sources {
let exists = source.exists();
let marker = if exists { "+" } else { "-" };
miseprintln!(" {} {}", marker, source.display());
}
let outputs = provider.outputs();
miseprintln!("Outputs:");
for output in &outputs {
let exists = output.exists();
let marker = if exists { "+" } else { "-" };
miseprintln!(" {} {}", marker, output.display());
}
if let Ok(cmd) = provider.prepare_command() {
miseprintln!("Command: {}", cmd.description);
}
miseprintln!("");
if freshness.is_fresh() {
miseprintln!("Status: fresh ({})", freshness.reason());
} else {
miseprintln!("Status: stale ({})", freshness.reason());
}
if !freshness.is_fresh() {
bail!("provider '{}' is stale", provider.id());
}
Ok(())
}
fn list_providers(&self, engine: &PrepareEngine) -> Result<()> {
let providers = engine.list_providers();
if providers.is_empty() {
miseprintln!("No prepare providers found for this project");
return Ok(());
}
miseprintln!("Available prepare providers:");
for provider in providers {
let sources = provider
.sources()
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(", ");
let outputs = provider
.outputs()
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(", ");
miseprintln!(" {}", provider.id());
miseprintln!(" sources: {}", sources);
miseprintln!(" outputs: {}", outputs);
}
Ok(())
}
}
static AFTER_LONG_HELP: &str = color_print::cstr!(
r#"<bold><underline>Examples:</underline></bold>
$ <bold>mise prepare</bold> # Run all applicable prepare steps
$ <bold>mise prepare npm</bold> # Run only npm prepare
$ <bold>mise prepare npm --explain</bold> # Show why npm is fresh or stale
$ <bold>mise prepare --dry-run</bold> # Show what would run without executing
$ <bold>mise prepare --force</bold> # Force run even if outputs are fresh
$ <bold>mise prepare --list</bold> # List available prepare providers
$ <bold>mise prepare --skip npm</bold> # Skip npm prepare
<bold><underline>Configuration:</underline></bold>
```toml
# Built-in npm provider (auto-detects lockfile)
[prepare.npm]
auto = true # Auto-run before mise x/run
# Custom provider
[prepare.codegen]
auto = true
sources = ["schema/*.graphql"]
outputs = ["src/generated/"]
run = "npm run codegen"
[prepare]
disable = ["npm"] # Disable specific providers at runtime
```
"#
);