Sunday, 27 November 2016

Force CLI Part 5 - Node Wrapper

Force CLI Part 5 - Node Wrapper

Json

Introduction

In earlier posts in this series I shared the odd example bash script that carried out some combination of commands in conjunction with the Force CLI to achieve things like creating a zip backup of Salesforce metadata. The reason I went with bash scripts is that I know the syntax really well and was able to quickly build some tools for managing deployment of our BrightMedia Full Force solution. In essence it allowed us to get big quick.

However, there are a couple of downsides to this approach:

  • Not that many people know bash these days - I spent many years building systems on Linux so I’m pretty familiar with it, but I didn’t want to be a bottleneck
  • Bash isn’t the easiest tool to process XML or JSON format data. You either end up running the files through additional commands such as XMLStarlet, which gives you a whole new syntax/configuration to learn, or you start parsing it using tools like sed and awk, which works for the basics but quickly becomes unusable with complex data.
I’m pushing most of our people towards JavaScript now, with the advent of lightning components, so JavaScript running on the Node platform seemed like the obvious choice to rewrite the scripts in. I then found out that the CLI for SalesforceDX will be built in JavaScript on Node, and will have a plugin architecture to allow us to extend it, which more than justified my choice. 

JSON takes Salesforce Data

While my code behind the tools is now a lot more flexible and easier to maintain, the key use case I’ve found is when extracting Salesforce record data for processing - e.g. carrying out a data fix as part of a release. The Force CLI can output the results of a query formatted as a JSON string, and JavaScript can parse this into an object using the built in JSON.parse() function.

The following example reflects a real-world scenario I encountered a few weeks ago. I was bitten by a known issue with the Winter 17 release of Salesforce, which meant that I couldn’t login to the Lightning Experience. I needed to flip the users in this org to the classic interface, which I started doing via the data loader before creating a script to do the same thing with the Force CLI wrapped in a Node script.

Getting Started

First, download and install Node from the NodeJS org website.

Then create a new directory for the project - in this example a subdirectory named cli.

  • mkdir cli
  • cd cli
Next, create a Node package file for the project:
  • npm init

Accept the defaults for everything and add a description if you so desire.

Note that you don’t strictly need to do this, for the simple example in this post, but it’s a really good habit to get into for when you need to include dependencies on other packages.

Show Me the Code

Save the following code into a file named index.js :

var child_process=require('child_process');

var instance=process.argv[2];
if (instance===undefined) {
    instance='login';
}
var loginOpts=['login', '-i=' + instance];
child_process.execFileSync('force', loginOpts);

// extract the user records as JSON
var queryOpts=['query', 'select id, Username, UserPreferencesLightningExperiencePreferred from User',
               '--format:json'];

var uJSON=child_process.execFileSync('force', queryOpts);

// turn into a JS object
var users=JSON.parse(uJSON);

// dump the users out to the console
for (var idx=0, len=users.length; idx<len; idx++) {
    var user=users[idx];
    console.log('User = ' + JSON.stringify(user, null, 4));
}

 Executing this produces the following (edited) output: 

kbowden@Keirs-MacBook-Air cli $ node index.js
User = {
    "Id": "00524000003vqIgAAI",
    "UserPreferencesLightningExperiencePreferred": true,
    "Username": "ltg.test@bgmctest.brightgen.com",
    "attributes": {
        "type": "User",
        "url": "/services/data/v36.0/sobjects/User/00524000003vqIgAAI"
    }
}
User = {
    "Id": "00524000000FctAAAS",
    "UserPreferencesLightningExperiencePreferred": true,
    "Username": "keirbowden@bgmctest.brightgen.com",
    "attributes": {
        "type": "User",
        "url": "/services/data/v36.0/sobjects/User/00524000000FctAAAS"
    }
}

So I can see that a number of users are setup for the Lightning Experience.

Explain Yourself

Breaking down the code a little, the first line loads the child_process module, which allows a script to spawn other processes:

var child_process=require('child_process');

The next lines figure out the correct Salesforce instance to connect to - the default is login, but you can override this by passing an additional argument after index.js, such as test to connect to the sandbox login endpoint. Once this is done, a child process is spawned to execute the login. 

var instance=process.argv[2];
if (instance===undefined) {
    instance='login';
}
var loginOpts=['login', '-i=' + instance];
child_process.execFileSync('force', loginOpts);

Note that the chid process function takes two parameters - the first is the command to execute (force in this case) and the second an array of parameters to pass to the command. Note also that I use the execFileSync variant to execute the command synchronously and return the output as the result which I promptly ignore!

Next, a query is executed via the Force CLI to extract details of all users in JSON format:

// extract the user records as JSON
var queryOpts=['query', 'select id, Username, UserPreferencesLightningExperiencePreferred from User',
               '--format:json'];

var uJSON=child_process.execFileSync('force', queryOpts);

and this is converted to an array of JavaScript objects:

var users=JSON.parse(uJSON);

Updating the Users

Once I verified that I could extract the users successfully, I could then update each user to use Salesforce Classic, by replacing the final loop with the following code which executes the Force CLI record update command for each returned user:

for (var idx=0, len=users.length; idx<len; idx++) {
    var user=users[idx];
    if (user.UserPreferencesLightningExperiencePreferred) {
        console.log('Reverting user ' + user.Username + ' to classic');
        var updateOpts=['record', 'update', 'user', user.Id,
                        'UserPreferencesLightningExperiencePreferred:false'];
        child_process.execFileSync('force', updateOpts);
    }
}

Running the revised script gives the following (edited) output:

kbowden@Keirs-MacBook-Air cli $ node index.js login
Reverting user ltg.test@bgmctest.brightgen.com to classic Reverting user keirbowden@bgmctest.brightgen.com to classic

Logging in after executing this script confirms that the users have been updated correctly as I am taken straight to the classic UI rather than a sad snowman with an error message! 

Related Posts

 

 

No comments:

Post a Comment