Contributing
Before submitting a PR, unless it's something obvious, consider creating a discussion or simply mention what you plan to do in the Discord. PRs are often either rejected or need to change significantly after submission so make sure before you start working on something it won't be a wasted effort.
Contributing Guidelines
- Before starting: Create a discussion or discuss in Discord for non-obvious changes
- Test thoroughly: Ensure both unit and E2E tests pass
- Follow conventions: Use existing code style and patterns
- Update documentation: Add/update docs for new features
Pull Request Workflow
- PR titles: Must follow conventional commit format (validated automatically)
- For new tools in registry: Use
registry: add tool-name (backend:full/name)
- For new tools in registry: Use
- Auto-formatting: Code will be automatically formatted by autofix.ci
- CI checks: All tests must pass across Linux, macOS, and Windows
- Coverage: New code should maintain or improve test coverage
- Dependencies: New dependencies are validated with cargo-deny
Development Tips
- Disable mise during development: If you use mise in your shell, disable it when running tests to avoid conflicts
- Test specific features: Use
cargo test test_namefor targeted testing - Update snapshots: Use
mise run snapshotswhen changing test outputs - Rate limiting: Set
MISE_GITHUB_TOKENto avoid GitHub API rate limits during development
Packaging and Self-Update Instructions
When mise is installed via a package manager, in-app self-update is disabled and users should update via their package manager. Packaging should install a TOML file with platform-specific instructions at lib/mise-self-update-instructions.toml (or lib/mise/mise-self-update-instructions.toml). Example contents:
# Debian/Ubuntu (APT)
message = "To update mise from the APT repository, run:\n\n sudo apt update && sudo apt install --only-upgrade mise\n"# Fedora/CentOS Stream (DNF)
message = "To update mise from COPR, run:\n\n sudo dnf upgrade mise\n"Testing
mise has a comprehensive test suite with multiple types of tests to ensure reliability and functionality across different platforms and scenarios.
Unit Tests
Unit tests are fast, focused tests for individual components and functions:
# Run all unit tests
cargo test --all-features
# Run specific unit tests
cargo test <test_name>Unit test structure:
- Located in
src/directory alongside source code - Use Rust's built-in test framework
- Test individual functions and modules
- Fast execution (used for quick feedback during development)
E2E Tests
End-to-end tests validate the complete functionality of mise in realistic scenarios:
# Run all E2E tests
mise run test:e2e
# Run specific E2E test
./e2e/run_test test_name
# Run E2E tests matching pattern
./e2e/run_test task # runs tests matching *task*
# Run all tests including slow ones
TEST_ALL=1 mise run test:e2eE2E test structure:
- Located in
e2e/directory - Organized by functionality:
e2e/cli/- Command-line interface testse2e/core/- Core functionality testse2e/env/- Environment variable testse2e/tasks/- Task runner testse2e/config/- Configuration testse2e/tools/- Tool management testse2e/shell/- Shell integration testse2e/backend/- Backend testse2e/plugins/- Plugin tests
E2E test categories:
- Fast tests (
test_*): Run in normal test suites - Slow tests (
test_*_slow): Only run whenTEST_ALL=1is set - Isolated environment: Each test runs in a clean, isolated environment
Coverage Tests
Coverage tests measure how much of the codebase is covered by tests:
# Run coverage tests
mise run test:coverage
# Coverage tests run in parallel tranches for CI
TEST_TRANCHE=0 TEST_TRANCHE_COUNT=8 mise run test:coverageWindows E2E Tests
Windows has its own test suite written in PowerShell:
# Run all Windows E2E tests
pwsh e2e-win\run.ps1
# Run specific Windows tests
pwsh e2e-win\run.ps1 task # run tests matching *task*Plugin Tests
Test plugin functionality across different backends:
# Test specific plugin
mise test-tool ripgrep
# Test all plugins in registry
mise test-tool --all
# Test all plugins in config files
mise test-tool --all-config
# Test with parallel jobs
mise test-tool --all --jobs 4Test Environment Setup
Tests run in isolated environments to avoid conflicts:
# Disable mise during development testing
export MISE_DISABLE_TOOLS=1
# Run tests with specific environment
MISE_TRUSTED_CONFIG_PATHS=$PWD cargo testTest Assertions
The E2E tests use a custom assertion framework (e2e/assert.sh):
# Basic assertions
assert "command" "expected_output"
assert_contains "command" "substring"
assert_fail "command" "expected_error"
# JSON assertions
assert_json "command" '{"key": "value"}'
assert_json_partial_array "command" "fields" '[{...}]'
# File/directory assertions
assert_directory_exists "/path/to/dir"
assert_directory_not_exists "/path/to/dir"
assert_empty "command"Running Specific Test Categories
# Run all tests (unit + e2e)
mise run test
# Run only unit tests
mise run test:unit
# Run only e2e tests
mise run test:e2e
# Run tests with shuffle (for detecting order dependencies)
mise run test:shuffle
# Run nightly tests (with bleeding edge Rust)
rustup default nightly && mise run testRunning Individual Tests
Running Single Unit Tests
# Run a specific unit test by name
cargo test test_name
# Run tests matching a pattern
cargo test pattern
# Run tests in a specific module
cargo test module_name
# Run a single test with output
cargo test test_name -- --nocaptureRunning Single E2E Tests
# Run a specific E2E test by name
./e2e/run_test test_name
# Run E2E tests matching a pattern
mise run test:e2e pattern
# Examples:
./e2e/run_test test_use # Run specific test
./e2e/run_test test_config_set # Run config-related test
mise run test:e2e task # Run all tests matching "task"Testing Individual Plugins
# Test a specific plugin
mise test-tool ripgrep
# Test a plugin with verbose output
mise test-tool ripgrep --raw
# Test multiple plugins
mise test-tool ripgrep jq terraformPerformance Testing
# Run performance benchmarks
mise run test:perf
# Build performance test workspace
mise run test:build-perf-workspaceSnapshot Testing
Used for testing output consistency:
# Update test snapshots when output changes
mise run snapshots
# Use cargo-insta for snapshot testing
cargo insta test --accept --unreferenced deleteDevelopment Setup
Prerequisites
- Rust (latest stable, we don't use mise to manage rust)
- mise
Getting Started
# Clone the repository
git clone https://github.com/jdx/mise.git
cd mise
# Install dependencies
mise install
# Build the project
mise run buildDevelopment Shim
Create a development shim to easily run mise during development:
# Create ~/.local/bin/@mise
#!/bin/sh
exec cargo run -q --all-features --manifest-path ~/src/mise/Cargo.toml -- "$@"Then use @mise to run the development version:
@mise --help
eval "$(@mise activate zsh)"Project Structure
mise/
├── src/ # Main Rust source code
├── e2e/ # End-to-end tests
├── docs/ # Documentation
├── tasks.toml # Development tasks
├── mise.toml # Project configuration
├── Cargo.toml # Rust project configuration
└── xtasks/ # Additional build scriptsAvailable Development Tasks
Use mise tasks to see all available development tasks:
Common Tasks
mise run build- Build the projectmise run test- Run all tests (unit + E2E)mise run test:unit- Run unit tests onlymise run test:e2e- Run E2E tests onlymise run lint- Run lintingmise run lint:fix- Run linting with fixesmise run format- Format codemise run clean- Clean build artifactsmise run snapshots- Update test snapshotsmise run render- Generate documentation and completions
Documentation Tasks
mise run docs- Start documentation development servermise run docs:build- Build documentationmise run render:help- Generate help documentationmise run render:completions- Generate shell completions
Release Tasks
mise run release- Create a releasemise run ci- Run CI tasks (format, build, test)
Setup
Shouldn't require anything special I'm aware of, but mise run build is a good sanity check to run and make sure it's all working.
Pre-commit Hooks & Code Quality
mise uses hk as its git hook manager for linting and code quality checks. hk is a modern alternative to lefthook written by the same author as mise.
hk Configuration
The project uses hk.pkl (written in the Pkl configuration language) to define linting rules:
# Run all linting checks
hk check --all
# Run linting with fixes
hk fix --all
# Run specific linter
hk check --step shellcheckAvailable Linters in hk
- prettier: Code formatting for multiple languages
- clippy: Rust linting with
cargo clippy - shellcheck: Shell script linting
- shfmt: Shell script formatting
- pkl: Pkl configuration file validation
Using hk in Development
# Run linting (used in CI and pre-commit)
mise run lint # This runs hk check --all
# Run linting with fixes
hk fix --all
# Check specific file types
hk check --step prettier
hk check --step shellcheckSetting Up Pre-commit Hooks
# Set up git hooks to run hk on pre-commit
hk install --miseRunning Checks Manually
# Run all checks
hk check --all
# Run checks with fixes
hk fix --all
# Run checks on specific files
hk check --files="src/**/*.rs"Running the CLI
I use the following shim in ~/.local/bin/@mise:
#!/bin/sh
exec cargo run -q --all-features --manifest-path ~/src/mise/Cargo.toml -- "$@"INFO
Don't forget to change the manifest path to the correct path for your setup.
Then if that is in PATH just use @mise to run mise by compiling it on the fly.
@mise --help
eval "$(@mise activate zsh)"
@mise activate fish | sourceReleasing
Run mise run release -x [minor|patch]. (minor if it is the first release in a month)
Linting
- Lint codebase:
mise run lint - Lint and fix codebase:
mise run lint:fix
Generating readme and shell completion files
mise run renderDependency Management
mise uses several tools to validate dependencies and code quality:
- cargo-deny: Validates licenses, security advisories, and dependency duplicates
- cargo-msrv: Verifies minimum supported Rust version compatibility
- cargo-machete: Detects unused dependencies in Cargo.toml
These checks run automatically in CI and can be run locally:
# Run checks (tools are automatically available via mise.toml)
cargo deny check
cargo msrv verify
cargo machete --with-metadataConventional Commits
mise uses Conventional Commits for consistent commit messages and automated changelog generation. All commits should follow this format:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]Commit Types
- feat: New features (🚀 Features)
- fix: Bug fixes (🐛 Bug Fixes)
- refactor: Code refactoring (🚜 Refactor)
- docs: Documentation changes (📚 Documentation)
- style: Code style changes (🎨 Styling)
- perf: Performance improvements (⚡ Performance)
- test: Testing changes (🧪 Testing)
- chore: Maintenance tasks, dependency updates
- revert: Reverting previous changes (◀️ Revert)
Examples
feat(cli): add new command for listing plugins
fix(parser): handle edge case in version parsing
refactor(config): simplify configuration loading logic
docs(readme): update installation instructions
test(e2e): add tests for new plugin functionality
chore(deps): update dependencies to latest versionsScopes
Common scopes used in mise:
cli- Command line interface changesconfig- Configuration system changesparser- Parsing logic changesdeps- Dependency updatessecurity- Security-related changes
Breaking Changes
Breaking Change Policy
Breaking changes are rarely accepted into mise and are only performed in exceptional situations where there is no better alternative. When a breaking change is necessary, the process includes:
- CLI warnings: Users receive deprecation warnings in the CLI
- Migration period: Several months are provided for users to migrate
- Documentation: Clear migration guides are provided
- Community notice: Announcements in Discord and GitHub discussions
For breaking changes, add ! after the type or include BREAKING CHANGE: in the footer:
feat(api)!: remove deprecated configuration options
# OR
feat(api): remove deprecated configuration options
BREAKING CHANGE: The old configuration format is no longer supportedCI/CD & Pull Request Automation
mise uses several automated workflows to maintain code quality and streamline development:
Automated Code Formatting
- autofix.ci: Automatically formats code and fixes linting issues in PRs
- Runs
mise run renderandmise run lint-fixautomatically - Commits fixes directly to the PR branch
PR Title Validation
- semantic-pr-lint: Validates PR titles follow conventional commit format
- PR titles must match:
<type>[optional scope]: <description> - Example:
feat(cli): add new command for listing plugins
Continuous Integration
- Cross-platform testing: Ubuntu, macOS, and Windows
- Unit tests: Fast component-level tests
- E2E tests: Full integration testing with multiple test tranches
- Dependency validation:
cargo deny,cargo msrv,cargo machete
Release Automation
- release-plz: Automated release management based on conventional commits
- Automatically creates release PRs and publishes releases
- Runs daily via scheduled workflow
- Handles version bumping and changelog generation
Adding a new setting
To add a new setting, add it to settings.toml in the root of the project and run mise run render to update the codebase.
Adding Tools
Adding tools to mise involves adding entries to the registry/ file. This allows users to install tools using short names like mise use ripgrep instead of the full backend specification.
Quick Start
Choose the right backend for your tool:
- aqua - Preferred for GitHub releases with security features
- github - Simple GitHub releases following standard conventions
- Language package managers -
npm,pipx,cargo,gem, etc. for ecosystem-specific tools - Core tools - Built-in support for major languages (not user-contributed)
Add to registry/:
toml[tools.your-tool] description = "Brief description of the tool" backends = ["aqua:owner/repo", "github:owner/repo"] test = ["your-tool --version", "{{version}}"]Test the tool works properly with
mise test-tool your-tool
Guidelines and Requirements
When adding a new tool, the following requirements apply (automatically enforced by GitHub Actions workflow):
- A test is required in
registry/- Must include atestfield to verify installation. - Tools may be rejected if they are not notable - The tool should be reasonably popular and well-maintained. There are no specific guidelines for this and a lot of factors are taken into account. @jdx won't explain why a given tool wasn't accepted. Include a brief popularity summary (stars, downloads, recent release date) in the PR description so the policy can be applied without re-doing the research.
Backend acceptance tiers
Which backend you choose for a registry entry matters as much as which tool you add. Backends fall into the following tiers:
Tier 1 — preferred, routinely accepted: aqua, github, and gitlab.
- Prefer
aquawhen the tool is in the aqua registry — it has better UX, SLSA verification, and per-version logic. - Use
githubwhen the tool isn't in aqua but ships GitHub releases. - Use
gitlabfor tools released through GitLab.
Tier 2 — high bar, but lower than tier 3: conda.
Potentially accepted for tools that can't reasonably be supported via aqua/github. The bar is lower than tier 3 because mise's conda backend does not require a separately-installed package manager — packages are downloaded and extracted directly from anaconda.org, with no conda/mamba/micromamba needed on the user's PATH. The tool still needs to be popular and well-maintained.
Tier 3 — very high bar, rarely accepted: npm, pipx, gem, cargo, go, dotnet.
These all depend on a separately-installed runtime or toolchain being present on the user's PATH (node, python, ruby, cargo, go, dotnet), which is fragile — npm/pipx/gem in particular silently bind tools to whichever node/python/ruby happened to be on PATH at install time, which breaks when versions change or the runtime isn't installed. Accepted only when no aqua/github option exists and the tool is widely used. Discuss with @jdx before submitting.
Not accepted: asdf, vfox, ubi.
- New
asdfplugins — supply-chain security. Use aqua or github instead. - New
vfoxplugins — same reason. Use aqua/github instead. ubiis deprecated and is not accepted for new registry entries.
Users can still install via any backend themselves with explicit syntax (mise use vfox:owner/repo, mise use cargo:name, etc.) — they just don't get a registry shorthand for it.
Registry Format
The registry/ file uses this format:
# Tool name "your-tool" (becomes the short name for `mise use`)
[tools.your-tool]
description = "Tool description"
backends = [
"aqua:owner/repo", # Preferred backend first
"github:owner/repo", # Fallback backends
"npm:package-name" # Multiple backends supported
]
test = [
"your-tool --version", # Command to run
"{{version}}" # Expected output pattern
]
aliases = ["alt-name"] # Optional alternative names
os = ["linux", "macos"] # Optional OS restrictionsBackend Priority
List backends in order of preference. Users will get the first available backend, but can override with explicit syntax like mise use aqua:owner/repo.
Tool Testing
All tools must include a test to verify proper installation:
test = [
"command-to-run",
"expected-output-pattern"
]The test command should be reliable and the output pattern should use {{version}} to match any version number.
Registry Examples
Recent tool additions:
DuckDB: Simple github backend (#4248)
toml[tools.duckdb] backends = ["github:duckdb/duckdb"] test = ["duckdb --version", "{{version}}"]Biome: Multiple backends (#4283)
toml[tools.biome] backends = ["aqua:biomejs/biome", "github:biomejs/biome"] test = ["biome --version", "Version: {{version}}"]
Adding Backends
Backend vs Tool Confusion
Most contributors want to add tools, not backends. Before reading this section, make sure you actually need a new backend. Tools are individual software packages (like node or ripgrep), while backends are installation mechanisms (like aqua or github). If you want to add a specific tool to mise, see Adding Tools instead.
Core Backend Acceptance Policy
New backends are unlikely to be accepted into mise core. They require a lot of maintenance so it's generally better to use the backend plugin system to add backends without core changes. A new backend would only be accepted for a major package manager or tool that would greatly enhance mise's capabilities.
If you need a custom backend:
- Discuss with jdx first in Discord or by creating a discussion
- Consider if existing backends (github, aqua, npm, pipx, etc.) can meet your needs
- Create a plugin - use the plugin system to create plugins for private/custom tools without core changes. Start with the mise-tool-plugin-template for a quick setup
Most tool installation needs can be met by existing backends, especially github for GitHub releases and aqua for comprehensive package management.
Backends are mise's abstraction for different tool installation methods. Each backend implements the Backend trait to provide consistent functionality across different installation systems.
Backend Types
- Core Backends (
src/backend/core/) - Built-in language runtimes like Node.js, Python, Ruby - Package Manager Backends (
src/backend/) - npm, pipx, cargo, gem, go modules - Universal Installers (
src/backend/) - github, aqua for GitHub releases and package management - Plugin Backends (
src/backend/) - plugins can provide custom backends or individual tools
Implementation Steps
Create the backend module in
src/backend/(e.g.,my_backend.rs)Implement the Backend trait:
rustuse crate::backend::{Backend, BackendType}; use crate::install_context::InstallContext; #[derive(Debug)] pub struct MyBackend { // backend-specific fields } impl Backend for MyBackend { fn get_type(&self) -> BackendType { BackendType::MyBackend } async fn list_remote_versions(&self) -> Result<Vec<String>> { // Implementation for listing available versions } async fn install_version(&self, ctx: &InstallContext, tv: &ToolVersion) -> Result<()> { // Implementation for installing a specific version } async fn uninstall_version(&self, tv: &ToolVersion) -> Result<()> { // Implementation for uninstalling a version } // ... other required methods }Register the backend in
src/backend/mod.rs:- Add your backend to the imports
- Add it to the backend registry/factory function
- Add the
BackendTypeenum variant
Add CLI argument parsing in
src/cli/args/backend_arg.rsif neededUpdate the registry in
registry/if it should be available as a shorthand
Testing Requirements
- Integration tests in
e2e/backend/test_my_backend - Test both installation and usage of tools from your backend
- Windows testing if the backend supports Windows
Documentation
- Update backend documentation in
docs/dev-tools/backends/ - Add usage examples showing how to install tools with your backend
- Update the registry documentation if adding new shorthand tools
Implementation Examples
Look at existing backends for patterns:
src/backend/github.rs- Simple GitHub release installersrc/backend/npm.rs- Package manager integrationsrc/backend/core/node.rs- Full language runtime implementation
For detailed architecture information, see Backend Architecture.
Testing packaging
This is only necessary to test if actually changing the packaging setup.
Ubuntu (apt)
This is for arm64, but you can change the arch to amd64 if you want.
docker run -ti --rm ubuntu
apt update -y
apt install -y curl
install -dm 755 /etc/apt/keyrings
curl -fSso /etc/apt/keyrings/mise-archive-keyring.pub https://mise.en.dev/gpg-key.pub
echo "deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.pub arch=arm64] \
https://mise.en.dev/deb stable main" >/etc/apt/sources.list.d/mise.list
apt update -y
apt install -y mise
mise -VFedora (dnf)
docker run -ti --rm fedora
dnf copr enable -y jdxcode/mise && dnf install -y mise && mise -vRHEL (dnf)
docker run -ti --rm registry.access.redhat.com/ubi9/ubi:latest
dnf copr enable -y jdxcode/mise && dnf install -y mise && mise -v