Using a child process to simplify the command line for Grunt tasks.

In the previous post you got an idea of how the Grunt utility can be used as a deployment and packaging utility for creating ThoughtFarmer custom cards. One of the main goals of these tools was ease of use. The difficulty here was that each custom card needed to know a few things in order to run a specific task. With all these parameters required, the command line was becoming rather cumbersome.

Let’s look at the example of the task grunt deploy. This task would run babel, to transpile and JSX to proper JavaScript, set up the API parameters and deploy the package. In order to do this it needed the following parameters specified:

  • folder: The current project folder that contained the code you wished to deploy.
  • site: The actual site the card was to be deployed to.
  • id: The customPortletID for the card on the site.

So a typical deployment command would end up looking like this:

It’s very clear what is happening here. But it really becomes a pain having to type all of this in each time you want to run a different command. It would be much preferable to manage the configuration and tasks in such away that required parameters are automatically pulled from configuration files.

 

Config file for all the things!

The main component that helped bring this together was various configuration files that would be able to store key parameters. You set and forget. The first one lives in the main working folder for all custom card projects. All of the tasks around sites management used this folder in order to store configuration information. There was a grunt task that went over collecting information for a site from the command line, including url, username and password. The task would then attempt to fetch the API token and store that for future use. Once complete, the root directory contained a config.json file that looked like the following:

This file was the source for fetching information when users entered the command line flag –site=comm. I also added support for a shortcut flag syntax so users could simply enter -comm. Grabbing that from the command line was done in a function called getSiteParams that ran before anything else to populate a global _site object used by various other tasks and functions.

 

Setting up a configuration task

In order to tighten up the workflow, a newcard task would also run the task configure. This would prompt the user for the customPortletId for each site that was currently configured in the aforementioned sites config.json. This id data would live in each of the custom card folders. That way it was simple to tie a customPortletId for a specific card and have it line up with the configuration for a site. The config.json for this would end up looking like:

Now the catch here is that the grunt.initConfig that sets up grunt-prompt needs to no ahead of time what questions are a part of the configure task. However, the questions themselves vary depending on changing and environment specific settings. We want to run configure based on the contents of the main config.json.

To accomplish this I set up the configure task as follows:

The check folder function merely checks to ensure the —folder parameter has been set either by the command line, or by virtue of the folder the task is being run from (more on that in the following section). If it is not set it throws a warning to stop the current grunt task and gives a helpful message to the users. It looks like this:

The global _folderPath would have already been parsed from the command line before any other actions have been performed.

Set up dynamic prompts based on config files

Since, as mentioned, the exact questions are not known when the grunt.initConfig is initially run, I just set that task up as a stub. So if you ran it without any other information it would be an empty task with no questions. To populate this dynamically, I used grunt.config within the setupConfigPrompt function that was part of the configure task.

As you can see, the function would grab a copy of the local config.json for the current working folder, or from the –folder command line parameter. It would then iterate over all of the sites found in the root config.json and set up a grunt-prompt question for each site. If an id was already found for that site, it would prompt for a new one, or just hit enter to keep. Otherwise, it would prompt for the id, or just hit enter to skip configuration for that site.

 

Spawning a child grunt process

Now you may ask why I needed to even needed to spawn a child process as all. Now recall, that the goal was to streamline the command line parameters required for executing tasks. The problem was with the way grunt operates in order to find the closest gruntFile.js. If you look at the docs you can see that grunt will look in the current folder, or the parents for the closest grunt file. However, the way it does that is by changing the working directory when it does not find a grunt file in the current folder. It also does not preserve the origin of where the command was originally executed. So in this case, a user could navigate to the folder that they were working on, run a grunt task and it would successfully find the grunt file, but have no idea where the original command came from. So the user would need to enter the –folder parameter, even though it was painfully obvious already.

To get around this it was necessary to create a child gruntFile.js that lived in each of the custom card folders. Setting this up became a part of the newcard task I wrote about in a previous blog post. The contents of the file were just saved as a string variable, then added to the new custom card folder. The contents of the file were as follows:

The child process was actually from the main gruntFile in the parent folder. The grunt task run-grunt simply passed along the pre-requisite information based on the current working directory. Any task that was supported from the child folder needed to be mapped to this task. It simply passed itself along and the child task would execute it as if it had been run directly.

The biggest trick to this is being sure to pipe the input and output streams. This is especially important since we want to display messaging and also gather input from the command line to the child process. You can see how that is accomplished in the above example through child.stdout.pipe, child.stderr.pipe, and process.stdin.pipe.

 

Conclusion

With everything in place it was now very easy to use the tool. Once the sites, and the custom card configuration utilities were run all the parameters were stored for reuse. The child gruntFile.js passed along the –folder parameter and the location of the associated config.json that stored customPortletIds. As well, I added the shortcut that if no site parameter was specified then it would just use default. This was typically a local development instance. As you see, the changes made things much simpler:

The simplified version would pass along the folder name so that grunt knew exactly where to pull the id configuration from and also which files to deploy. Since the sites configuration was also already set up nothing else needed to happen. If you wanted to switch the site, just add the flag for it. If the sites configuration existed then it would proceed successfully, and warn you otherwise.

The overall benefits to the user were obvious. What was originally a somewhat clumsy tool, now became more user friendly and productive.

 

 

Leave a Reply

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