Building a Salesforce CLI Plug-In : Julián Duque

Building a Salesforce CLI Plug-In
by: Julián Duque
blow post content copied from  Salesforce Developers Blog
click here to view original post


The Salesforce CLI is a powerful and essential tool for Salesforce development. It can be used not only to create and build applications, but also to create and manage orgs, import and export data, run tests, and function as an automation tool for multiple purposes. But one of the most powerful, and often overlooked, aspects of the CLI is its extensibility through plug-ins.

In this blog post, we’ll show you how to write a Salesforce CLI plug-in using the @salesforce/plugin-dev generator for the sf CLI. We’ll also cover how to connect to a Salesforce org using the libraries provided by the plug-in generator.

Prerequisites

  1. Node.js LTS
  2. Yarn package manager
  3. The latest version of the Salesforce CLI: sfdx update
  4. Visual Studio Code with Salesforce Extensions (optional but recommended)

Also, you’ll need to install the @salesforce/plugin-dev plug-in for the sf CLI to start creating a new plug-in. You can do so by running:

sf plugins install @salesforce/plugin-dev

Generating a plug-in

Let’s generate a new plug-in by running sf dev generate plugin. This generator will ask you some questions:

  • Whether or not the plug-in is an internal Salesforce plug-in
  • Plug-in name
  • Plug-in description
  • Author information
  • Code coverage % to enforce

As an example, we will create a REST API plug-in that will allow us to perform authenticated Salesforce REST API requests directly from the terminal. We won’t have to worry about authorization tokens, instance URLs, etc. — all of this will be taken care of by our plug-in.

Generating a command

A plug-in can be composed of a stand-alone command or multiple commands grouped by topics. Note that we covered a lot of ground on CLI application fundamentals in the Building a CLI Application with oclif blog post.

The @salesforce/plugin-dev plug-in comes with a handy generator, and we can also generate commands by running:

sf dev generate command --name [command]

Let’s see what files were created for the rest command.

File Description
package.json npm file that describes package dependencies and versions.
src/commands/rest.ts Main TypeScript file that contains the code for the rest command. The command imports and extends classes from @salesforce/sf-plugins-core.
messages/rest.md Markdown file that contains the messages that make up the command help and errors.
test/commands/rest.nut.ts Complex integration, smoke, and end-to-end tests. Also known as NUTS (non-unit-tests.)
test/commands/rest.test.ts Unit tests.

For the purposes of this blog post, we will focus only on the src/commands/rest.ts file. If you want to take a look at the project, the full source code is hosted on GitHub.

import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.load('plugin-rest', 'rest', [
  'summary', 
  'description', 
  'examples', 
  'flags.name.summary'
]);

export type RestResult = {
  path: string;
};

export default class Rest extends SfCommand {
  public static summary = messages.getMessage('summary');
  public static description = messages.getMessage('description');
  public static examples = messages.getMessages('examples');

  public static flags = {
    name: Flags.string({
      summary: messages.getMessage('flags.name.summary'),
      char: 'n',
      required: false,
    }),
  };

  public async run(): Promise {
    const { flags } = await this.parse(Rest);

    const name = flags.name ?? 'world';
    this.log(`hello ${name} from /Users/jduque/blog/cli/rest-api/src/commands/rest.ts`);
    return {
      path: '/Users/jduque/blog/cli/rest-api/src/commands/rest.ts',
    };
  }
}

Some key parts of the previous code include:

  • A command is extended from the SfCommand class from the @salesforce/sf-plugins-core library.
  • Strings (messages) like the summary, description, examples, etc. are stored in markdown format and loaded using the Messages class.
  • Flags are defined with the Flags class from @salesforce/sf-plugins-core, this will give you more options for flags like directory, url, requiredOrg, orgApiVersion, and others. More information about the available flags can be found in the documentation.
  • The run method is where the command logic is executed. It is recommended to specify a return type, which will be used by the --json flag.

Implementing a Salesforce REST API command

The command that we want to implement will allow us to perform authenticated requests to the Salesforce REST API. Before implementing the logic, it is recommended to design how your plug-in will work. More information about how to design a plug-in can be found in the documentation.

# perform a GET request to the provided REST API path 
# (without /services/data/<apiVersion>
sf rest [path] --target-org [target-org] 

# Example: Describe the Account object
sf rest /sobjects/Account/describe --target-org mydevhub

Arguments

  • path — represents the path of the API resource (with or without the /services/data/<apiVersion>/ prefix)

Flags

  • target-org, o — represents the username or alias of the authenticated org
  • method, m — represents the HTTP method used to interact with the API
  • content-type, t — represents the Content-Type header of the request (either application/json or application/xml)
  • payload, p — represents the request payload either in JSON or XML
  • api-version — represents the Salesforce API version to use for the request (if not present, it will use the current default)

Connecting to a Salesforce org

There are two ways to connect to a Salesforce org:

  1. We can create a connection using the AuthInfo and Connection classes from the @salesforce/core library. For that, we would need the org username (or alias), which will be provided by the target-org flag.

 

import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { AuthInfo, Connection, Messages } from '@salesforce/core';

// ...

export default class Rest extends SfCommand {

  // ...

  public static flags = {
    // target-org as a plain string
    'target-org': Flags.string({
      summary: messages.getMessage('flags.targetorg.summary'),
      char: 'o',
      required: true,
    }),
  };
 
  // ...
 
  public async run(): Promise {
    const { flags, args } = await this.parse(Rest);
    
    // Create the AuthInfo and the Connection
    const authInfo = await AuthInfo.create({ username: flags['target-org'] });
    const conn = await Connection.create({ authInfo });
    
    // ....
  }
}

Fortunately, the @salesforce/sf-plugins-core give us better utilities for this. We can use the custom Flags.requiredOrg utility to retrieve the org by username or alias, and this will return an authenticated org object when the flags are parsed.

 

import { SfCommand, Flags } from '@salesforce/sf-plugins-core';

// ...

export default class Rest extends SfCommand {

  // ...

  public static flags = {
    // target-org as a required Org object
    'target-org': Flags.requiredOrg({
      summary: messages.getMessage('flags.targetorg.summary'),
      char: 'o',
    }),
  };

  // ...

  public async run(): Promise {
    const { flags, args } = await this.parse(Rest);

    // Get the Salesforce org and Connection directly from the requiredOrg flag.
    const org = flags['target-org'];
    const conn = org.getConnection();
    
    // ...
  }
}

Using the Connection object

The Connection class extends from JSforce Connection, which means that you’ll be able to perform any operation supported by JSforce. For the purpose of this plug-in, we will use the request method to perform raw HTTP requests to the Salesforce REST API. Note: JSforce is a powerful JavaScript library to develop Salesforce applications.

public async run(): Promise {
    const { flags, args } = await this.parse(Rest);

    // Get the Salesforce org and Connection
    const org = flags['target-org'];
    const conn = org.getConnection();

    // Set Api Version if specified
    const apiVersion = flags['api-version'];
    if (apiVersion) {
      conn.setApiVersion(apiVersion);
    }

    // Extract HTTP parameters from flags
    const method = flags.method as 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
    const contentType = flags['content-type'];
    const payload = flags.payload;

    // Perform HTTP request to Salesforce API using the Connection object
    const response = await conn.request({
      method,
      url: args.path as string,
      headers: {
        'content-type': contentType,
      },
      body: payload,
    });
    this.styledJSON(response);
    return response;
  }

The plug-in logic as shown above does the following:

  • Extracts the org from the target-org flag
  • Gets the connection from the org using org.getConnection(), which will return an authenticated connection object
  • Sets the apiVersion to the connection if the flag is specified
  • Extracts the HTTP parameters from flags
  • Performs the HTTP request using the request method from the connection object
  • Prints the output as a styled JSON using the this.styledJSON() method

Note: Be sure to authenticate into an org using sf login org before using the sf rest plug-in.

And finally, you can link the plug-in with your local sf installation by running:

sf plugins link

And voila! Now you can use the plug-in and start performing Salesforce REST API requests using the sf CLI.

What’s next?

Make sure to take a look at the big improvements coming to the Salesforce CLI, especially those related to plugin development being easier than ever.

Also, we didn’t cover how to write tests for a Salesforce plug-in, which will require its own blog post. Luckily, our friend and Lead Developer Advocate Mohith Shrivastava already presented a codeLive session where he taught how to test Salesforce CLI plug-ins using NUTs, and there will be a follow-up post that we will publish soon. Stay tuned, and happy coding!

Learning resources

About the author

Julián Duque is a Principal Developer Advocate at Salesforce. He is a developer and educator and spends his time running TTRPG games online and playing and training his Mini Aussie, Cumbia, and his Sphynx kitten, Nefertiti.

The post Building a Salesforce CLI Plug-In appeared first on Salesforce Developers Blog.


January 10, 2023 at 09:30PM
Click here for more details...

=============================
The original post is available in Salesforce Developers Blog by Julián Duque
this post has been published as it is through automation. Automation script brings all the top bloggers post under a single umbrella.
The purpose of this blog, Follow the top Salesforce bloggers and collect all blogs in a single place through automation.
============================

Salesforce