Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added

- New backend: `mise` (requested in #127, implemented in #166), thanks
@Mikel-Landa!

### Fixed

- Issue with the `cargo` `binstall` option not tracking installed packages
has been fixed by switching from using the `.crates2.json` file to using
the `.crates.toml` file (#167), thanks @Mikel-Landa!

## [0.6.4] - 2025-11-10

### Added
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ for additional backends are always welcome!
| [`cargo`](#cargo) |
| [`dnf`](#dnf) |
| [`flatpak`](#flatpak) |
| [`mise`](#mise) |
| [`npm`](#npm) |
| [`pipx`](#pipx) |
| [`pnpm`](#pnpm) |
Expand Down Expand Up @@ -212,6 +213,8 @@ Reported in #152.

### flatpak

### mise

### npm

If on linux you might need to first run `npm config set prefix ~/.local`.
Expand Down Expand Up @@ -405,6 +408,11 @@ flatpak = [
"package1",
{ package = "package2", options = { remote = "flathub", systemwide = false } },
]
mise = [
"package1",
{ package = "package2", options = { version = "1.0.0" } },
{ package = "package3", options = { version = "lts" } },
]
npm = ["package1", { package = "package2" }]
pipx = ["package1", { package = "package2" }]
pnpm = ["package1", { package = "package2" }]
Expand Down Expand Up @@ -439,7 +447,6 @@ of any other package managers we should be aware of.
packages <https://github.com/denoland/deno/discussions/28230>
- [`emerge`](https://wiki.gentoo.org/wiki/Emerge): no attempt made yet
- [`guix`](https://codeberg.org/guix/guix): no attempt made yet
- [`mise`](https://github.com/jdx/mise): no attempt made yet
- [`nala`](https://github.com/volitank/nala): no attempt made yet
- [`nix`](https://github.com/NixOS/nix): no attempt made yet
- [`opkg`](https://github.com/oe-mirrors/opkg): no attempt made yet
Expand Down
141 changes: 141 additions & 0 deletions src/backends/mise.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use std::collections::{BTreeMap, BTreeSet};

use color_eyre::Result;
use color_eyre::eyre::eyre;
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::cmd::{run_command, run_command_for_stdout};
use crate::prelude::*;

#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, derive_more::Display)]
pub struct Mise;

#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MiseOptions {
#[serde(default)]
version: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct MiseConfig {}

impl Backend for Mise {
type Options = MiseOptions;
type Config = MiseConfig;

fn invalid_package_help_text() -> String {
String::new()
}

fn is_valid_package_name(_: &str) -> Option<bool> {
None
}

fn get_all(_: &Self::Config) -> Result<BTreeSet<String>> {
let search = run_command_for_stdout(
["mise", "search", "--no-headers", "--quiet"],
Perms::Same,
true,
)?;

Ok(search
.lines()
.map(|line| {
line.split_whitespace()
.next()
.expect("mise search lines should not be empty")
.to_string()
})
.collect())
}

fn get_installed(config: &Self::Config) -> Result<BTreeMap<String, Self::Options>> {
if Self::version(config).is_err() {
return Ok(BTreeMap::new());
}

let packages = run_command_for_stdout(
["mise", "ls", "--installed", "--json", "--quiet"],
Perms::Same,
true,
)?;

let packages_json = match serde_json::from_str(&packages)? {
Value::Object(x) => x,
_ => return Err(eyre!("json should be an object")),
};

let mut packages = BTreeMap::new();
for (key, value) in packages_json {
// Each package maps to an array of version objects
let versions = value
.as_array()
.ok_or(eyre!("mise package {key:?} should be an array"))?;

// Take the first version and ignore any others
if let Some(first_version) = versions.first() {
packages.insert(
key.clone(),
MiseOptions {
version: first_version
.get("version")
.and_then(|x| x.as_str())
.map(|x| x.to_string()),
},
);
}
}

Ok(packages)
}

fn install(
packages: &BTreeMap<String, Self::Options>,
no_confirm: bool,
_: &Self::Config,
) -> Result<()> {
for (package, options) in packages {
let package = format!("{package}@{}", options.version.as_deref().unwrap_or(""));
run_command(
["mise", "install"]
.into_iter()
.chain(Some("--yes").filter(|_| no_confirm))
.chain(std::iter::once(package.as_str())),
Perms::Same,
)?;
}

Ok(())
}

fn uninstall(packages: &BTreeSet<String>, _: bool, _: &Self::Config) -> Result<()> {
for package in packages {
run_command(["mise", "uninstall", package], Perms::Same)?;
}

Ok(())
}

fn update(packages: &BTreeSet<String>, _: bool, _: &Self::Config) -> Result<()> {
for package in packages {
run_command(["mise", "upgrade", package], Perms::Same)?;
}

Ok(())
}

fn update_all(_: bool, _: &Self::Config) -> Result<()> {
run_command(["mise", "upgrade"], Perms::Same)
}

fn clean_cache(_: &Self::Config) -> Result<()> {
Ok(())
}

fn version(_: &Self::Config) -> Result<String> {
run_command_for_stdout(["mise", "--version"], Perms::Same, false)
}
}
2 changes: 2 additions & 0 deletions src/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod bun;
pub mod cargo;
pub mod dnf;
pub mod flatpak;
pub mod mise;
pub mod npm;
pub mod pipx;
pub mod pnpm;
Expand All @@ -32,6 +33,7 @@ macro_rules! apply_backends {
(Cargo, cargo),
(Dnf, dnf),
(Flatpak, flatpak),
(Mise, mise),
(Npm, npm),
(Pipx, pipx),
(Pnpm, pnpm),
Expand Down
1 change: 1 addition & 0 deletions src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub use crate::backends::bun::{Bun, BunOptions};
pub use crate::backends::cargo::{Cargo, CargoConfig, CargoOptions};
pub use crate::backends::dnf::{Dnf, DnfOptions};
pub use crate::backends::flatpak::{Flatpak, FlatpakConfig, FlatpakOptions};
pub use crate::backends::mise::{Mise, MiseConfig, MiseOptions};
pub use crate::backends::npm::{Npm, NpmOptions};
pub use crate::backends::pipx::{Pipx, PipxOptions};
pub use crate::backends::pnpm::{Pnpm, PnpmOptions};
Expand Down