1use std::collections::{BTreeMap, BTreeSet};
2
3use color_eyre::Result;
4use color_eyre::eyre::eyre;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::cmd::{run_command, run_command_for_stdout};
9use crate::prelude::*;
10
11#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, derive_more::Display)]
12pub struct Mise;
13
14#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
15#[serde(deny_unknown_fields)]
16pub struct MiseOptions {
17 #[serde(default)]
18 version: Option<String>,
19}
20
21#[derive(Debug, Serialize, Deserialize, Default)]
22#[serde(deny_unknown_fields)]
23pub struct MiseConfig {}
24
25impl Backend for Mise {
26 type Options = MiseOptions;
27 type Config = MiseConfig;
28
29 fn invalid_package_help_text() -> String {
30 String::new()
31 }
32
33 fn is_valid_package_name(_: &str) -> Option<bool> {
34 None
35 }
36
37 fn get_all(_: &Self::Config) -> Result<BTreeSet<String>> {
38 let search = run_command_for_stdout(
39 ["mise", "search", "--no-headers", "--quiet"],
40 Perms::Same,
41 true,
42 )?;
43
44 Ok(search
45 .lines()
46 .map(|line| {
47 line.split_whitespace()
48 .next()
49 .expect("mise search lines should not be empty")
50 .to_string()
51 })
52 .collect())
53 }
54
55 fn get_installed(config: &Self::Config) -> Result<BTreeMap<String, Self::Options>> {
56 if Self::version(config).is_err() {
57 return Ok(BTreeMap::new());
58 }
59
60 let packages = run_command_for_stdout(
61 [
62 "mise",
63 "ls",
64 "--current",
65 "--installed",
66 "--global",
67 "--json",
68 "--quiet",
69 ],
70 Perms::Same,
71 true,
72 )?;
73
74 let packages_json = match serde_json::from_str(&packages)? {
75 Value::Object(x) => x,
76 _ => return Err(eyre!("json should be an object")),
77 };
78
79 let mut packages = BTreeMap::new();
80 for (key, value) in packages_json {
81 let versions = value
83 .as_array()
84 .ok_or(eyre!("mise package {key:?} should be an array"))?;
85
86 if let Some(first_version) = versions.first() {
88 packages.insert(
89 key.clone(),
90 MiseOptions {
91 version: first_version
92 .get("requested_version")
93 .and_then(|x| x.as_str())
94 .map(|x| x.to_string()),
95 },
96 );
97 }
98 }
99
100 Ok(packages)
101 }
102
103 fn install(
104 packages: &BTreeMap<String, Self::Options>,
105 no_confirm: bool,
106 _: &Self::Config,
107 ) -> Result<()> {
108 for (package, options) in packages {
109 let package = format!("{package}@{}", options.version.as_deref().unwrap_or(""));
110 run_command(
111 ["mise", "use", "--global"]
112 .into_iter()
113 .chain(Some("--yes").filter(|_| no_confirm))
114 .chain(std::iter::once(package.as_str())),
115 Perms::Same,
116 )?;
117 }
118
119 Ok(())
120 }
121
122 fn uninstall(packages: &BTreeSet<String>, _: bool, _: &Self::Config) -> Result<()> {
123 for package in packages {
124 run_command(["mise", "uninstall", package], Perms::Same)?;
125 }
126
127 Ok(())
128 }
129
130 fn update(packages: &BTreeSet<String>, _: bool, _: &Self::Config) -> Result<()> {
131 for package in packages {
132 run_command(["mise", "upgrade", package], Perms::Same)?;
133 }
134
135 Ok(())
136 }
137
138 fn update_all(_: bool, _: &Self::Config) -> Result<()> {
139 run_command(["mise", "upgrade"], Perms::Same)
140 }
141
142 fn clean_cache(_: &Self::Config) -> Result<()> {
143 Ok(())
144 }
145
146 fn version(_: &Self::Config) -> Result<String> {
147 run_command_for_stdout(["mise", "--version"], Perms::Same, false)
148 }
149}