Driving the grunt command line with grunt-prompt

The previous blog post outlined the problem we were having with our existing web based editors for creating custom cards. In this tutorial blog post I’m going to show you how I used grunt along with other grunt based extensions to create a command line tool that let’s you create local project folders, and set up configuration parameters based on interactive user input. The examples I will show here are:

  1. Creating a local project folder
  2. Adding configuration information for a new site



If you haven’t already, please be sure to install Node and NPM, and grunt-cli globally. Once installed go to a newly created project folder and run npm init. Then you’ll be ready to start adding project dependencies. Start with grunt-prompt:

Also, if you haven’t used grunt before, check out the basics over at http://gruntjs.com/getting-started.


Creating a local project folder

Custom cards in ThoughtFarmer comprise of the following components:

  1. Server side (C#): A cshtml file that can contain a mix of C# and HTML in a Razor template. It will be rendered server side by RazorEngine and the results sent to the client.
  2. Client side (JavaScript): This is the pure JavaScript code that will run client side for the custom card. If you want to use JSX templates then it must first be transpiled before deploying to ThoughtFarmer.
  3. Styling (CSS): All CSS has its own area for the deployment package.

When using the ThoughtFarmer dev tools the newcard command will create a folder that contains the following files where [name] is the chosen name of the card. For this example I will do a similar project folder creation. The task will create the following folder structure:

  1. ./name/name.cshtml: this maps to the Server side custom card component.
  2. ./name/name.js: this maps to the Client side custom card component.
  3. ./name/name.jsx (if you said yes to JSX): this file is never deployed. Instead it will first be transpiled using Babel into the name.js file.
  4. ./name/name.css: maps to the styling component of the custom card.


Create a GruntFile.js and configure the tasks

Within the grunt.initConfig function you can add the configuration object for the grunt-prompt task called newcard. The layout of the configuration object for grunt-prompt is pretty straight forward. The key pieces of this are:

  • options.questions: This is the array that holds all of the questions that make up each of the prompt tasks.
  • then(results): Once all questions are complete the then function follows up with the needed actions based on those inputs.

I’ve put those together in a sample fully working GruntFile.js below.

A prompt task can contain as many questions as you need (see the grunt-prompt docs for a full list of available types). For this example I only needed the two, seen by the config parameters ‘newName’ and ‘useJsx’.  The config value for the questions are important. This is how you access the user entered value within the then function. Also, you should note the validate and filter functions available for each question. This lets you ensure the data prompted for is in the format you expect.

So now with my task registered I can simply type grunt newcard from my project folder. This will prompt me for the card name, ask if I want to use JSX, then create the folder and file structure accordingly using the grunt.file utility.


Adding configuration information for a new site

In order to make the dev tools easier to use I wanted limit the amount of command line parameters required each time an action was performed. I also wanted to store API tokens for sites, not passwords. So that required gathering some information from the user, fetching the token via API call, then storing parameters in a set-and-forget manner. I used grunt-prompt again to facilitate the questions needed to store all the correct information.


Before getting started be sure to install grunt-http. This will let us make API calls to get the tokens, as well as to deploy the actual code to the ThoughtFarmer site.


Create a “sites” task with shortcut commands

There were a couple other tasks I wanted to role up into the grunt task I called sites. This would let users either list sites configured, add a new site, or delete an existing site from the configuration with nice and short commands. To do this I registered the grunt task as follows:

Each case called its own function that performed the desired actions. If there was a missing or unsupported argument for the task then some help text was displayed in the console.

The addsite function was simple and just ran the tasks needed to prompt for new site information, and fetch the token in order to update the configuration file for the project.


Configure the prompt:newsite task

In the previous example you can see how the configuration object for grunt-prompt is structured within the grunt.initConfig. So the following example is just for the configuration portion of the prompt:newsite task:


The key to using grunt-prompt effectively

Normally, a task’s parameters need to be known ahead of time and are loaded when the grunt.initConfig completes. However, by using grunt.config.set I was able to modify the http:fetchToken task within grunt-prompt’s then function. You can use this method to setup run-time configuration for any task that relies on user defined parameters.  As well as configuration, you can also share user entered parameters across tasks using grunt.option.


Configure the http:fetchToken task

Now that the parameters are captured and stored we can use them in order to make the API Authenticate call to get the token. In order to do this we need to set up the http:fetchToken task in the grunt.initConfig.

You can see in the above that the URL and the Body portion of the request configuration are blank. These are the pieces that are filled in by user-defined parameters from the prompt:newsites task, which must always be run first. If someone were to just run grunt http:fetchToken it would successfully execute but throw an error saying ‘url is a required parameter‘. This is why I set up the command grunt sites:new. Running that will handle the order of execution for us.


Read and write the results to a JSON config file

Once the API call completes the callback runs. Here I check the response for the token that I can add to a special config.json file that remains in the root project directory. I use grunt.file.readJSON to open up the config file and store the contents in a local object variable. From there it is trivial to update and re-serialize the object then save the configuration back to disk using grunt.file.write.


Use command line arguments to fetch config file data

Once this configuration file is set, other tasks can use it in order to call other API tasks to the desired site. No need for re-authentication or specifying the URL again. Everything targeting a configured site can be pulled in via command line options. For example, to deploy a specific customization to a site called dev you could run grunt deploy -dev OR grunt deploy –site=dev. 

Here is a function that will pull in the site name from a command line argument, and populate a site object. You can run something like this first thing in your main grunt function to populate globals used by a variety of tasks and functions.


Leave a Reply

Your email address will not be published. Required fields are marked *