Source code for meta_package_manager.managers.mise

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

from __future__ import annotations

import json
import re

from extra_platforms import ALL_PLATFORMS

from ..capabilities import search_capabilities
from ..manager import PackageManager

TYPE_CHECKING = False
if TYPE_CHECKING:
    from collections.abc import Iterator

    from ..package import Package


[docs] class Mise(PackageManager): """mise (formerly rtx) installs and switches between versions of developer tools like Node, Python, Ruby and any tool reachable through its plugin backends. .. note:: ``mpm`` is system-scoped, so this wrapper reports every tool version present on disk regardless of which ``mise.toml`` (global or project) requested it. Project-pinned versions are not surfaced as a separate scope. .. note:: Backend-prefixed tool IDs (``pipx:ruff``, ``cargo:ubi-cli``, ``asdf:mise-plugins/mise-poetry``) round-trip as-is. The colon is part of the package ID; ``mpm install pipx:ruff`` resolves the backend through ``mise`` itself. .. caution:: ``mise outdated --json`` only reports tools tracked in a ``mise.toml`` (global or project). A tool installed bare with ``mise install <tool>`` and never pinned with ``mise use`` will not appear in the outdated list, so ``mpm outdated --mise`` understates the upgrade surface for those entries. """ name = "mise" homepage_url = "https://mise.jdx.dev" platforms = ALL_PLATFORMS requirement = ">=2025.5.10" """``mise search`` shipped in ``2025.5.10``, the binding floor for the feature set this wrapper depends on. Earlier releases also miss the ``outdated --json`` fix from ``2025.2.8`` that emits valid JSON when no tool is outdated. """ version_regexes = (r"^(?P<version>\d+\.\d+\.\d+)",) """``mise`` uses CalVer (``YYYY.M.P``), not SemVer. .. code-block:: shell-session $ mise --version 2026.6.3 macos-arm64 (2026-06-13) """ _SEARCH_REGEXP = re.compile( r"^(?P<package_id>\S+)\s{2,}(?P<description>.+)$", re.MULTILINE, ) @property def installed(self) -> Iterator[Package]: """Fetch installed packages. Emits one :py:class:`meta_package_manager.package.Package` per ``(tool, installed_version)`` pair, so a tool installed at multiple versions yields multiple entries sharing the same ID. .. code-block:: shell-session $ mise ls --installed --json { "node": [ { "version": "20.10.0", "install_path": "~/.local/share/mise/installs/node/20.10.0", "source": {"type": "mise.toml", "path": "~/.config/mise/config.toml"} } ], "pipx:ruff": [ { "version": "0.6.9", "install_path": "~/.local/share/mise/installs/pipx-ruff/0.6.9" } ] } """ output = self.run_cli("ls", "--installed", "--json", must_succeed=True) if not output: return for tool_id, entries in json.loads(output).items(): for entry in entries: yield self.package( id=tool_id, installed_version=entry["version"], ) @property def outdated(self) -> Iterator[Package]: """Fetch outdated packages. .. code-block:: shell-session $ mise outdated --json { "node": { "requested": "20", "current": "20.0.0", "latest": "20.10.0" } } """ output = self.run_cli("outdated", "--json", must_succeed=True) if not output: return for tool_id, info in json.loads(output).items(): yield self.package( id=tool_id, installed_version=info["current"], latest_version=info["latest"], )
[docs] @search_capabilities(extended_support=True, exact_support=False) def search(self, query: str, extended: bool, exact: bool) -> Iterator[Package]: """Fetch matching packages. ``mise search`` returns a two-column ``Tool Description`` table. ``--match-type contains`` keeps the candidate set wide; the framework's :py:meth:`meta_package_manager.manager.PackageManager.refiltered_search` narrows it down to honor ``extended`` and ``exact`` flags. .. code-block:: shell-session $ mise search --no-header --match-type contains node node Node.js node-build Compile and install Node.js nodejs alias for node """ output = self.run_cli( "search", "--no-header", "--match-type", "contains", query, ) for match in self._SEARCH_REGEXP.finditer(output): yield self.package( id=match.group("package_id"), description=match.group("description").strip(), )
[docs] def install(self, package_id: str, version: str | None = None) -> str: """Install one package. ``mise install <tool>`` resolves to the latest version compatible with the active config; ``mise install <tool>@<version>`` pins it explicitly. Neither variant writes to ``mise.toml``: the dedicated ``mise use`` command is the config-mutating verb and is deliberately avoided here. .. code-block:: shell-session $ mise install node@20 """ spec = f"{package_id}@{version}" if version else package_id return self.run_cli("install", spec)
[docs] def upgrade_all_cli(self) -> tuple[str, ...]: """Generates the CLI to upgrade all packages. .. code-block:: shell-session $ mise upgrade """ return self.build_cli("upgrade")
[docs] def upgrade_one_cli( self, package_id: str, version: str | None = None, ) -> tuple[str, ...]: """Generates the CLI to upgrade one package. .. code-block:: shell-session $ mise upgrade node """ spec = f"{package_id}@{version}" if version else package_id return self.build_cli("upgrade", spec)
[docs] def remove(self, package_id: str) -> str: """Remove one package. ``mise uninstall <tool>`` errors when more than one version of the tool is installed. ``--all`` removes every installed version unconditionally, which matches ``mpm``'s "remove this package" contract. .. code-block:: shell-session $ mise uninstall --all node """ return self.run_cli("uninstall", "--all", package_id)
[docs] def sync(self) -> None: """Refresh plugin metadata. ``mise`` resolves tool listings and version catalogues through its plugins, so updating the plugins is the closest equivalent to the package-list refresh other managers perform during sync. .. code-block:: shell-session $ mise plugins update """ self.run_cli("plugins", "update")
[docs] def cleanup(self) -> None: """Clear ``mise``'s download and metadata caches. .. code-block:: shell-session $ mise cache clear """ self.run_cli("cache", "clear")