DEV Community

JeanCarl
JeanCarl

Posted on

A review of semantic versioning using npm version

This past week I was watching a Twitch stream and happened to see the developer using the npm version command. I haven't come across this command before, but after looking at it...will be using it from now on. Since I was already playing with it, I thought I would recap it here for others to learn (through a theoretical story), and of course my future self to refer back to.

Here's a little primer on semantic versioning before we dive into using the command from the npm docs.

Git repo setup

I created a private repo on GitHub and cloned it locally:

$ git clone https://github.com/jeancarl/greeting.git
Cloning into 'greeting'...
warning: You appear to have cloned an empty repository.
$ 

Greeting Package

Keeping it simple, I created this module to construct a greeting and output it to the console:

// index.js

class Greeting {
    constructor() {
        console.log('Greeting constructor called');
    }

    hello() {
        return 'hello world';
    }
}

module.exports = Greeting;

Here's my test application that used the new module:

// test.js

const Greeting = require('.');

const greeting = new Greeting();
console.log(greeting.hello());             // outputs hello world

npm init

I needed to create a package.json file. I called the npm init utility to run through a wizard to collect the necessary info:

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (greeting)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository: (https://github.com/jeancarl/greeting.git)
keywords:
license: (ISC)
About to write to /greeting/package.json:

{
  "name": "greeting",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/jeancarl/greeting.git"
  },
  "author": "JeanCarl Bisson <---> (http://jeancarlbisson.com)",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/jeancarl/greeting/issues"
  },
  "homepage": "https://github.com/jeancarl/greeting#readme"
}


Is this OK? (yes)
$

A Quick Test

I ran node test.js to check that everything was working:

$ node test.js
Greeting constructor called
hello world
$

I committed these files to the git repo:

$ git add index.js package.json test.js
$ git commit -m "init"
[master (root-commit) 9e14a36] init
 3 files changed, 38 insertions(+)
 create mode 100644 index.js
 create mode 100644 package.json
 create mode 100644 test.js
$ git push
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 762 bytes | 762.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0)
To https://github.com/jeancarl/greeting.git
 * [new branch]      master -> master
$

I could have npm publish and been excited about this really cool module, but this was just an example and there would need to be more documentation on how to use it.

A Bug!

In my haste to release this module, I had (theoretically) left a small bug that wrote to the console when a Greeting object was created (perhaps a common artifact from debugging what code was executed). Yes, better testing would have prevented this silly bug in the first place...but then I wouldn't have had such an easy bug to fix! I removed the line in the constructor:

// index.js

class Greeting {
    constructor() {

    }

    hello() {
        return 'hello world';
    }
}

module.exports = Greeting;

Testing it again, it all looked good:

$ node test.js
hello world
$

I committed this change:

$ git add index.js
$ git commit -m "Removed console log in Greeting constructor"
[master 17ea869] Removed console log in Greeting constructor
 1 file changed, 1 insertion(+), 1 deletion(-)
$

I now needed to update the npm version. I could have gone into the package.json file and manually updated the version. Instead, I used the npm version patch command:

$ npm version patch
v1.0.1
$

This command accomplished two things. It bumped the patch version in the package.json file, and staged a second commit with the version number as the commit message. I pushed these two changes:

$ git push
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 668 bytes | 668.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/jeancarl/greeting.git
   9e14a36..4de7b41  master -> master
$

Here are the two commits:

A minor new feature!

As my module got popular, someone (theoretically) suggested that we should say hello to a specific name, not just the world.

I modified the hello method with an optional parameter:

// index.js

class Greeting {
    constructor() {

    }

    hello(name = 'world') {
        return 'hello '+name;
    }
}

module.exports = Greeting;

I added another example, passing in my name to the modified method:

// test.js

const Greeting = require('.');

const greeting = new Greeting();
console.log(greeting.hello());             // outputs hello world
console.log(greeting.hello('JeanCarl'));   // outputs hello JeanCarl

Testing it again, it all looked good:

$ node test.js
hello world
hello JeanCarl
$

I committed these changes:

$ git add index.js test.js
$ git commit -m "Added ability to say hello to a custom name"
[master 6505ba6] Added ability to say hello to a custom name
 2 files changed, 5 insertions(+), 4 deletions(-)
$

Since this was a new feature and was backwards compatible, I bumped the minor version:

$ npm version minor
v1.1.0
$

I pushed these two changes:

$ git push
Counting objects: 7, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 827 bytes | 827.00 KiB/s, done.
Total 7 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/jeancarl/greeting.git
   4de7b41..59fe128  master -> master
$

Here are the two commits:

A Major Change

As time goes on this module may grow and change. While the hope is to avoid breaking applications that use this module, technology changes and there might be something that won't be backwards compatible. This is a good time to update the major version.

$ npm version major
v2.0.0
$

And push the change:

$ git push
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 337 bytes | 337.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/jeancarl/greeting.git
   59fe128..a74eaf3  master -> master
$

Versioning your software isn't the most exciting thing, but having this command in your toolbox can help. What other exciting commands do you use?

Top comments (3)

Collapse
 
mattmcmahon profile image
Matt McMahon

npm version will also automatically create a new git tag for you, too. These tags won't be pushed upstream, though, unless they're signed.

Configuring git to cryptographically sign commits would be a great follow up post! 😁👍

Collapse
 
andreandyp profile image
André Michel Andy

Thank you, it is a really good command. I have a webapp that has a lot of commits since November 2017, even with a major release and I forgot (still forgetting) to update the npm version number. Starting from now, I will always update the version number with this useful command.

Collapse
 
jessyco profile image
Jessy

I would love to see an example of using the from-git option and how it works.