feat(node): support package.json as idiomatic version file#8059
Conversation
Summary of ChangesHello @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
Changelog
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
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.
| 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) | ||
| } |
There was a problem hiding this comment.
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())
}| 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()) | ||
| } |
There was a problem hiding this comment.
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())
})
}There was a problem hiding this comment.
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.jsonto idiomatic filename detection for node/bun and package managers. - Introduce
PackageJsonparsing + 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.
| trace!("skipping {} for {}: {e}", plugin.id(), path.display()); | ||
| continue; |
There was a problem hiding this comment.
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.
| 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); |
| 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())); | ||
| } |
There was a problem hiding this comment.
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.
| 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(); |
There was a problem hiding this comment.
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.x → 18, 18.2.* → 18.2) and add unit tests for those cases.
src/package_json.rs
Outdated
| #[derive(Debug, Deserialize)] | ||
| struct DevEngine { | ||
| name: String, | ||
| version: Option<String>, | ||
| } |
There was a problem hiding this comment.
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.
a53b501 to
fcf2561
Compare
|
bugbot run |
Hyperfine Performance
|
| 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 |
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>
666db5f to
1f4a630
Compare
### 🚀 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)
## 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>
### 🚀 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)
Summary
package.jsondevEnginesandpackageManagerfields (closes Support `package.json#devEngines` as an idiomatic Node.js version file #7379)devEngines.runtimefor node/bun version detectiondevEngines.packageManagerandpackageManagerfield for pnpm/yarn/npm version detectiondevEnginestakes priority overpackageManagerfield when both are present>=18.0.0→18,^20.0.0→20)package.jsondoesn't contain their version info (multiple backends claim the same file)Test plan
simplify_semvercovering all range operators, wildcards, and exact versionsruntime_versionandpackage_manager_versionextractione2e/core/test_package_jsoncovering node devEngines, pnpm packageManager, and prioritypackage.jsoncontainingdevEngines/packageManager, verifymise lsshows 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.jsonas an idiomatic version file to auto-detect tool versions.Introduces a new
PackageJsonparser that extracts versions fromdevEngines.runtime(for runtimes likenode/bun) and fromdevEngines.packageManagerorpackageManager(for package managers likepnpm/yarn/npm), with semver ranges simplified into mise-friendly prefixes.Updates core
node/bunplugins and registry-backed package manager tools to includepackage.jsoninidiomatic_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.