Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fcb42d6
feat(backend): add filter_bins option to github/gitlab backends
risu729 Nov 27, 2025
fdc5f15
feat(registry): Add github:pypa/hatch with filter_bins for Python iso…
risu729 Nov 27, 2025
e409fca
fix(backend): Use wrapper scripts for filter_bins on Unix to support …
risu729 Nov 27, 2025
2df024a
feat(test): Merge hatch filter_bins test into github filter_bins test
risu729 Nov 27, 2025
c69f197
fix(backend): Use batch wrappers for filter_bins on Windows
risu729 Nov 27, 2025
b208d20
docs: document Windows batch wrapper behavior for filter_bins
risu729 Nov 27, 2025
558b90a
fix(backend): Add fallback for non-Unix/non-Windows OS in filter_bins
risu729 Nov 27, 2025
30562b5
revert: Remove wrapper script logic from filter_bins and update docs
risu729 Nov 28, 2025
221f8a9
fix: Update filter_bins docs with pandoc example and revert wrappers
risu729 Nov 28, 2025
152530b
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 28, 2025
0deb871
docs: Remove mentions of filtering documentation from filter_bins docs
risu729 Nov 28, 2025
0fc6522
refactor(backend): Move bin_path resolution to discover_bin_paths in …
risu729 Nov 28, 2025
f87fbc1
Update docs/dev-tools/backends/github.md
risu729 Nov 28, 2025
6d085fd
Update docs/dev-tools/backends/gitlab.md
risu729 Nov 28, 2025
7148d49
test: simplify test
risu729 Nov 28, 2025
abc4450
test: pass filter_bins option
risu729 Nov 28, 2025
b4a8328
docs: tweak
risu729 Nov 28, 2025
54ae448
feat(backend): Support platform-specific filter_bins option
risu729 Nov 28, 2025
4647df4
fix: fix style issues
risu729 Nov 28, 2025
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
14 changes: 14 additions & 0 deletions docs/dev-tools/backends/github.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,20 @@ bin_path = "{name}-{version}/bin" # expands to cli-1.0.0/bin
3. If no `bin/` directory exists, search subdirectories for `bin/` directories
4. If no `bin/` directories are found, use the root of the extracted directory

### `filter_bins`

Comma-separated list of binaries to symlink into a filtered `.mise-bins` directory. This is useful when the tool comes with extra binaries that you do not want to expose on PATH.

```toml
[tools]
"github:jgm/pandoc" = { version = "latest", filter_bins = "pandoc" }
```

When enabled:

- A `.mise-bins` subdirectory is created with symlinks only to the specified binaries
- Other binaries (like `pandoc-lua` or `pandoc-server`) are not exposed on PATH

### `api_url`

For GitHub Enterprise or self-hosted GitHub instances, specify the API URL:
Expand Down
14 changes: 14 additions & 0 deletions docs/dev-tools/backends/gitlab.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,20 @@ bin_path = "{name}-{version}/bin" # expands to gitlab-runner-1.0.0/bin
3. If no `bin/` directory exists, search subdirectories for `bin/` directories
4. If no `bin/` directories are found, use the root of the extracted directory

### `filter_bins`

Comma-separated list of binaries to symlink into a filtered `.mise-bins` directory. This is useful when the tool comes with extra binaries that you do not want to expose on PATH.

```toml
[tools]
"gitlab:myorg/mytool" = { version = "1.0.0", filter_bins = "mybin" }
```

When enabled:

- A `.mise-bins` subdirectory is created with symlinks only to the specified binaries
- Other binaries are not exposed on PATH

### `api_url`

For self-hosted GitLab instances, specify the API URL:
Expand Down
21 changes: 21 additions & 0 deletions e2e/backend/test_github_filter_bins
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash

# Test filter_bins option for github backend using pandoc.
# We use pandoc because it is a compiled binary and comes with multiple binaries to filter.

if [[ "$(uname -s)" != "Linux" ]]; then
echo "Skipping Linux-specific test on non-Linux OS"
exit 0
fi

export MISE_EXPERIMENTAL=1

mise install "github:jgm/pandoc[filter_bins=pandoc]@3.8.2"

bin_path="$(mise where github:jgm/[email protected])/.mise-bins"
assert_directory_exists "$bin_path"

bins=$(find "$bin_path" -maxdepth 1 \( -type l -o -type f \) -print0 | xargs -0 -n1 basename | sort | tr '\n' ' ')
assert_contains "echo '$bins'" "pandoc"
assert_not_contains "echo '$bins'" "pandoc-lua"
assert_not_contains "echo '$bins'" "pandoc-server"
79 changes: 71 additions & 8 deletions src/backend/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::backend::static_helpers::{
use crate::cli::args::BackendArg;
use crate::config::Config;
use crate::config::Settings;
use crate::file;
use crate::http::HTTP;
use crate::install_context::InstallContext;
use crate::toolset::ToolVersionOptions;
Expand Down Expand Up @@ -123,15 +124,11 @@ impl Backend for UnifiedGitBackend {
_config: &Arc<Config>,
tv: &ToolVersion,
) -> Result<Vec<std::path::PathBuf>> {
let opts = tv.request.options();
if let Some(bin_path_template) =
lookup_platform_key(&opts, "bin_path").or_else(|| opts.get("bin_path").cloned())
{
let bin_path = template_string(&bin_path_template, tv);
Ok(vec![tv.install_path().join(&bin_path)])
} else {
self.discover_bin_paths(tv)
if self.get_filter_bins(tv).is_some() {
return Ok(vec![tv.install_path().join(".mise-bins")]);
}

self.discover_bin_paths(tv)
}

fn resolve_lockfile_options(
Expand Down Expand Up @@ -277,11 +274,23 @@ impl UnifiedGitBackend {
install_artifact(tv, &file_path, opts, Some(ctx.pr.as_ref()))?;
self.verify_checksum(ctx, tv, &file_path)?;

if let Some(bins) = self.get_filter_bins(tv) {
self.create_symlink_bin_dir(tv, bins)?;
}

Ok(())
}

/// Discovers bin paths in the installation directory
fn discover_bin_paths(&self, tv: &ToolVersion) -> Result<Vec<std::path::PathBuf>> {
let opts = tv.request.options();
if let Some(bin_path_template) =
lookup_platform_key(&opts, "bin_path").or_else(|| opts.get("bin_path").cloned())
{
let bin_path = template_string(&bin_path_template, tv);
return Ok(vec![tv.install_path().join(&bin_path)]);
}

let bin_path = tv.install_path().join("bin");
if bin_path.exists() {
return Ok(vec![bin_path]);
Expand Down Expand Up @@ -533,6 +542,60 @@ impl UnifiedGitBackend {
tag_name.to_string()
}
}

fn get_filter_bins(&self, tv: &ToolVersion) -> Option<Vec<String>> {
let opts = tv.request.options();
let filter_bins = lookup_platform_key(&opts, "filter_bins")
.or_else(|| opts.get("filter_bins").cloned())?;

Some(
filter_bins
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect(),
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Empty filter_bins value hides all binaries unexpectedly

The get_filter_bins function returns Some(vec![]) when filter_bins is set to an empty or whitespace-only value. Since list_bin_paths checks .is_some() to decide whether to use the .mise-bins directory, an empty filter_bins causes the tool to be installed with no binaries exposed on PATH. Users who accidentally set filter_bins="" would see the installation succeed but be unable to run any commands. The function could return None when the resulting vector is empty to treat this as "option not set."

Fix in Cursor Fix in Web


/// Creates a `.mise-bins` directory with symlinks only to the binaries specified in filter_bins.
fn create_symlink_bin_dir(&self, tv: &ToolVersion, bins: Vec<String>) -> Result<()> {
let symlink_dir = tv.install_path().join(".mise-bins");
file::create_dir_all(&symlink_dir)?;

// Find where the actual binaries are
let install_path = tv.install_path();
let bin_paths = self.discover_bin_paths(tv)?;

// Collect all possible source directories (install root + discovered bin paths)
let mut src_dirs = bin_paths;
if !src_dirs.contains(&install_path) {
src_dirs.push(install_path);
}

for bin_name in bins {
// Find the binary in any of the source directories
let mut found = false;
for dir in &src_dirs {
let src = dir.join(&bin_name);
if src.exists() {
let dst = symlink_dir.join(&bin_name);
if !dst.exists() {
file::make_symlink_or_copy(&src, &dst)?;
}
found = true;
break;
}
}

if !found {
warn!(
"Could not find binary '{}' in install directories. Available paths: {:?}",
bin_name, src_dirs
);
}
}
Ok(())
}
}

#[cfg(test)]
Expand Down
Loading