Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
542b03d
fix(idiomatic): use generic parser for idiomatic files
risu729 Feb 16, 2026
f82fa88
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 16, 2026
7a89923
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Feb 16, 2026
01512a7
style: simplify bun parse
risu729 Feb 16, 2026
18a816f
fix: trim in java.rs
risu729 Feb 16, 2026
5de1639
Revert "fix: trim in java.rs"
risu729 Feb 16, 2026
009fcff
fix: fallback to other parsers if no versions are found
risu729 Feb 16, 2026
bddff77
Revert "fix: fallback to other parsers if no versions are found"
risu729 Feb 17, 2026
3ed16a1
fix: trim .java-version content
risu729 Feb 17, 2026
49bbafd
fix: compile error
risu729 Feb 17, 2026
fe98d1d
fix: allow all idiomatic files in IdiomaticVersionFile::from_file
risu729 Feb 17, 2026
0c4bf84
style: add comment in package.json
risu729 Feb 17, 2026
29a247b
fix(node): return emtpy versions array for empty idiomatic file
risu729 Feb 17, 2026
3320f27
fix(idimatic): dedup idiomatic filenames for a tool
risu729 Feb 17, 2026
2fc0446
fix: try next plugin if a plugin failed to parse a idiomatic file
risu729 Feb 17, 2026
336d4d8
fix: fallback to raw text parser only if custom parser isn't implemen…
risu729 Feb 18, 2026
81e3ad9
style: fix typo
risu729 Feb 18, 2026
5607857
fix: restore debug logging
risu729 Feb 18, 2026
38d120f
fix: don't fall back to raw text parser
risu729 Feb 23, 2026
7ce45c8
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 23, 2026
0871d67
refactor: move package.json parse into backend/mod.rs
risu729 Feb 27, 2026
2baa1f6
fix: ignore empty strings in idiomatic version files
risu729 Feb 28, 2026
e37b8a1
fix(idiomatic): restore normalize_idiomatic_contents to default idiom…
risu729 Mar 2, 2026
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
37 changes: 21 additions & 16 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,22 +414,27 @@ other developers to use a specific tool like mise or asdf.
They support aliases, which means you can have an `.nvmrc` file with `lts/hydrogen` and it will work
in mise and nvm. Here are some of the supported idiomatic version files:

| Plugin | Idiomatic Files |
| ---------- | ------------------------------------- |
| atmos | `.atmos-version` |
| crystal | `.crystal-version` |
| elixir | `.exenv-version` |
| go | `.go-version` |
| java | `.java-version`, `.sdkmanrc` |
| node | `.nvmrc`, `.node-version` |
| opentofu | `.opentofu-version` |
| packer | `.packer-version` |
| python | `.python-version`, `.python-versions` |
| ruby | `.ruby-version`, `Gemfile` |
| terraform | `.terraform-version`, `main.tf` |
| terragrunt | `.terragrunt-version` |
| terramate | `.terramate-version` |
| yarn | `.yvmrc` |
| Plugin | Idiomatic Files |
| ---------- | ----------------------------------------- |
| atmos | `.atmos-version` |
| bun | `.bun-version`, `package.json` |
| crystal | `.crystal-version` |
| deno | `.deno-version`, `package.json` |
| dotnet | `global.json` |
| elixir | `.exenv-version` |
| go | `.go-version` |
| java | `.java-version`, `.sdkmanrc` |
| node | `.nvmrc`, `.node-version`, `package.json` |
| npm | `package.json` |
| opentofu | `.opentofu-version` |
| packer | `.packer-version` |
| pnpm | `package.json` |
| python | `.python-version`, `.python-versions` |
| ruby | `.ruby-version`, `Gemfile` |
| terraform | `.terraform-version`, `main.tf` |
| terragrunt | `.terragrunt-version` |
| terramate | `.terramate-version` |
| yarn | `.yvmrc`, `package.json` |

In mise, these are disabled by default, see <https://github.com/jdx/mise/discussions/4345> for rationale.

Expand Down
14 changes: 10 additions & 4 deletions src/backend/asdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ impl Backend for AsdfBackend {
Ok(aliases)
}

async fn idiomatic_filenames(&self) -> Result<Vec<String>> {
async fn _idiomatic_filenames(&self) -> Result<Vec<String>> {
if let Some(data) = &self.toml.list_idiomatic_filenames.data {
return Ok(self.plugin.parse_idiomatic_filenames(data));
}
Expand All @@ -326,9 +326,9 @@ impl Backend for AsdfBackend {
.cloned()
}

async fn parse_idiomatic_file(&self, idiomatic_file: &Path) -> Result<String> {
async fn _parse_idiomatic_file(&self, idiomatic_file: &Path) -> Result<Vec<String>> {
if let Some(cached) = self.fetch_cached_idiomatic_file(idiomatic_file)? {
return Ok(cached);
return Ok(cached.split_whitespace().map(|s| s.to_string()).collect());
}
trace!(
"parsing idiomatic file: {}",
Expand All @@ -343,7 +343,13 @@ impl Backend for AsdfBackend {
let idiomatic_version = normalize_idiomatic_contents(&idiomatic_version);

self.write_idiomatic_cache(idiomatic_file, &idiomatic_version)?;
Ok(idiomatic_version)
if idiomatic_version.is_empty() {
return Ok(vec![]);
}
Ok(idiomatic_version
.split_whitespace()
.map(|s| s.to_string())
.collect())
}

fn plugin(&self) -> Option<&PluginEnum> {
Expand Down
55 changes: 45 additions & 10 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -915,22 +915,57 @@ pub trait Backend: Debug + Send + Sync {
fn get_aliases(&self) -> eyre::Result<BTreeMap<String, String>> {
Ok(BTreeMap::new())
}

/// Returns a list of idiomatic filenames for this tool.
///
/// This method is additive:
/// 1. It calls `_idiomatic_filenames` to get backend-specific filenames.
/// 2. It checks the Registry for any additional filenames defined there.
async fn idiomatic_filenames(&self) -> Result<Vec<String>> {
Ok(REGISTRY
.get(self.id())
.map(|rt| rt.idiomatic_files.iter().map(|s| s.to_string()).collect())
.unwrap_or_default())
let mut filenames = self._idiomatic_filenames().await?;
if let Some(rt) = REGISTRY.get(self.id()) {
filenames.extend(rt.idiomatic_files.iter().map(|s| s.to_string()));
}
filenames = filenames.into_iter().unique().collect();
Ok(filenames)
}

/// Backend-specific implementation for `idiomatic_filenames`.
/// Override this to provide native idiomatic filenames for the backend.
async fn _idiomatic_filenames(&self) -> Result<Vec<String>> {
Ok(vec![])
}
async fn parse_idiomatic_file(&self, path: &Path) -> eyre::Result<String> {

/// Parses an idiomatic version file to extract the version.
///
/// This handles special files like `package.json` which are parsed natively to avoid
/// every backend needing to implement `package.json` support. For other files, it
/// delegates to `_parse_idiomatic_file`.
async fn parse_idiomatic_file(&self, path: &Path) -> eyre::Result<Vec<String>> {
if path.file_name().is_some_and(|f| f == "package.json") {
let pkg = crate::package_json::PackageJson::parse(path)?;
return pkg
.package_manager_version(self.id())
.ok_or_else(|| eyre::eyre!("no {} version found in package.json", self.id()));
return crate::config::config_file::idiomatic_version::package_json::parse(
path,
self.id(),
);
}
self._parse_idiomatic_file(path).await
}

/// Backend-specific implementation for `parse_idiomatic_file`.
/// Default implementation reads the file and treats each whitespace-separated token as a version.
/// Override to provide format-specific parsing; return `Err` on real failures so the plugin is skipped.
async fn _parse_idiomatic_file(&self, path: &Path) -> eyre::Result<Vec<String>> {
let contents = file::read_to_string(path)?;
Ok(normalize_idiomatic_contents(&contents))
let normalized = normalize_idiomatic_contents(&contents);
if normalized.is_empty() {
return Ok(vec![]);
}
Ok(normalized
.split_whitespace()
.map(|s| s.to_string())
.collect())
}
Comment thread
risu729 marked this conversation as resolved.

fn plugin(&self) -> Option<&PluginEnum> {
None
}
Expand Down
20 changes: 12 additions & 8 deletions src/backend/vfox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,20 @@ impl Backend for VfoxBackend {
Some(&self.plugin_enum)
}

async fn parse_idiomatic_file(&self, path: &Path) -> eyre::Result<String> {
async fn _idiomatic_filenames(&self) -> eyre::Result<Vec<String>> {
let (vfox, _log_rx) = self.plugin.vfox();

let metadata = vfox.metadata(&self.pathname).await?;
Ok(metadata.legacy_filenames)
}

async fn _parse_idiomatic_file(&self, path: &Path) -> eyre::Result<Vec<String>> {
let (vfox, _log_rx) = self.plugin.vfox();
let response = vfox.parse_legacy_file(&self.pathname, path).await?;
response.version.ok_or_else(|| {
eyre::eyre!(
"Version for {} not found in '{}'",
self.pathname,
path.display()
)
})
if let Some(version) = response.version {
return Ok(version.split_whitespace().map(|s| s.to_string()).collect());
}
Ok(vec![])
}

async fn get_tarball_url(
Expand Down
104 changes: 0 additions & 104 deletions src/config/config_file/idiomatic_version.rs

This file was deleted.

Loading
Loading