The basic idea to make this comparison would be to get arrays of parts from the version numbers, and then compare pairs of parts from the two arrays. If the parts are not equal, we know which version is smaller.
There are a few of important details to keep in mind:
- The parts in each pair are compared numerically, even if we have version strings that are not made up of just digits (e.g.
3.0a
or3.1.3-beta
). In this case, the script will still return a value, in this case0
. The script cannot make the difference between1.0-alpha
,1.0-beta
and1.0-prerelease
. - If one version string has more parts than the other, the script will normalize (basically adding a
0
at the end of the shorter version) the versions (1.0
should be considered less than1.0.1
) and then compare them.
The script below compares two version strings. This only considers numeric components to a version – all non-digit components (besides the delimiter – period) will be ignored. Trailing zero components/pieces will be ignored (i.e. 3.1.0
is equivalent to 3.1
, but 3.10
is greater than 3.1
).
const Versioning = {
DOT_PATTERN: /\./,
NON_DIGIT_PATTERN: /\D/,
/**
* Compare the major version (first number).
*
* @param {string} version1 First version string to compare
* @param {string} version2 Second version string to compare
* @return {number} negative if version1 < version2, zero if equal, positive if version1 > version2
*/
compareMajor: (version1, version2) => Versioning.compareLevels(version1, version2, 0),
/**
* Compare the minor version (second number).
*
* @param {string} version1 First version string to compare
* @param {string} version2 Second version string to compare
* @return {number} negative if version1 < version2, zero if equal, positive if version1 > version2
*/
compareMinor: (version1, version2) => Versioning.compareLevels(version1, version2, 1),
/**
* Compare the revision version (third number).
*
* @param {string} version1 First version string to compare
* @param {string} version2 Second version string to compare
* @return {number} negative if version1 < version2, zero if equal, positive if version1 > version2
*/
compareRevision: (version1, version2) => Versioning.compareLevels(version1, version2, 2),
/**
* Compare two version strings up to a given index (major, minor, revision).
*
* @param {string} version1 First version string to compare
* @param {string} version2 Second version string to compare
* @param {number} index 0-indexed level to compare (0 = major, 1 = minor, 2 = revision)
* @return {number} negative if version1 < version2, zero if equal, positive if version1 > version2
*/
compareLevels: (version1, version2, index) => {
const length = index + 1;
const v1 = Versioning.normalize(version1).slice(0, length);
const v2 = Versioning.normalize(version2).slice(0, length);
return Versioning.cmp(v1, v2);
},
/**
* Compare two full version strings.
*
* @param {string} version1 First version string to compare
* @param {string} version2 Second version string to compare
* @return {number} negative if version1 < version2, zero if equal, positive if version1 > version2
*/
compare: (version1, version2) =>
Versioning.cmp(Versioning.normalize(version1), Versioning.normalize(version2)),
/**
* Normalize a version string to an array of integers.
* Removes non-digit characters and trims trailing zeroes.
*
* @param {string} version Version string (e.g. "1.2.0-beta")
* @return {number[]} Array of numeric parts (e.g. [1, 2])
*/
normalize: (version = '') => {
const parts = version
.trim()
.split(Versioning.DOT_PATTERN)
.map(piece => {
const num = parseInt(piece.replace(Versioning.NON_DIGIT_PATTERN, ''), 10);
return Number.isNaN(num) ? 0 : num;
});
// Remove trailing zeros
while (parts.length > 0 && parts[parts.length - 1] === 0) {
parts.pop();
}
return parts;
},
/**
* Compare two arrays of integers.
*
* @param {number[]} x Array of version parts
* @param {number[]} y Array of version parts
* @return {number} negative if x < y, zero if equal, positive if x > y
*/
cmp: (x, y) => {
const size = Math.min(x.length, y.length);
for (let i = 0; i < size; i++) {
if (x[i] !== y[i]) {
return x[i] < y[i] ? -1 : 1;
}
}
if (x.length === y.length) return 0;
return x.length < y.length ? -1 : 1;
}
};
How to test the script:
console.log(Versioning.compare('3.1', '3.0')); // > 0
console.log(Versioning.compare('1.0', '1.0.1')); // < 0
console.log(Versioning.compare('2.0.0', '2.0.0')); // 0
console.log(Versioning.compare('1.1.0', '1.1')); // 0
console.log(Versioning.compare('1.1-beta', '1.0-alpha')); // > 0
The values returned are 1
for higher version, -1
for lower version and 0
for equal (or undecided) version.