Telerik blogs

Telerik makes use of the npm and Bower package managers in the engineering of several of its products. As an example npm and Bower are used in the construction (i.e. source to build) and delivery of the open source Kendo UI Core code. If you're unfamiliar with the purpose of a package manager and specifically the aforementioned package managers, it might be wise to stop now and read an article I wrote called, "Package Managers: An Introductory Guide For The Uninitiated Front-End Developer".

With a firm grasp on the purpose and usage of npm and Bower, it should be no surprise that "semver" is mentioned in the documentation describing how to add a properly versioned package to a repository. When you see the term "semver" you can take this to mean that a version number is required and it should follow the semantical prescriptions detailed in the Semantic Versioning 2.0.0 specification from Tom Preston-Werner.

I'm not going to assume that the semver spec is crystal clear to everyone. So, before I get to the meat of this article, which is really about all of those mystical and magical characters parsed by the node semver parser, I'm going to quickly review the purpose and semantics of semver versioning.

What is semver?

According to author of the semver spec, and succinctly put, the purpose of semver is to avoid "dependency hell". Tom, the author, describes this "dependency hell" phenomenon as:

Dependency hell is where you are when version lock and/or version promiscuity prevents you from easily and safely moving your project forward.

In order to avoid this hellish place, Tom offers 11 rules that dictate what constitutes a version number and the semantical meaning associated with numeric change among the version numbers. In short, Tom thinks the decision to update a dependency (i.e. a package with an API) should convey a very specific semantical meaning to developers who use the package in a bigger system. This seems reasonable. Let's examine what, exactly, this entails.

Constructing a Version Number

Tom recommends that a version number consists of three digits separated by a period. An example of a semver would be:

Version 1.0.0

The first number (from left to right) is called the MAJOR number and only changes when you make incompatible API changes. The second number is called the MINOR number and only changes when you add functionality in a backwards-compatible manner. The third number is called the PATCH number and only changes when you make backwards-compatible bug fixes.

If the description for semver versioning just given, which is almost verbatom from the spec, contains too much spec-like-words, you might find the explanation and example from semver-ftw.org (shown below) to be more human friendly.

version [Major].[Minor].[Patch]

Major = "breaking changes" - Increment MAJOR version when you have removed or changed a feature, and dependent modules will have to modified to be compatible with the new version.

Minor = "new feature" - Increment MINOR version when you have added a feature, but the module is backwards compatible.

Patch = "bugfix" - Increment PATCH version when you have fixed a problem, but not broken or changed anything else.

Below is an example from semver-ftw.org of how you might interpret the version changes in a package based on the above definitions.

Suppose a new module called pizza gets published to npm as version 0.0.1. When the author of the module decides to add some new functions like .pepperoni() it should get incremented to 0.1.0. When an issue on GitHub is opened about a bug in .pepperoni() and the bug gets fixed it should get pushed as 0.1.1. When the author goes vegetarian and eliminates the .pepperoni() method it should be published as 1.0.0.

Hopefully, after that great pizza illustration, the meaning a semver encapsulates is nothing mystical. That is, from now on when you read a package's semver you should understand what is semantically being expressed so you can make sane decisions about upgrading the package. Or, maybe more so, which version ranges you'll most likely always want to immediately upgrade too (e.g. "1.2.3 || 1.2.4. || 1.2.5", or may be simpler to think about it as a range such as anything equal to or between "1.2.3 - 1.2.5").

When semver is understood by developers and respected by package authors, a tranquil harmony abounds among software engineers over the avoidance of dependency hell. Or so the theory goes. In all seriousness, this unenforceable agreement of semantical convention between package author and developer seems to work. And nothing better has come along. So for now, semantic versioning or "semver" is what we have, and it's assumed and used all over the place, even outside of NPM and Bower land.

Semver Ranges

The absolute best place to find a grouping of semver numbers in the wild is in a package.json or bower.json file. The intention of the semver strings found in the package.json file are to indicate the currently acceptable version(s) of the package(s) a developer is depending upon in their project, today and in the future. More on the future part in a minute.

Below is an example of the package.json from the Kendo UI Core github repository.

{
   "name": "kendo-ui-core",
   "version": "1.0.0",
   "dependencies": {
   },
   "devDependencies": {
       "optimist": "0.3.7",
       "typescript": ">=1.0.0",
       "uglify-js": "2.4.15",
       "cssmin": "0.3.2",
       "faye": "0.8.3",
       "xmlbuilder": "0.4.2",
       "colors": "0.6.0-1",
       "cookiejar": "1.3.0",

       "faye-websocket": "0.4.4",
       "less": "1.3.3",
       "jshint": "1.1.0",

       "grunt": "~0.4.2",
       "grunt-cli": "~0.1.0",
       "grunt-contrib-jshint": "~0.7.2",
       "grunt-contrib-copy": "~0.5.0",
       "grunt-debug-task": "0.1.3",
       "grunt-karma": "0.6.2",

       "qunitjs": "~1.12.0",

       "karma": "0.10.5",
       "karma-chrome-launcher": "~0.1",
       "karma-firefox-launcher": "~0.1",
       "karma-html2js-preprocessor": "~0.1",
       "karma-qunit": "0.1.1",
       "karma-junit-reporter": "0.2.1",
       "karma-osx-reporter": "0.0.4",
       "karma-ie-launcher": "~0.1",
       "karma-coffee-preprocessor": "~0.1.3"
   },
   "scripts": {
       "build": "npm install && grunt build",
       "test": "./node_modules/.bin/grunt travis"
   }
}

Wow...that's a lot of dependencies. This should be expected in large projects. Wouldn't it irk your developer mind if you had to manage the upgrade path (patch>minor>major) for all of these dependencies manually? Certainly it can be done this way and that's exactly what one would have to do if a dependency is indicated with a non-range semver number (e.g. "faye-websocket": "0.4.4").

Programmatic Operators

However, notice that some of the semver numbers have what appears to be programmatic operators (e.g. >=, ~, -) around them. These helpful characters are what the node-semver parsers consider to be operators (a.k.a. semver sugar) in a comparator statement used to define acceptable semver ranges. Basicly node-semver operators make it possible to define acceptable upgrading paths for each package based on a range of accepted versions. In other words, the operators can indicate to the package manager system (or a human) which version(s) are acceptable to be upgraded to, upon availability, when installing or updating dependencies.

If you think about semver semantics, it's most likely the case that minor and patch updates won't break the package's API, or so the theory goes. So, wouldn't you almost always want those updates automatically? Well, this is exactly what can be done with a few extra characters in a semver string to define a range of acceptable versions to be updated too in the future. For example the semver >=1.0.0 means always get me the latest updates when updating, even breaking API changes. That's nuts, right? But it's certainly possible using semver sugar range operators.

Commonly Seen semver Range Patterns

The type and scope of the ranges that can be defined is a complicated topic to express in words, especially for those new to the concept of a version ranges. In order to get you acquainted with the meaning of the range operators found in semver strings, I'm going demonstrate and explain, in the table below, several of the most commonly seen semver range patterns found in the wild.

semver ex.

Type of range

What it means

Verbose semver range ex.
(verbose equivalent to the semver in the first column)

*

X-Range

The most recent version will be used whatever that might be (note that either *, or X, or x can be used)

>=0.0.0

1.2.3 - 2.3.4

Hyphen Range

use the most recent version greater than or equal 1.2.3 and less than or equal to 2.3.4 found in the repository

>=1.2.3 <=2.3.4

1.2.3 - 2

Hyphen Range

use the most recent version greater than or equal to 1.2.3 and less than 3.0.0 found in the repository

>=1.2.3 <3.0.0

1.*

X-Range

use the most recent version greater than or equal to 1.0.0 and less than 2.0.0 in the repository

(i.e. get any major version change greater than or equal to 1.0.0 and less than 2.0.0)

>=1.0.0 <2.0.0

1.2.*

X-Ranges

use the most recent version greater than or equal to 1.2.0 and less than 1.3.0 in the repository

(i.e. get any major and minor version change greater than or equal to 1.2.0 and less than 1.3.0)

>=1.2.0 <1.3.0

~1.2.3

Tilde Ranges

use the most recent version greater than or equal to 1.2.3 and less than 1.3.0 in the repository

(i.e. Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not.)

>=1.2.3 <1.3.0

^1.2.3

Caret Ranges

use the most recent version greater than or equal to 1.2.3 and less than 2.0.0 in the repository

(i.e. Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch])

>=1.2.3 <2.0.0

Note: For me details about the Tilde and Caret Ranges check out "Semver: Tilde and Caret" from Tim Oxley on Nodesource.com blog.

If you already understand the basics being communicated in the table above and desire a more exhaustive explanation of ranges, you can read the NPM/Github documentation. I'll warn you though, I found the explanations in the documentation to be rather difficult to grok. My advice, if you are satisfied with only knowing the basics, skip the mental exercise of trying to understand the documentation. Just realize that almost any complexity of semver ranges can be accomplished, especially if you consider you can join comparator sets using || (e.g. The range 1.2.7 || >=1.2.9 <2.0.0 would match the versions 1.2.7, 1.2.9, and 1.4.6, but not the versions 1.2.8 or 2.0.0.).

Package Monitoring, Using Semver Ranges

Once you have a general idea of what is possible with semver ranges the next logical question is how to automate the process of:

  1. Being informed that an upgrade can occur that is within range;
  2. Upgrading to this version automatically without, human, manual, intervention (i.e. typing bower update or npm update at the command line).

One way to address this problem is to run the bower update or npm update at some sort of interval in a build/deploy process. That's ok, I suppose. But this is 2014 and, as you might guess, there's a service for this type of thing. The one I've used in the past is Gemnasium. Gemnasium can inform you when packages are available to be upgraded based on semver ranges. Then, it can also automagically update your packages, if you give it the correct permission to do so. And that, my friend, is some automate-everything-hawt-sauce!

Below I am showing the Gemnasium dashboard for the Kendo UI Core npm package dependencies found in the package.json file on GitHub.

Gemnasium Dashboard

Conclusion

As you can see, semver ranges are powerful and useful when it comes to managing code. So powerful they can be used by a service like Gemnasium to automate the management of packages, their relationships, and upgrades paths.

Hopefully, this article has cleared up any confusion around semver and the seemingly mystical and magical characters in a semver string representing semver ranges.


cody-lindley
About the Author

Cody Lindley

Cody Lindley is a front-end developer working as a developer advocate for Telerik focused on the Kendo UI tools. He lives in Boise, ID with his wife and three children. You can read more about Cody on his site or follow him on Twitter at @codylindley.

Comments

Comments are disabled in preview mode.