Why does `>1.2` throw as an invalid comparator?
https://github.com/npm/node-semver/blob/d17aebf8485edfe9dda982dab578c603d031e4ab/test/fixtures/range-exclude.js#L68
I am trying to understand why npx -r '>1.2' 1.2.8 results in 1.2.8 not satisfying the range. I think >1.2 should expand to >1.2.0 <1.3.0 which would mean 1.2.8 satisfies. But stepping through the tool, I see it hits this path:
https://github.com/npm/node-semver/blob/d17aebf8485edfe9dda982dab578c603d031e4ab/classes/comparator.js#L37-L42
The result being m === null and the TypeError is thrown.
>1.2 does not expand to >1.2.0, it equates to >= 1.3. In other words, > 1.2 means that no 1.2.x version can satisfy it.
I see. Thank you.
Sorry, I'm reopening this. I think this line is confirming your answer @ljharb:
https://github.com/npm/node-semver/blob/d17aebf8485edfe9dda982dab578c603d031e4ab/README.md?plain=1#L164-L166
But further down we have:
https://github.com/npm/node-semver/blob/d17aebf8485edfe9dda982dab578c603d031e4ab/README.md?plain=1#L316-L321
This seems to be a conflict to me.
1.2 is not the same as >1.2
1.2 is a component of >1.2. The version string should still expand the same.
1.2 does not expand to 1.2.0. It expands to 1.2.x, and >1.2.x indeed excludes any 1.2.
It expands to
1.2.x
Which expands to 1.2.0. Thus, >1.2 should expand to >1.2.0, right?
No, it does not expand to 1.2.0. It can't expand without a concrete list of existing versions, and it will always match the latest 1.2, not the .0
I'm just not seeing the rule you're basing that on. If the readme isn't the source of truth for the expansion rules, please point me to the right document.
It’s not documented that i can see, but it’s common sense - would “1.2” be satisfied by 1.2.99? Obviously yes, because it’s a range, not a short way to express one version.
Sorry, you've just confused me even more. I originally asserted that 1.2.8 should satisfy >1.2. Your response was that it shouldn't. Now you're suggesting it should.
1.2.8 certainly satisfies 1.2. It certainly does not satisfy >1.2 (which is the same as >= 1.3).
This discussion will continue for years until there's an npm specification :)
@jsumners ,
As you, I'm surprised me because I thought semver.coerce() was being applied before the comparison.
Then I reasoned that it was checking for inclusion in a set, so the logical thing to do was to convert '1.2' to '1.2.x' and not coerce to '1.2.0' (the same for 1 to 1.x.x).
After that, the results in this list made perfect sense to me...
semver.satisfies('1.2.0', '1') // true -- '1' is converted to '1.x.x'
semver.satisfies('1.2.0', '1.2') // true -- '1.2' to '1.2.x' an so on
semver.satisfies('1.2.0', '1.2.0') // true
semver.satisfies('1.2.0', '=1.2') // true -- The '=' makes no difference
semver.satisfies('1.2.0', '=1.2.0') // true
semver.satisfies('1.2.0', '~1.2') // true -- '~#.#' is equal to '#.#.x'
semver.satisfies('1.2.0', '~1.2.0') // true
semver.satisfies('1.2.0', '<1.2') // false
semver.satisfies('1.2.0', '<1.2.0') // false
semver.satisfies('1.2.0', '<=1.2') // true
semver.satisfies('1.2.0', '<=1.2.0') // true
semver.satisfies('1.2.0', '>1.2') // false
semver.satisfies('1.2.0', '>1.2.0') // false
semver.satisfies('1.2.0', '>=1.2') // true
semver.satisfies('1.2.0', '>=1.2.0') // true
semver.satisfies('1.2.0', '^1.2') // true
semver.satisfies('1.2.0', '^1.2.0') // true -- '^#.#' is equal to '#.x.x'
...as this...
semver.satisfies('1.2.8', '1') // true
semver.satisfies('1.2.8', '1.2') // true
semver.satisfies('1.2.8', '1.2.0') // false
semver.satisfies('1.2.8', '=1.2') // true
semver.satisfies('1.2.8', '=1.2.0') // false
semver.satisfies('1.2.8', '~1.2') // true
semver.satisfies('1.2.8', '~1.2.0') // true
semver.satisfies('1.2.8', '<1.2') // false
semver.satisfies('1.2.8', '<1.2.0') // false
semver.satisfies('1.2.8', '<=1.2') // true
semver.satisfies('1.2.8', '<=1.2.0') // false
semver.satisfies('1.2.8', '>1.2') // false
semver.satisfies('1.2.8', '>1.2.0') // true
semver.satisfies('1.2.8', '>=1.2') // true
semver.satisfies('1.2.8', '>=1.2.0') // true
semver.satisfies('1.2.8', '^1.2') // true
semver.satisfies('1.2.8', '^1.2.0') // true
satisfies does not work as other semver methods ...gt, lt, etc.
The conclusión is to use the full semver (n.n.n) if you're unsure.
This is the gist with the source
@ljharb does this list of rules look correct to you?
- A version string with all three primary positions provided specifies an exact version, e.g.
1.2.3is equivalent to=1.2.3. - A version string with only one or two primary positions provided equates to an X-range, e.g
1.2is equivalent to1.2.xis equivalent to>=1.2.0 <1.3.0and1is equivalent to1.x.xis equivalent to>=1.0.0 <2.0.0. - A version string prefixed with any primitive (
< | > | >= | <= | =) is not an X-range and only the positions provided count, e.g>1.2.3is equivalent to>=1.2.4,>1.2is equivalent to>=1.3.0, and>1is equivalent to>=2.0.0. - Tilde and Caret range expansions are described fully in the readme.
There's some nuance you're missing about prereleases, but otherwise that sounds correct.
- A version string with all three primary positions provided specifies an exact version, e.g.
1.2.3is equivalent to=1.2.3.
yes
- A version string with only one or two primary positions provided equates to an X-range, e.g
1.2is equivalent to1.2.xis equivalent to>=1.2.0 <1.3.0and1is equivalent to1.x.xis equivalent to>=1.0.0 <2.0.0.
right
- A version string prefixed with any primitive (
< | > | >= | <= | =) is not an X-range and only the positions provided count, e.g>1.2.3is equivalent to>=1.2.4,>1.2is equivalent to>=1.3.0, and>1is equivalent to>=2.0.0.
In the context of semver.satisfy() all the shortened versions are X-ranges.
That's why it's better to use the full version with comparison operators, as in this example from eslint's package.json.
{
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
}
See the output of semver.toComparators('^18.18.0 || ^20.9.0 || >=21.1.0') ...
[
[ '>=18.18.0', '<19.0.0-0' ],
[ '>=20.9.0', '<21.0.0-0' ],
[ '>=21.1.0' ]
]
Saludos desde México.
Indeed, ^18.8 and ^18.8.0 are identical. (typically the shorter one is preferred)
@aMarCruz thank you for the information. I am not concerned with the various methods exported by this module. I am solely concerned with how strings are meant to be parsed and interpreted. In that regard, your example for semver.satisfy is covered by the fourth "rule," not the third one.