Versioning your Angular app with Gulp and SVN

Recently I worked on a front-end web application that, well, needed some version info. Every release application should have some clear, easy way for a customer to see what the version of the application is. This can be invaluable in helping to track down bugs and keep up with the latest and greatest version of your software.

Most organizations use a version numbering scheme consisting of MAJOR.MINOR.PATCH. In addition, some may use a build number or a revision number on the end. Here, I'll show you how to pull the static version information (MAJOR, MINOR, etc) from a JSON file that you can manually update, and also how to grab the latest revision number from the SVN repository and tack that onto the end. You could technically use this same technique to append a GIT hash on the end of the version number if GIT is what you use for source control, but for now we'll concentrate on SVN.

Setup

To get started, first we need something to version! I personally like the Yeoman scaffolding app for getting started quickly on a new project. Of course, you can use an existing project if you have one. We're going to be using the generator-webapp generator to scaffold out a simple front-end application. Assuming you have yeoman, the generator and its dependencies installed, go to a command prompt, and in an empty directory enter the following:

yo webapp  

Use the default generator options, or customize your app if you like. Yeoman will scaffold out your webapp, and you preview it by typing:

gulp serve  

You should get something like this:
Yo initial screenshot

Cool!

Adding Angular

Now we're going to add Angular for a very simple single page app. Back on the command line, run the following to install some front-end dependencies:

bower install --save angular  
bower install --save angular-bootstrap  
bower install --save angular-animate  
bower install --save angular-ui  

Then, run the following gulp task to inject the bower dependencies into the html:

gulp wiredep  

Now, let's set up the angular app. Open index.html in your favorite text editor. On one of the top-level document elements (such as html or body) define an angular app by adding this attribute: ng-app="version-example". Now, somewhere in the page, add this to create a modal dialog box that will contain some version info:

<div ng-controller="ModalCtrl">  
   <script type="text/ng-template" id="versionModal.html">
      <div class="modal-header">
         <h3 class="modal-title">About this app</h3>
      </div>
      <div class="modal-body">
         Version info will go here
      </div>
      <div class="modal-footer">
         <button class="btn btn-primary" type="button" ng-click="ok()">OK</button>
      </div>
   </script>
   <button type="button" class="btn btn-default" ng-click="open()">Click me!</button>
</div>  

Now, let's add a controller for this modal dialog. Create a new javascript file called controller.js. In that file, put the following:

// Defines the module and its dependencies
angular.module('version-example', ['ngAnimate', 'ui.bootstrap']);

// Define the controller for the modal dialog
angular.module('version-example').controller('ModalCtrl', function ($scope, $uibModal, $log) {

  $scope.open = function () {
    $uibModal.open({
      animation: true,
      templateUrl: 'versionModal.html',
      controller: 'ModalInstanceCtrl'
    });
  };
});

// This controller simply handles closing the modal dialog
angular.module('version-example').controller('ModalInstanceCtrl', function ($scope, $uibModalInstance) {  
  $scope.ok = function () {
    $uibModalInstance.close();
  };
});

Add this line toward the bottom of index.html to include the controller:

<script src="scripts/controller.js"></script>  

Now, when you run gulp serve, you should have an application with a modal dialog that pops up when you click Click me!.

Modal with version placeholder

Adding version info

Now we're ready to use gulp to get revision info from SVN, as well as inject our own hard-coded version numbers. First, we need to install some npm packages.

npm install node-svn-ultimate --save  
npm install gulp-ng-constant --save  
npm install gulp-rename --save  

The first of these, node-svn-ultimate, is simply a javascript wrapper around the command-line interface to Subversion. Note that you must have a CLI SVN client installed for this to work! There are several available - TortiseSVN for Windows comes bundled with one, but you must explicitly select it in the installer. XCode for OS X also includes a SVN client.

The second package, gulp-ng-constant, allows us to dynamically generate Angular constant modules. This is how we will inject version numbers into our app.

The final package, gulp-rename, is a handy utility for easily renaming files.

The Gulp task

So, with these packages installed, we can now add the following to our gulpfile.js:

// gulp task to obtain version info from SVN
gulp.task('version', [], function () {  
  var svn = require('node-svn-ultimate');
  var ngConstant = require('gulp-ng-constant');
  var rename = require('gulp-rename');
  // get the state of the current working directory
  svn.commands.info('.', [],
    function(err, info) {
      var revision = info.entry.$.revision;
      console.log('Current working copy is at revision: ' + revision);
      var versionJson = require('version.template.json');
      versionJson.VERSION.REVISION = revision;
      gulp.src('version.template.json').pipe(ngConstant({
        name: 'versioning-example.version',
        constants: versionJson
      }))
      .pipe(rename('version.js'))
      .pipe(gulp.dest('src/app/scripts'));
   });
});

Let's examine what this does. Line 2 defines the task, with the [] indicating it has no dependencies. The next few lines bring in the required npm packages. Next, on line 7, we issue a svn info command and define a callback for when that completes. On line 9 we pull out the revision number from the JSON object returned by the svn command. (Note that I had to debug this to see what the actual format of the JSON returned was - I'm still not 100% on what the "$" in the middle there represents.) On line 11, we're opening a template version JSON file. The format of this file is as follows:

// version.template.json
****
{ 
   VERSION: {
      MAJOR: 1,
      MINOR: 1, 
      REVISION: 0
   }
}

We read the contents of this file into memory and set the REVISION field equal to what we got out of SVN. Next we pipe this file to an Angular constant module so we can access it easily from our app. Finally, I need to double check and make sure this actually works.