Repository navigation
fix: return prerelease of 0.0.0 from minVersion when it is the minimum - #936
thisisshivamtiwari wants to merge 3 commits into
Conversation
minVersion tried 0.0.0 before 0.0.0-0 and returned it as soon as it matched, so a lower bound such as >=0.0.0-alpha, or any range with includePrerelease, gave 0.0.0 instead of the lower prerelease. Fixes npm#934
There was a problem hiding this comment.
🟡 Changes recommended
Candidate validation occurs too late, allowing an impossible OR branch to mask a valid prerelease minimum and return an incorrect fallback.
1 open issue
What changed in this PR
This PR addresses #934 by updating minVersion to consider matching prereleases below 0.0.0, rather than returning the stable version immediately.
Changes:
- Check
0.0.0-0first and consider prerelease lower bounds before falling back to0.0.0. - Skip wildcard comparators during lower-bound calculation.
- Add prerelease and
includePrereleaseregression cases, with readable options in test names.
| File | Description |
|---|---|
test/ranges/min-version.js |
Adds regression cases and improves test labels for options objects. |
ranges/min-version.js |
Revises minimum-version selection and handles wildcard comparators. |
Review effort: Balanced
A set whose lower bound fails its own upper bounds, such as >=0.0.0-alpha <0.0.0-alpha, could win the minimum across a union and then fail the final range check, hiding another set's valid minimum. Check each set's candidate against that set before comparing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| if (setMin && (!minver || gt(minver, setMin))) { | ||
| // A set whose own lower bound fails its upper bounds (e.g. `>=1.0.0 <1.0.0`) has no minimum; skipping it | ||
| // keeps it from hiding the minimum of another set in the union. | ||
| if (setMin && new Range(comparators.join(' '), range.options).test(setMin) && |
There was a problem hiding this comment.
This now returns null for a range with a valid minimum:
const range = '>=1.0.1-0 <1.0.0 || >1.0.0 <1.0.1-alpha'
semver.minVersion(range) // null; previously 1.0.1-0
semver.satisfies('1.0.1-0', range) // trueThe generated 1.0.1 candidate fails the upper bound, but 1.0.1-0 satisfies the second branch. Please preserve this minimum and add a regression test.
There was a problem hiding this comment.
Thanks, confirmed: this was a regression from my change (before it, this range gave 1.0.1-0). Fixed in 791b290. When a > bound's release fails its set, the first prerelease of that release (X.Y.Z-0) is now tried first, so 1.0.1-0 is kept. Regression tests added for the default, loose and includePrerelease options. Side effect: minVersion('>1.0.0', { includePrerelease: true }) now returns 1.0.1-0, the actual lowest match, and has a test. npm test passes with 100% coverage.
| } | ||
|
|
||
| if (minver && range.test(minver)) { | ||
| if (minver && range.test(minver) && (!zeroMatches || gt(zero, minver))) { |
There was a problem hiding this comment.
Valid long prerelease bounds can now return an invalid version or throw:
const range = '>0.0.0-' + 'a'.repeat(249)
const result = semver.minVersion(range)
semver.valid(result.version) // null: the result has 257 characters
semver.minVersion(range, { includePrerelease: true }) // throwsThis input previously returned 0.0.0 without throwing. Please handle the version-length limit when generating the successor and add boundary tests for default, loose, and includePrerelease options.
There was a problem hiding this comment.
Thanks, confirmed: with a bound at the 256-character limit, the pushed .0 produced an over-length version, and with loose it also threw. Fixed in 791b290. The successor prerelease is generated only if it fits within MAX_LENGTH. Otherwise the release itself is used, as before this PR (>0.0.0-<249 a's> gives 0.0.0). Boundary tests cover lengths 255 and 256 under the default, loose and includePrerelease options.
Per review: - A '>' bound's release candidate can fail its set while the first prerelease of that version passes (>1.0.0 <1.0.1-alpha allows 1.0.1-0), so try the prerelease first. - The next prerelease of a bound at the 256-character limit cannot be written; use the release instead, as minVersion did before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Fixes #934
minVersionchecked0.0.0before0.0.0-0and returned it as soon as it matched. Since every prerelease of0.0.0sorts below0.0.0, that shortcut returned the wrong answer whenever a0.0.0prerelease also matches:Changes in
ranges/min-version.js:0.0.0-0first, since it is the lowest possible version.0.0.0matches, still run the normal lower-bound loop and return its result only when it matches and is below0.0.0. Otherwise return0.0.0as before. This keeps every existing result: when the loop finds nothing lower, the answer is still0.0.0.ANYcomparators in the loop. Before this change the loop never saw them, because any range containing*matched0.0.0and returned early.Without
includePrerelease,*and<2still return0.0.0, which matches the expectation in the issue thread.Tests
test/ranges/min-version.jsfor0.0.0prerelease lower bounds (>=,>, in a union, and an impossible prerelease set next to<1that must still fall back to0.0.0), plusincludePrereleasecases. The new cases fail onmainand pass with this change.JSON.stringifyso the newincludePrereleasecases have readable names.npm test(tap with 100% coverage, then eslint andtemplate-oss-check) passes locally.🤖 Generated with Claude Code