Skip to content

feat(node): support package.json as idiomatic version file#8059

Merged
jdx merged 1 commit intomainfrom
feat/package-json-idiomatic-versions
Feb 8, 2026
Merged

feat(node): support package.json as idiomatic version file#8059
jdx merged 1 commit intomainfrom
feat/package-json-idiomatic-versions

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Feb 8, 2026

Summary

  • Read tool versions from package.json devEngines and packageManager fields (closes Support `package.json#devEngines` as an idiomatic Node.js version file #7379)
  • Support devEngines.runtime for node/bun version detection
  • Support devEngines.packageManager and packageManager field for pnpm/yarn/npm version detection
  • devEngines takes priority over packageManager field when both are present
  • Semver ranges are simplified to mise-compatible prefixes (>=18.0.018, ^20.0.020)
  • Gracefully skip backends when package.json doesn't contain their version info (multiple backends claim the same file)

Test plan

  • Unit tests for simplify_semver covering all range operators, wildcards, and exact versions
  • Unit tests for runtime_version and package_manager_version extraction
  • Unit tests for priority logic (devEngines overrides packageManager field)
  • Unit tests for missing/empty fields returning None
  • E2E test e2e/core/test_package_json covering node devEngines, pnpm packageManager, and priority
  • Manual: create project with package.json containing devEngines/packageManager, verify mise ls shows correct versions

🤖 Generated with Claude Code


Note

Medium Risk
Changes how tool versions are discovered from idiomatic files and adds parsing of package.json, which could affect version resolution when multiple tools claim the same file or when semver ranges are simplified.

Overview
Adds support for using package.json as an idiomatic version file to auto-detect tool versions.

Introduces a new PackageJson parser that extracts versions from devEngines.runtime (for runtimes like node/bun) and from devEngines.packageManager or packageManager (for package managers like pnpm/yarn/npm), with semver ranges simplified into mise-friendly prefixes.

Updates core node/bun plugins and registry-backed package manager tools to include package.json in idiomatic_files, and changes idiomatic file parsing to skip backends that can’t extract a version from the shared file instead of failing. Adds unit coverage for parsing/semver simplification and an e2e test for node+pnpm detection and precedence.

Written by Cursor Bugbot for commit 1f4a630. This will update automatically on new commits. Configure here.

Copilot AI review requested due to automatic review settings February 8, 2026 16:03
@jdx jdx changed the title feat: support package.json as idiomatic version file feat(node): support package.json as idiomatic version file Feb 8, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances mise's integration with JavaScript ecosystems by introducing support for package.json as an idiomatic version file. It allows mise to automatically detect and apply versions for Node.js runtimes and various package managers (npm, pnpm, Yarn) by parsing the devEngines and packageManager fields. This change aims to provide a more seamless and intuitive experience for developers working on JavaScript projects, aligning mise with common project configuration practices.

Highlights

  • package.json as Idiomatic Version File: mise now supports package.json as a source for tool versions, enabling more idiomatic configuration for JavaScript projects.
  • Version Extraction Logic: Implemented logic to extract Node/Bun versions from devEngines.runtime and npm/pnpm/yarn versions from devEngines.packageManager or the packageManager field.
  • Priority Handling: devEngines fields are prioritized over the top-level packageManager field when both are present.
  • Semver Simplification: Semver ranges (e.g., >=18.0.0, ^20.0.0) are simplified to mise-compatible prefixes (e.g., 18, 20).
  • Robust Backend Handling: Backends gracefully skip package.json if it doesn't contain relevant version information for them, preventing conflicts when multiple backends might claim the file.
Changelog
  • e2e/core/test_package_json
    • Added a new end-to-end test to validate the correct parsing and application of Node and pnpm versions from package.json, including the devEngines override logic.
  • registry/npm.toml
    • Updated to include package.json in its list of idiomatic_files.
  • registry/pnpm.toml
    • Updated to include package.json in its list of idiomatic_files.
  • registry/yarn.toml
    • Updated to include package.json in its list of idiomatic_files.
  • src/backend/mod.rs
    • Modified the parse_idiomatic_file trait method to specifically handle package.json for package managers, delegating parsing to the new package_json module.
  • src/config/config_file/idiomatic_version.rs
    • Enhanced the IdiomaticVersionFile parsing logic to gracefully handle errors when a backend attempts to parse an idiomatic file but finds no relevant version, allowing other backends to process the file.
  • src/main.rs
    • Added a new module import for package_json.
  • src/package_json.rs
    • Introduced a new module responsible for parsing package.json files, extracting runtime versions (e.g., Node, Bun) and package manager versions (e.g., npm, pnpm, Yarn), and simplifying semver ranges into mise-compatible formats. Includes comprehensive unit tests for its functionality.
  • src/plugins/core/bun.rs
    • Modified the Bun plugin to recognize package.json as an idiomatic version file and implemented logic to extract Bun versions from the devEngines.runtime field.
  • src/plugins/core/node.rs
    • Modified the Node plugin to recognize package.json as an idiomatic version file and implemented logic to extract Node versions from the devEngines.runtime field.
Activity
  • The pull request was authored by jdx.
  • A detailed summary and test plan are provided in the PR description.
  • Unit tests have been implemented for semver simplification, version extraction from package.json, and priority logic between devEngines and packageManager fields.
  • An E2E test (e2e/core/test_package_json) has been added to verify the new functionality.
  • Manual testing is suggested to confirm mise ls displays correct versions from package.json in a project.
  • The PR description indicates it was generated with Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for using package.json as an idiomatic version file, which is a great feature for the Node.js ecosystem. The implementation includes a new package_json.rs module for parsing devEngines and packageManager fields, along with comprehensive unit and end-to-end tests. The changes are well-structured and the logic for gracefully skipping backends when version information is missing is a good design choice.

My review includes a couple of suggestions to refactor parts of the new parsing logic in package_json.rs to be more idiomatic and concise by leveraging Rust's Option combinators. These changes should improve the maintainability of the new code.

Comment on lines +36 to +46
pub fn runtime_version(&self, tool_name: &str) -> Option<String> {
let runtime = self.dev_engines.as_ref()?.runtime.as_ref()?;
if runtime.name != tool_name {
return None;
}
let version = simplify_semver(runtime.version.as_deref()?);
if version.is_empty() {
return None;
}
Some(version)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This function can be made more concise and idiomatic by using Option's combinator methods. This improves readability by chaining the operations in a functional style.

    pub fn runtime_version(&self, tool_name: &str) -> Option<String> {
        self.dev_engines.as_ref()
            .and_then(|de| de.runtime.as_ref())
            .filter(|r| r.name == tool_name)
            .and_then(|r| r.version.as_deref())
            .map(simplify_semver)
            .filter(|v| !v.is_empty())
    }

Comment on lines +50 to +78
pub fn package_manager_version(&self, tool_name: &str) -> Option<String> {
// Try devEngines.packageManager first
if let Some(dev_engines) = &self.dev_engines {
if let Some(pm) = &dev_engines.package_manager {
if pm.name == tool_name {
if let Some(version) = &pm.version {
let v = simplify_semver(version);
if !v.is_empty() {
return Some(v);
}
}
}
}
}

// Fall back to packageManager field (e.g. "pnpm@9.1.0+sha256.abc")
let pm_field = self.package_manager.as_deref()?;
let (name, rest) = pm_field.split_once('@')?;
if name != tool_name {
return None;
}
// Strip +sha... suffix
let version = rest.split('+').next().unwrap_or(rest);
let version = version.trim();
if version.is_empty() {
return None;
}
Some(version.to_string())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This function can be refactored to be more idiomatic and readable. The nested if statements for checking devEngines can be flattened using Option combinators. The fallback logic can then be chained with or_else for a cleaner implementation.

    pub fn package_manager_version(&self, tool_name: &str) -> Option<String> {
        // Try devEngines.packageManager first
        self.dev_engines.as_ref()
            .and_then(|de| de.package_manager.as_ref())
            .filter(|pm| pm.name == tool_name)
            .and_then(|pm| pm.version.as_deref())
            .map(simplify_semver)
            .filter(|v| !v.is_empty())
            .or_else(|| {
                // Fall back to packageManager field (e.g. "pnpm@9.1.0+sha256.abc")
                let pm_field = self.package_manager.as_deref()?;
                let (name, rest) = pm_field.split_once('@')?;
                if name != tool_name {
                    return None;
                }
                // Strip +sha... suffix
                let version = rest.split('+').next().unwrap_or(rest);
                let version = version.trim();
                if version.is_empty() {
                    return None;
                }
                Some(version.to_string())
            })
    }

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for extracting runtime and package-manager tool versions from package.json so mise can treat it as an idiomatic version file for node/bun and npm/pnpm/yarn.

Changes:

  • Add package.json to idiomatic filename detection for node/bun and package managers.
  • Introduce PackageJson parsing + semver-range simplification helpers for version extraction.
  • Adjust idiomatic version file aggregation to skip backends that can’t extract a relevant version.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/plugins/core/node.rs Treats package.json as an idiomatic source for devEngines.runtime node version.
src/plugins/core/bun.rs Treats package.json as an idiomatic source for devEngines.runtime bun version.
src/package_json.rs New parser + helpers to extract devEngines/packageManager versions and simplify semver ranges.
src/main.rs Registers the new package_json module.
src/config/config_file/idiomatic_version.rs Changes aggregation to skip plugins that fail parsing/extraction.
src/backend/mod.rs Default idiomatic parser now supports extracting versions from package.json for package managers.
registry/yarn.toml Declares package.json as an idiomatic file for yarn.
registry/pnpm.toml Declares package.json as an idiomatic file for pnpm.
registry/npm.toml Declares package.json as an idiomatic file for npm.
e2e/core/test_package_json E2E coverage for node/pnpm extraction + priority behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +35 to +36
trace!("skipping {} for {}: {e}", plugin.id(), path.display());
continue;
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

This change skips all errors from parse_idiomatic_file, not just the expected "no relevant version info" case. That can silently hide real failures (e.g., unreadable file, invalid JSON, permission issues) across any idiomatic version file type. Consider only continue-ing for a specific, typed error (e.g., a custom NoVersionFound error) and propagating unexpected parse/IO/JSON errors so users still see actionable failures.

Suggested change
trace!("skipping {} for {}: {e}", plugin.id(), path.display());
continue;
trace!("error parsing idiomatic version file with {} for {}: {e}", plugin.id(), path.display());
return Err(e);

Copilot uses AI. Check for mistakes.
Comment on lines +821 to +826
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()));
}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

When multiple backends claim package.json, this will read + JSON-parse the same file once per backend. With node/bun plus several package managers enabled, that can add noticeable overhead in large repos. Consider caching the parsed PackageJson (or even raw file contents) per path during idiomatic file processing so it’s parsed once and reused across backends.

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +105
pub fn simplify_semver(input: &str) -> String {
let input = input.trim();
if input == "*" || input == "x" {
return "latest".to_string();
}

// Strip leading range operators
let version = input
.trim_start_matches(">=")
.trim_start_matches("<=")
.trim_start_matches('>')
.trim_start_matches('<')
.trim_start_matches('^')
.trim_start_matches('~')
.trim_start_matches('=')
.trim();
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

Despite the PR description mentioning wildcard support, simplify_semver doesn’t normalize common wildcard forms like 18.x, 18.*, or 18.2.x (with or without operators). As written, these will be returned as-is and may not be mise-compatible. Consider explicitly handling wildcard segments by truncating to the non-wildcard prefix (e.g., 18.x18, 18.2.*18.2) and add unit tests for those cases.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +26
#[derive(Debug, Deserialize)]
struct DevEngine {
name: String,
version: Option<String>,
}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

Deserialization will fail if a devEngines.runtime / devEngines.packageManager object is present but missing name (or has an unexpected shape), which can cause the whole package.json parse to error and block extraction for all tools. If the goal is to be resilient to partial/invalid devEngines blocks, consider making name an Option<String> (and treating missing/empty as non-matching) so the parser can still succeed and simply return None for version lookups.

Copilot uses AI. Check for mistakes.
@jdx jdx force-pushed the feat/package-json-idiomatic-versions branch from a53b501 to fcf2561 Compare February 8, 2026 16:10
@jdx
Copy link
Owner Author

jdx commented Feb 8, 2026

bugbot run

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is ON, but a Cloud Agent failed to start.

@github-actions
Copy link

github-actions bot commented Feb 8, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.7 x -- echo 22.2 ± 0.7 20.3 24.4 1.00
mise x -- echo 23.3 ± 1.1 21.4 27.0 1.05 ± 0.06

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.7 env 20.8 ± 0.7 19.5 24.9 1.00
mise env 23.2 ± 0.7 21.2 24.8 1.12 ± 0.05
⚠️ Warning: Performance variance for env is 12%

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.7 hook-env 22.4 ± 0.6 21.0 27.1 1.01 ± 0.04
mise hook-env 22.1 ± 0.5 21.4 25.2 1.00

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.7 ls 19.0 ± 0.3 18.2 22.3 1.00
mise ls 20.3 ± 0.8 19.4 27.7 1.07 ± 0.04

xtasks/test/perf

Command mise-2026.2.7 mise Variance
install (cached) 121ms 123ms -1%
ls (cached) 75ms 77ms -2%
bin-paths (cached) 78ms 81ms -3%
task-ls (cached) 535ms 536ms +0%

Read tool versions from package.json devEngines and packageManager fields.
Supports devEngines.runtime (node/bun), devEngines.packageManager, and
the packageManager field (pnpm/yarn/npm) with semver range simplification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx force-pushed the feat/package-json-idiomatic-versions branch from 666db5f to 1f4a630 Compare February 8, 2026 16:38
@jdx jdx merged commit 3439a73 into main Feb 8, 2026
34 of 35 checks passed
@jdx jdx deleted the feat/package-json-idiomatic-versions branch February 8, 2026 16:54
mise-en-dev added a commit that referenced this pull request Feb 9, 2026
### 🚀 Features

- **(node)** support package.json as idiomatic version file by @jdx in
[#8059](#8059)
- **(ruby)** graduate precompiled ruby from experimental (gradual
rollout) by @jdx in [#8052](#8052)
- add --dry-run-code flag to exit non-zero when there is work to do by
@jdx in [#8063](#8063)

### 🐛 Bug Fixes

- **(core)** respect MISE_ARCH override in bun and erlang plugins by
@jdx in [#8062](#8062)
- **(hooks)** resolve 12 community-reported hooks issues by @jdx in
[#8058](#8058)
- accept key=value format in set/add subcommands by @jdx in
[#8053](#8053)

### 📚 Documentation

- bump action versions in GitHub Actions examples by @muzimuzhi in
[#8065](#8065)
- add opengraph meta tags by @jdx in
[#8066](#8066)

### 📦️ Dependency Updates

- upgrade toml to 0.9 and toml_edit to 0.24 (TOML 1.1) by @jdx in
[#8057](#8057)

### 📦 Registry

- add quicktype (npm:quicktype) by @zdunecki in
[#8054](#8054)
- use inline table for test definitions by @jdx in
[#8056](#8056)
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
## Summary

- Read tool versions from `package.json` `devEngines` and
`packageManager` fields (closes jdx#7379)
- Support `devEngines.runtime` for node/bun version detection
- Support `devEngines.packageManager` and `packageManager` field for
pnpm/yarn/npm version detection
- `devEngines` takes priority over `packageManager` field when both are
present
- Semver ranges are simplified to mise-compatible prefixes (`>=18.0.0` →
`18`, `^20.0.0` → `20`)
- Gracefully skip backends when `package.json` doesn't contain their
version info (multiple backends claim the same file)

## Test plan

- [x] Unit tests for `simplify_semver` covering all range operators,
wildcards, and exact versions
- [x] Unit tests for `runtime_version` and `package_manager_version`
extraction
- [x] Unit tests for priority logic (devEngines overrides packageManager
field)
- [x] Unit tests for missing/empty fields returning None
- [x] E2E test `e2e/core/test_package_json` covering node devEngines,
pnpm packageManager, and priority
- [ ] Manual: create project with `package.json` containing
`devEngines`/`packageManager`, verify `mise ls` shows correct versions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes how tool versions are discovered from idiomatic files and adds
parsing of `package.json`, which could affect version resolution when
multiple tools claim the same file or when semver ranges are simplified.
> 
> **Overview**
> Adds support for using `package.json` as an *idiomatic version file*
to auto-detect tool versions.
> 
> Introduces a new `PackageJson` parser that extracts versions from
`devEngines.runtime` (for runtimes like `node`/`bun`) and from
`devEngines.packageManager` or `packageManager` (for package managers
like `pnpm`/`yarn`/`npm`), with semver ranges simplified into
mise-friendly prefixes.
> 
> Updates core `node`/`bun` plugins and registry-backed package manager
tools to include `package.json` in `idiomatic_files`, and changes
idiomatic file parsing to *skip* backends that can’t extract a version
from the shared file instead of failing. Adds unit coverage for
parsing/semver simplification and an e2e test for node+pnpm detection
and precedence.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1f4a630. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
### 🚀 Features

- **(node)** support package.json as idiomatic version file by @jdx in
[jdx#8059](jdx#8059)
- **(ruby)** graduate precompiled ruby from experimental (gradual
rollout) by @jdx in [jdx#8052](jdx#8052)
- add --dry-run-code flag to exit non-zero when there is work to do by
@jdx in [jdx#8063](jdx#8063)

### 🐛 Bug Fixes

- **(core)** respect MISE_ARCH override in bun and erlang plugins by
@jdx in [jdx#8062](jdx#8062)
- **(hooks)** resolve 12 community-reported hooks issues by @jdx in
[jdx#8058](jdx#8058)
- accept key=value format in set/add subcommands by @jdx in
[jdx#8053](jdx#8053)

### 📚 Documentation

- bump action versions in GitHub Actions examples by @muzimuzhi in
[jdx#8065](jdx#8065)
- add opengraph meta tags by @jdx in
[jdx#8066](jdx#8066)

### 📦️ Dependency Updates

- upgrade toml to 0.9 and toml_edit to 0.24 (TOML 1.1) by @jdx in
[jdx#8057](jdx#8057)

### 📦 Registry

- add quicktype (npm:quicktype) by @zdunecki in
[jdx#8054](jdx#8054)
- use inline table for test definitions by @jdx in
[jdx#8056](jdx#8056)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants