Skip to content

Instantly share code, notes, and snippets.

@cowboy
Created September 5, 2010 18:37
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save cowboy/566233 to your computer and use it in GitHub Desktop.
Save cowboy/566233 to your computer and use it in GitHub Desktop.
isSemVer - semantic version comparison for JavaScript
/*!
* isSemVer - v0.1 - 9/05/2010
* http://benalman.com/
* http://semver.org/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
// Compare one or more semantic version numbers to a reference version number.
//
// Returns true if all version criteria are met. Comparison operator defaults
// to == if omitted. Valid comparisons are == != < <= > >=.
//
// isSemVer( my.version, '1.2.3' ) // == is assumed if comparison op excluded
// isSemVer( my.version, '>= 1.2.3', '!= 1.2.4alpha' )
// isSemVer( my.version, '< 2.0', '!= 1.2.3', '!= 1.2.4alpha' )
//
var isSemVer = (function(){
// This regexp matches an optional comparison operator, followed by optional
// whitespace and then a valid semantic version per http://semver.org/.
var re = /^(<|>|[=!<>]=)?\s*(\d+(?:\.\d+){0,2})([a-z][a-z0-9\-]*)?$/i;
// Returns a value that can be used in version comparison. Because a semantic
// version is comprised of 1 to 3 dot-separated numbers followed by an
// optional lexicographically-sorted alphanumeric suffix, it is easiest to
// convert the entire value into a single string that can be compared in a
// lexicographic fashion. Each dot-separated number is first left-padded by
// zeroes such that 1.2.3 -> 000000010000000200000003. This allows possible
// major / minor / patch versions between 0-99999999. The optional suffix is
// then appended, and if one was not provided, ~ will be used because it will
// always compare > than any suffix.
//
// If the include_cmp flag is set, the result value will be prepended with
// the specified comparison operator (or == if the operator was omitted).
function get_val( str, include_cmp ) {
// matches[1] = optional comparison operator.
// matches[2] = dot-separated major / minor / patch version.
// matches[3] = alphanumeric "special version" suffix.
var matches = ( str + '' ).match( re );
return matches
// Include matched comparison operator, defaulting to == if necessary.
? ( include_cmp ? ( matches[1] || '==' ) : '' )
// Since this string is going to be evaled, wrap it in quotes.
+ '"'
// In case version passed is like 1 or 1.0, right-pad it with extra .0
+ ( matches[2] + '.0.0' )
// Remove any unnecessary trailing .0
.match( /\d+(?:\.\d+){0,2}/ )[0]
// Replace each dot-separated number with a 0-padded value.
.replace( /(?:^|\.)(\d+)/g, function(a,b){
return Array( 9 - b.length ).join(0) + b;
})
// Append suffix or ~ if suffix was omitted.
+ ( matches[3] || '~' )
// Since this string is going to be evaled, wrap it in quotes.
+ '"'
// Handle an invalid semantic version / comparison.
: ( include_cmp ? '==0' : 1 );
};
return function( base_ver ) {
// Get the comparison value for the base version.
base_ver = get_val( base_ver );
// Iterate over all additional function arguments.
for ( var arg, i = 1; arg = arguments[ i++ ]; ) {
// If any comparison fails, exit immediately with a false value.
if ( !(new Function( 'return ' + base_ver + get_val( arg, 1 ) ))() ) {
return false;
}
}
// All comparisons passed, return true!
return true;
};
})();
/*!
* isSemVer - v0.1 - 9/05/2010
* http://benalman.com/
* http://semver.org/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
var isSemVer=(function(){var a=/^(<|>|[=!<>]=)?\s*(\d+(?:\.\d+){0,2})([a-z][a-z0-9\-]*)?$/i;function b(e,c){var d=(e+"").match(a);return d?(c?(d[1]||"=="):"")+'"'+(d[2]+".0.0").match(/\d+(?:\.\d+){0,2}/)[0].replace(/(?:^|\.)(\d+)/g,function(g,f){return Array(9-f.length).join(0)+f})+(d[3]||"~")+'"':(c?"==0":1)}return function(e){e=b(e);for(var c,d=1;c=arguments[d++];){if(!(new Function("return "+e+b(c,1)))()){return false}}return true}})();
@cowboy
Copy link
Author

cowboy commented Sep 5, 2010

Some basic unit tests:

[
  [ true, ['1','1'] ],
  [ true, ['1','1.0'] ],
  [ true, ['1','1.0.0'] ],
  [ true, ['1.0','1'] ],
  [ true, ['1.0.0','1'] ],
  [ true, ['1.0000','1.0.0000'] ],
  [ true, ['0001.0000.0000','1'] ],
  [ true, ['1','== 1.0.0'] ],
  [ true, ['1','!= 1.0.1'] ],
  [ true, ['1','!= 1.1.0'] ],
  [ true, ['1','!= 0.1.0'] ],
  [ true, ['1','!= 0.1.0alpha'] ],
  [ true, ['1','!= 0.1.0beta'] ],
  [ true, ['1','!= 0.1.0rc1'] ],
  [ true, ['1','> 1alpha'] ],
  [ true, ['1','> 1beta'] ],
  [ true, ['1','> 1rc1'] ],
  [ true, ['1','> 1.0alpha'] ],
  [ true, ['1alpha','>= 1.0alpha'] ],
  [ true, ['1beta','> 1.0alpha'] ],
  [ true, ['1rc1','> 1.0beta'] ],
  [ true, ['1','> 1.0beta'] ],
  [ true, ['1','> 1.0rc1'] ],
  [ true, ['1.10.9','>= 1','<= 2'] ],
  [ true, ['1.10.9','> 1.9.9','> 1.10.9pre'] ],
  [ false, ['1','> 1'] ],
  [ false, ['1','< 1.0'] ],
  [ false, ['1','!= 1.0.0'] ],
  [ false, ['1','1alpha'] ],
  [ false, ['1','== 1beta'] ],
  [ false, ['1','== 1rc1'] ],
  [ false, ['1alpha','< 1.0alpha'] ],
  [ false, ['1alpha','> 1.0alpha'] ],
  [ false, ['1alpha','!= 1.0alpha'] ],
  [ false, ['1beta','< 1.0alpha'] ],
  [ false, ['1.10.9','< 1'] ],
  [ false, ['1.10.9','> 1.9.9','< 1.10.9pre'] ]
].every(function(v){
  return v[0] === isVersion.apply(null, v[1]);
})

@Marak
Copy link

Marak commented Jan 10, 2011

@cowboy
Copy link
Author

cowboy commented Jan 10, 2011

I like the idea of using - to test "between-ness" and using x in a version like 2.x or 2.x.x as a shorthand for that. I'm not sure I understand why "~" is an alias for "*" though.

@kflorence
Copy link

@gene1wood
Copy link

@srescio
Copy link

srescio commented Apr 14, 2015

used in my phonegap app to notify updates by comparing current version and previous one stored in db, thanks @cowboy :) https://sresc.io/dA4

@Noitidart
Copy link

Noitidart commented Feb 21, 2017

Can we get a version without new Function as that makes it fail CSP. We need to pass script-src: 'unsafe-eval' otherwise, and browser extensions dont allow this (Firefox). For example running this on github we get this error - http://i.imgur.com/MtLfhqx.png

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment