Running Grunt Build Scripts in TeamCity

Running Grunt from TeamCity

Writing a build script for my DocPad project was only the first step before finally setting up a configuration in TeamCity which automatically runs on every commit. Although there is no built in support for Grunt in TeamCity, it was easy enough to find a plugin for it, so that I didn't have to invoke it from command line. Plugin installation was straightforward as usual for TeamCity plugins. Of course, the correct version of Node.js must also be installed on the server which is hosting the build agent.

The first step in the build configuration must install the NPM dependencies which are not committed in the source control repository. Thanks to the already mentioned TeamCity.Node plugin this can be easily done by just selecting the Node.js NPM runner and calling it with install command as always. As long as Node.js is properly installed (and TeamCity was already restarted after the installation to pick up new machine-wide paths), it just works.

Since I already had a build script ready, I only had to run my existing Grunt tasks (generate, test and deploy) as the next 3 steps in my TeamCity configuration. Again, it was enough to select Grunt as the runner and enter the name of the task for each of the steps. As suggested in the Grunt documentation, I selected the runner to use the NPM package from project instead from a system-wide installation. It came as a slight surprise to me that this meant grunt-cli will have to be installed as a local development dependency in the project as well.

Using Globally Installed Packages

The next stumbling block on the path to a working build in TeamCity were globally installed NPM packages - DocPad to be exact. In the recent versions of Node.js NPM is globally installing packages per user and not per machine. This avoids the need to have administrative privileges when globally installing packages, but requires them to be installed separately for every user which will need them.

In the case of TeamCity build agents, this means that it is necessary to globally install the required packages also for the user account which is being used by them. As long as they are running as regular users and don't use any of the system accounts, this shouldn't be a problem: you can always log in with that account and install the missing packages.

For agents, using the Local System account, as configured by default, it is possible to start a command prompt for that account using PSTools and install the package from there. This isn't enough, though, because the install location (%WINDIR%\System32\config\systemprofile\AppData\Roaming\npm) in this case isn't added to path, as it is for the other users. Since running services with Local System account isn't a good idea any way, I would suggest to use a dedicated service account for the agents and avoid the issue altogether.

Nicer Output in TeamCity Logs

The steps above were enough to achieve a working build inside TeamCity, but in case of failed builds the output only included the name of the step which failed without any additional information:

[14:44:54]   Step 3/4: Test (Grunt)
[14:44:55]      [Step 3/4] Step Test (Grunt) failed

For cases like this TeamCity supports service messages in special format which are automatically parsed and aggregated to the build overview page. To make matters even simpler, there's already a Grunt task available which adds support for them to any existing Grunt task. The usage of grunt-teamcity is really simple:

grunt.initConfig(
  {
    teamcity:
      all: {}
  }
)

grunt.loadNpmTasks('grunt-teamcity')

grunt.registerTask('generate-TC', ['teamcity', 'generate'])
grunt.registerTask('test-TC', ['teamcity', 'test'])
grunt.registerTask('deploy-TC', ['teamcity', 'deploy'])

To add emitting of TeamCity service messages to other tasks, teamcity task always needs to be executed before the other ones. To avoid its extra output when running Grunt from command line, I decided to add a simple wrapper for each of the tasks which runs teamcity as the very first task, and changed build configuration in TeamCity to start these wrapper tasks instead of their original counterparts. Now the output provides much more information in case of a failure:

[15:47:11]   Step 3/4: Test (Grunt) (1m:08s)
[15:47:16]      [Step 3/4] Resource not found linked from http://localhost:9778/ 
                           to http://localhost:9778/blog/archive.html
[15:47:16]      [Step 3/4] Status code: 404
[15:48:19]      [Step 3/4] Aborted due to warnings.
[15:48:19]      [Step 3/4] Done, but with warnings.
[15:48:19]      [Step 3/4] Step Test (Grunt) failed

The same report is also included in the mail sent from TeamCity every time a build fails. In most cases the included information should be enough to fix the build even without looking at the complete log in TeamCity. I'm quite satisfied with that.

Copyright
Creative Commons License