Creating NuGet packages with Grunt

Grunt is a JavaScript task runner that can  be used to automate (among other things) the various tasks around building and deploying JavaScript: concatenation, minification, JSHint, QUnit. etc. etc.

There are plenty of example configurations for the tasks above – particularly this example from the grunt documentation – but I wanted to do one more step at the end of my build: create a NuGet package.

Setup

For this example, let’s assume that we have the following structure:

+ src
   - source1.js
   - source2.js
 + dist
   - compiled.js
   - compiled.min.js
 - grunt.js
 - package.json

Here we have 2 source files (under src) that have been compiled into compiled.js and compiled.min.js under the dist folder.  For the purposes of this example it doesn’t matter whether they have been created manually or have been generated by an earlier grunt task; we just want to make sure that they are there.

In addition to these we have gruntfile.js and package.json which define the available grunt tasks and the package.  The grunt docs cover these files in detail so I’m not going to go over the initial contents, but they are included below for reference.

package.json

{
  "name": "my-library",
  "version": "0.1.2",
  "devDependencies": {
    "grunt": "~0.4.1"
  }
}

The important things to note here are the definition of the package name and package version – we will use those later.

gruntfile.js

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json')
    //other task configuration here
  });

  grunt.registerTask('default', [
    /*other tasks here*/
  ]);
};

The gruntfile would ordinarily contain the definitions of all of the other tasks that have been configured, but for clarity I have omitted them.

The NuGet Definition

The first step in automatically generate our NuGet package is to create a definition for the package in a Nuspec file.  This is an XML document with some pretty-much self explanatory fields:

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <!--
    ID used to identify the nuget package -
    e.g. ProxyApi -> https://www.nuget.org/packages/ProxyApi
    -->
    <id>my-library</id>

    <!--
    Package version - we are going to leave this blank for now
    -->
    <version>0.0.0</version>

    <!-- Author, Owner and Licensing details -->
    <authors>Steve Greatrex</authors>
    <owners>Steve Greatrex</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <copyright>Copyright 2013</copyright>

    <!-- General Information -->
    <description>Helper extensions for KnockoutJs</description>
    <releaseNotes>-</releaseNotes>
    <tags>JavaScript KnockoutJS</tags>

    <!--
    Packages (incl. minimum version) on which this package
    depends
    -->
    <dependencies>
      <dependency id="knockoutjs" version="2.1.0" />
      <dependency id="jQuery" version="1.8.0" />
    </dependencies>
  </metadata>
  <files>
    <file src="dist\compiled.js" target="content\scripts" />
    <file src="dist\compiled.min.js" target="content\scripts" />
  </files>
</package>

Important things to note here:

  • Dependent packages are listed under package/metadata/dependencies.  These define which other NuGet packages will be installed as prerequisites to your package

    • Package outputs (i.e. what will get inserted into the project) are listed under package/files.
    • File locations are relative to the path from which we will run NuGet.exe
    • For content files (i.e. not a referenced assembly), paths are relative to a special “content” path which refers to the root of the project.  In the example above, the files will be added to the scripts folder in the project root

We can now pass this file to NuGet.exe and it will generate a .nupkg package for us that is ready to be published.  To test this, let’s put a copy of NuGet.exe alongside the saved my-library.nuspec file and run the following command:

nuget pack my-library.nuspec

Voila – my-library.0.0.0.nupkg has been created!

Invoking NuGet from Grunt

In order to invoke this command from our grunt script we will need to create a task.  To keep things simple we will use the basic syntax for the custom task:

grunt.registerTask('nuget', 'Create a nuget package', function() {
  //do something here
});

The implementation of task just needs to call the NuGet.exe as we did above, but with a couple more parameters.  We can achieve this using grunt.util.spawn to asynchronously invoke a child process.

grunt.registerTask('nuget', 'Create a nuget package', function() {
  //we're running asynchronously so we need to grab
  //a callback
  var done = this.async();

  //invoke nuget.exe
  grunt.util.spawn(
    {
      cmd: 'nuget.exe',
      args: [
        //specify the .nuspec file
        'pack',
        'my-library.nuspec',

        //specify where we want the package to be created
        '-OutputDirectory',
        'dist',

        //override the version with whatever is currently defined
        //in package.json
        '-Version',
        grunt.config.get('pkg').version
      ]
    },
    function(error, result) {
      //output either result text or error message...
      if (error) {
        grunt.log.error(error);
      } else {
        grunt.log.write(result);
      }
      //...and notify grunt that the async task has
      //finished
      done();
    }
  );
});

Things to note here:

  • This code should appear in gruntfile.js after the call to initConfig but before the call to registerTask
  • Arguments to NuGet.exe are specified in the args array
  • The -Version argument is used to override the value from the .nuspec file with whatever has been loaded from package.json.  This avoids the need to define the version number in multiple locations.

With this in place we can add the nuget task to the default task definition from our original gruntfile and we are ready to go.

grunt.registerTask('default', [/*other tasks here*/ 'nuget']);

Fire up the shell run the grunt command, and a nuget package will be created for you:

$ grunt
Running "nuget" task
Attempting to build package from 'my-library.nuspec'.
Successfully created package 'dist\my-library.0.1.2.nupkg'.
Done, without errors.

Combine this with concatenation, minification, unit tests and static code analysis and you have a very quick one-hit process to go from source to publication!