Skip to content

fix: support npm semver ranges in devEngines#9061

Merged
jdx merged 6 commits intojdx:mainfrom
risu729:fix/devengines-npm-semver-ranges
Apr 12, 2026
Merged

fix: support npm semver ranges in devEngines#9061
jdx merged 6 commits intojdx:mainfrom
risu729:fix/devengines-npm-semver-ranges

Conversation

@risu729
Copy link
Copy Markdown
Contributor

@risu729 risu729 commented Apr 12, 2026

Refs #8935

Summary

  • preserve package.json devEngines semver ranges instead of simplifying them to exact-looking versions
  • resolve npm-compatible ranges with nodejs-semver when filtering backend versions
  • cover lower-bound, compound, caret, wildcard, and package manager range behavior

Tests

  • cargo test config::config_file::idiomatic_version::package_json
  • cargo test semver
  • ./e2e/run_test e2e/core/test_package_json

@risu729 risu729 marked this pull request as draft April 12, 2026 04:28
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 12, 2026

Greptile Summary

This PR adds support for npm-compatible semver ranges (e.g. >=22.0.0 <23.0.0, ^10.0.0, >=18 <20 || >=22) in package.json devEngines fields by preserving the range string in package_json.rs and resolving it via a new npm_semver_range_filter utility (backed by the nodejs-semver crate) in a scoped block inside resolve_version. The integration in tool_version.rs is correctly gated to IdiomaticVersionFile sources whose path is a package.json.

Confidence Score: 5/5

Safe to merge — core logic is correct, well-scoped to package.json sources, and backed by solid unit and e2e tests; remaining findings are minor style/coverage gaps.

Both P2 findings are non-blocking: the tilde-range test gap is a coverage nicety and the prefer_offline concern only manifests in an edge case (range not installed + prefer_offline mode) that doesn't produce wrong data. No P0/P1 issues found.

src/semver.rs — minor unit-test coverage gap for tilde ranges; src/toolset/tool_version.rs — prefer_offline guard inconsistency in the npm range remote-fetch block.

Important Files Changed

Filename Overview
src/semver.rs New npm_semver_range_filter and is_npm_semver_range_query helpers; good unit-test coverage for >=, ^, and
src/toolset/tool_version.rs New npm range resolution block in resolve_version correctly scoped to package.json idiomatic files; installed-then-remote lookup order is correct; logic is well-placed within the existing resolution flow.
src/config/config_file/idiomatic_version/package_json.rs Ranges are now preserved verbatim from devEngines; extensive unit tests cover lower-bound, compound, caret,
e2e/core/test_package_json E2E tests exercise the full resolution path for compound (>=/<) and caret (^) ranges via mise current; covers devEngines.runtime, packageManager field, and devEngines.packageManager override.
Cargo.toml Adds nodejs-semver = "4.2" dependency; appropriate for npm-compatible range parsing.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[resolve_version called\nv = '>=22.0.0 <23.0.0'] --> B{v == 'latest'?}
    B -- No --> C[list_installed_versions_matching v\nfuzzy_match_filter → empty for range]
    C --> D{source is package.json\n&& is_npm_semver_range_query?}
    D -- No --> G[normal fuzzy resolution path]
    D -- Yes --> E{latest_versions disabled?}
    E -- Yes --> F[list_installed_versions\nnpm_semver_range_filter]
    F --> H{match found?}
    H -- Yes --> I[return latest match\nmatches.last]
    H -- No --> J{offline?}
    E -- No --> J
    J -- No --> K[list_remote_versions\nnpm_semver_range_filter]
    K --> L{match found?}
    L -- Yes --> I
    L -- No --> G
    J -- Yes --> M[return range string as-is]
    G --> N[list_versions_matching_with_opts\nfuzzy empty → resolve_prefix]
Loading

Reviews (4): Last reviewed commit: "refactor: inline package json range sour..." | Re-trigger Greptile

Comment thread src/semver.rs
Comment thread src/semver.rs
Comment on lines +103 to +105
if query.split_whitespace().count() > 1 {
return true;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 split_whitespace().count() > 1 may intercept unusual non-range inputs

This catch-all check fires for any multi-word string (e.g. "long term", "lts carbon"), routing it through Range::parse. If parsing fails the function returns None and the regex fallback runs normally — so correctness is preserved — but the heuristic silently swallows those inputs rather than letting them reach fuzzy_match_filter directly. Narrowing the check to strings that contain at least one semver operator character alongside the space would make the intent clearer and reduce ambiguity for non-standard version aliases.

// Suggested: replace the broad whitespace check with a more targeted guard
if query.split_whitespace().count() > 1
    && query.bytes().any(|b| matches!(b, b'<' | b'>' | b'=' | b'^' | b'~'))
{
    return true;
}

@socket-security
Copy link
Copy Markdown

socket-security Bot commented Apr 12, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​nodejs-semver@​4.2.010010094100100

View full report

Copy link
Copy Markdown
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 full npm-compatible semver range support by integrating the nodejs-semver crate. It replaces the previous heuristic-based simplify_semver logic with a robust npm_semver_range_filter used during version resolution. Feedback suggests optimizing the version parsing in src/semver.rs by stripping 'v' prefixes once to avoid redundant parsing operations.

Comment thread src/semver.rs
Comment on lines +73 to +78
let version = v.as_str();
NodeVersion::parse(version)
.or_else(|_| {
NodeVersion::parse(version.trim_start_matches(['v', 'V']))
})
.is_ok_and(|version| range.satisfies(&version))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation performs a double parse for any version string starting with 'v' or 'V'. Since these prefixes are extremely common in tool versions, it's more efficient to strip the prefix once and perform a single parse. This optimization is safe as semver parsing logic remains consistent whether the prefix is stripped beforehand or as a fallback.

Suggested change
let version = v.as_str();
NodeVersion::parse(version)
.or_else(|_| {
NodeVersion::parse(version.trim_start_matches(['v', 'V']))
})
.is_ok_and(|version| range.satisfies(&version))
let version = v.trim_start_matches(|c| c == 'v' || c == 'V');
NodeVersion::parse(version).is_ok_and(|version| range.satisfies(&version))

@risu729 risu729 marked this pull request as ready for review April 12, 2026 21:22
@jdx jdx merged commit 885752f into jdx:main Apr 12, 2026
34 checks passed
@risu729 risu729 deleted the fix/devengines-npm-semver-ranges branch April 12, 2026 22:09
KOBeerose pushed a commit to KobeTools/mise that referenced this pull request Apr 13, 2026
Refs jdx#8935

## Summary
- preserve package.json devEngines semver ranges instead of simplifying
them to exact-looking versions
- resolve npm-compatible ranges with nodejs-semver when filtering
backend versions
- cover lower-bound, compound, caret, wildcard, and package manager
range behavior

## Tests
- cargo test config::config_file::idiomatic_version::package_json
- cargo test semver
- ./e2e/run_test e2e/core/test_package_json

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
mise-en-dev added a commit that referenced this pull request Apr 13, 2026
### 🐛 Bug Fixes

- **(docs)** typo in Go Backend by @dolmen in
[#9065](#9065)
- **(task)** render dependency templates even when no args are passed by
@MatthiasGrandl in [#9062](#9062)
- support npm semver ranges in devEngines by @risu729 in
[#9061](#9061)

### New Contributors

- @dolmen made their first contribution in
[#9065](#9065)
- @MatthiasGrandl made their first contribution in
[#9062](#9062)
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