Skip to content

[QUESTION] Very confusing behavior for pre-release range matching #721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
robross0606 opened this issue Jun 17, 2024 · 6 comments
Open

Comments

@robross0606
Copy link

robross0606 commented Jun 17, 2024

When checking pre-release ranges, the results are very confusing. I have read the documentation many times, run a bunch of tests and reviewed all bug tickets. There are several open and closed issues that seem related to this, but I still can't figure out how this is expected to work. For example:

npx semver --range ">=1.0.2-0" "1.0.3-6"

Results in a non-match. Why if the range is ">=" and the range explicitly includes prereleases via "-0"? This seems to be incorrect.

npx semver --range "^1.0.2-0" "1.0.3-6"

Also results in a non-match. This seems to also be incorrect.

npx semver --range "^1.0.2-0" "1.0.3"

Results in a match. This is correct, but doesn't make sense that this would match when "1.0.3-6" does not.

 npx semver --range ">=1.0.2-0" "1.0.2-6"

Results in a match. This is correct.

So then I looked at using --include-prerelease. This seemed like the right approach based on the "Prerelease Tags" section in the README. However, this causes behavior to seem incorrect in the other extreme:

npx semver --include-prerelease --range "^1.0.2" "1.0.3-6"

Results in a match. Wait, what? This seems to completely contradict motivation expressed in the "Prerelease Tags" section.

So how can this be used effectively in automation to match versions in situations where we do want to include pre-releases from those where we do not if we cannot express that purely from the range expression itself? It seems to me that, the way this is designed, the range expression is barely (if at all) being used to decide if pre-releases are included.

@robross0606
Copy link
Author

robross0606 commented Jun 24, 2024

Even more inexplicable...

npx semver --include-prerelease --range ">=1.0.2" "1.0.3-6"

Results in a match. But this doesn't?!

npx semver --include-prerelease --range ">=1.0.2" "1.0.2-6"

But this does match:

npx semver --include-prerelease --range ">=1.0.2-0" "1.0.2-6"

And so does this:

npx semver --range ">=1.0.2-0" "1.0.2-6"

😵

I can understand almost every behavior except this one:

npx semver --include-prerelease --range ">=1.0.2" "1.0.2-6"

If I'm explicitly asking to include prereleases, then why would "1.0.2-6" not match while "1.0.3-6" does match? That behavior is baffling to me. That doesn't seem to consistently follow the README which states:

Note that this behavior can be suppressed (treating all prerelease versions as if they were normal versions, for range-matching) by setting the includePrerelease flag on the options object to any functions that do range matching.

@robross0606
Copy link
Author

robross0606 commented Jun 24, 2024

And it gets even weirder...
This matches:

npx semver --range "1.x.x-x" "1.0.2"

But this doesn't match:

npx semver --range "1.x.x-x" "1.0.2-6"

Nor does this:

npx semver --include-prereleases --range "1.x.x-x" "1.0.2-6"

I'm not sure if this is following some set of rules I just don't understand, or if range matching is buggy.

@robross0606 robross0606 changed the title [QUESTION] Very confusing behavior for range matching [QUESTION] Very confusing behavior for pre-release range matching Jun 24, 2024
@jasperBailey
Copy link

I can understand almost every behavior except this one:

npx semver --include-prerelease --range ">=1.0.2" "1.0.2-6"
If I'm explicitly asking to include prereleases, then why would "1.0.2-6" not match while "1.0.3-6" does match?

from https://semver.org/#spec-item-11:

When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version:

Example: 1.0.0-alpha < 1.0.0.

@robross0606
Copy link
Author

Thanks for the explanation on that one. What about this one that also doesn't match?

npx semver --range "1.x.x-x" "1.0.2-6"

@ljharb
Copy link
Contributor

ljharb commented Jan 13, 2025

You may want ^1.0.2-0, which is one of the only ranges that I think will match that? x's i wouldn't expect to ever be useful.

@mbtools
Copy link
Contributor

mbtools commented Apr 24, 2025

Looking at https://github.com/jubianchi/semver-check, please note that this is an unreliable source. It's out-of-date and has its own interpretation of semver rules. It does not match what this package does (node-semver).

When it comes to comparing prereleases and ranges, the following should help:

// Find the set of versions that are allowed to have prereleases
// For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0
// That should allow `1.2.3-pr.2` to pass.
// However, `1.2.4-alpha.notready` should NOT be allowed,
// even though it's within the range set by the comparators.

For example, >=1.0.2-0 means all pre-releases up to 1.0.2 and then all regular releases that are higher. But it does not included pre-releases of other versions!

Ranges are mapped to so-called comparators which are a combination of a semantic version and an operator. Here are your examples:

range                  | comparator          | 1.0.2 | 1.0.2-6 | 1.0.3 | 1.0.3-6
-----------------------|---------------------|-------|---------|-------|--------
new Range('^1.0.2')    | >=1.0.2 <2.0.0-0    | true  | false   | true  | false
new Range('^1.0.2-0')  | >=1.0.2-0 <2.0.0-0  | true  | true    | true  | false
new Range('>=1.0.2')   | >=1.0.2             | true  | false   | true  | false
new Range('>=1.0.2-0') | >=1.0.2-0           | true  | true    | true  | false

If you had the pre-releases for 1.0.2 in your range, then they will be a match. But pre-releases for 1.0.3 will not.

If you use includePrerelease: true, this changes and now also the 1.0.3 pre-releases are a match:

range                                               | comparator          | 1.0.2 | 1.0.2-6 | 1.0.3 | 1.0.3-6 
----------------------------------------------------|---------------------|-------|---------|-------|--------
new Range('^1.0.2', { includePrerelease: true })    | >=1.0.2 <2.0.0-0    | true  | false   | true  | true
new Range('^1.0.2-0', { includePrerelease: true })  | >=1.0.2-0 <2.0.0-0  | true  | true    | true  | true
new Range('>=1.0.2', { includePrerelease: true })   | >=1.0.2	          | true  | false   | true  | true
new Range('>=1.0.2-0', { includePrerelease: true }) | >=1.0.2-0 	  | true  | true    | true  | true

Your x-range maps to 1.0.0. Therefore, the pre-releases for 1.0.2 like 1.0.2-6 don't match unless you set the option::

range                                               | comparator          | 1.0.2 | 1.0.2-6 | 1.0.3 | 1.0.3-6 
----------------------------------------------------|---------------------|-------|---------|-------|--------
new Range('1.x.x-x')                                | >=1.0.0 <2.0.0-0    | true  | false   | true  | false
new Range('1.x.x-x', { includePrerelease: true })   | >=1.0.0-0 <2.0.0-0  | true  | true    | true  | true

Hope that help. Works as designed 😃

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

No branches or pull requests

4 participants