Thursday, May 19, 2016

Everything You Need to Know About Bower, Part 2

In Part 1 of this two-part rant on Bower, I discussed many of the arguably non-intuitive quirks of SemVer as implemented by NPM and Bower.  In Part 2, we round that out by sharing how we've dealt with those peculiarities and successfully collaborate using a private bower server.

How to Make it Work

At Bodybuilding.com, different teams are often making distinct changes in the same code base, and we need to be able differentiate between their releases.  Git branches are obviously going to keep the work separate, but in order to consume or deploy the changes, we need to pre-release the app to Bower, and then it's out there for anyone to consume.  Here's where the whole "stable release" restriction and shorthands can cause trouble.

We Need to Be Restrictive

Third-party apps are generally fine to leave open-ended, using the ^ or ~ shorthands.  More often than not, we want the latest bug fix.  But as we're developing our own interdependent apps, we need to lock down the version request.  It's verbose, but in most cases the only way to restrict the version to a set tuple, while allowing pre-releases within that tuple, while also rejecting any version outside of that tuple, is:

>=x.y.z-0 <x.y.z+1 (where -0 is literal text and z+1 is one more than the number represented by z)
Or, if using a pre-release tag:
>=x.y.z-tag.0 <x.y.z-tag.100
Ya gotta tell Bower what ya want.
For example, use >=1.2.3-0 <1.2.4. Note that we need to explicitly use the -0 part of the version; without it, Bower will only look for stable releases, and, in effect, we could have just said =1.2.3.  With the -0, though, we gain several things:
  • Bower will consider pre-releases acceptable, and pick up the latest 1.2.3-x version.
  • Bower will refuse 1.2.4 and higher, pre-release or otherwise, ensuring that the team gets the version they're working on.
  • Bower will consider the 1.2.3 release valid for that range and prefer it over the pre-releases, meaning the app that is requesting the version doesn't necessarily need to update its bower.json in order to release.  Ideally, at release time, bower.json specifies exact version numbers, but if a version was missed then this notation won't break the desired behavior.
Similarly, if a different team uses >=1.2.4-0 <1.2.5, they will ensure that they don't accidentally pick up version 1.2.3 when it becomes an official release.

Examples

Note that you can try these with your own private Bower registry.  Create a new git repo, register it with your Bower server, and then iterate over changes to the readme file, while releasing the various tags.  Then you can test with bower info [your-bower-project]#[request version here].  

Further note that you need to either escape the ><, and space characters – for example, bower info test-semver#\>=1.0.0-0\ \<1.0.1 – or else wrap the whole package#version part in quotes, like bower info "test-semver#>=1.0.0-0 <1.0.1".  (For reference: how the command line uses spaces and angle brackets)

Examples of Favoring Stable Releases

The examples below assume the following available versions:
  • 0.0.0
  • 0.0.1
  • 0.0.2-0
  • 0.0.2-1
  • 0.0.2-2
  • 0.0.2
  • 0.0.3-0
  • 0.0.3-1
  • 0.0.3
  • 0.0.4-0
  • 0.0.4-1
  • 0.0.5-0
  • 0.0.5-1

Requested version
Resolved version
Notes
^0.0.0 or ~0.0.00.0.3Bower assumes you want the latest stable release. 0.0.4 and 0.0.5 exist only in pre-release versions, so 0.0.3 is resolved.
>0.0.3
0.0.5-1Only pre-release versions are greater than 0.0.3 (because we aren't including 0.0.3 itself), so even though Bower would assume you want the latest stable version, there is no latest stable version in this case, and the latest pre-release is returned.
~0.0.40.0.5-1Essentially the same as the above example. We want the latest patch greater than or equal to 0.0.4, and since there are no stable releases in that range, we get the latest pre-release.
0.0.5 or =0.0.5noneDeclaring a stable version with no range will not return any version if the stable version doesn't exist; Bower assumes you want the exact stable version requested and will not return the pre-release versions.

Examples of Pre-release Tags


The following examples assume the following available versions (released in order):
  • 1.0.0-alpha.0
  • 1.0.0-alpha.1
  • 1.0.0-beta.0
  • 1.0.0-beta.1
  • 1.0.1-foo.0
  • 1.0.1-foo.1
  • 1.0.1-bar.0
  • 1.0.1-bar.1
  • 1.0.1-z.0
  • 1.0.1-z.1
  • 1.0.1-a.0
  • 1.0.1-a.1

Requested version
Resolved version
Notes
>=1.0.0-alpha.01.0.1-z.1
Apparently, Bower considers anything with a pre-release tag as simply a marker to get a pre-release. It considers the "latest" to be the highest in an alphabetically sorted list of tags. Therefore, pre-release z is chosen.
>=1.0.0-alpha.0 <1.0.11.0.0-beta.1The range really only limits the numeric triad to 1.0.0, so we filter out all of the 1.0.1s. But the "latest" version is the highest alphabetically, so beta is returned.
>=1.0.0-made-up-stuff1.0.1-z.1Note that the pre-release tag doesn't even exist, and moreover there is no "dot number." Bower sees no foul in this. It muses, "Ah, you want a pre-release. Here's the latest pre-release!"
>=1.0.0-alpha.0 <1.0.0-alpha.1001.0.0-alpha.1This actually works! Cumbersome as heck, but it does enable separation of pre-release tags.

The End (v1.0.0-a.1)

Hopefully this guide to how Bower handles SemVers reduces your aches and pains.  If you'd like to read more, check out the section below.

References and Further Reading

Official SemVer spec: http://semver.org/
Official node-semver spec: https://github.com/npm/node-semver#versions
A short and easy-to-read attempt at making the SemVer spec more approachable: https://github.com/dominictarr/semver-ftw