The most common way to interact with an operating system is through a user interface (UI), where familiar windows, labels, forms, and buttons make an appealing user experience (but not always … that’s why UX visual design is a dedicated science now). But, a UI is not the only way; more technical users (like us developers) prefer to use a command line to interact with the operating system.

Windows, macOS, Linux, and other operating systems come with a way to interact through commands using a terminal application. You can perform multiple sets of tasks, from navigating the filesystem to manipulating files and folders to performing more complex tasks. These command line interfaces, or shells, come with a Turing-complete programming language, so you can create your commands and automate specific tasks or even an application that interacts with an API.

In this post, you’ll learn how to create a command line application using a Node.js framework created here at Salesforce called oclif, the Open CLI Framework. We will not discuss those native programming languages like Bash (Linux, macOS) or Power Shell (Windows); instead, we will use a more familiar language, TypeScript (or JavaScript), to create a simple but powerful command-line application.

What is oclif and why does it matter?

oclif is an open source framework for Node.js for creating command-line applications. It was created back in 2018 by Salesforce, and it’s the one that powers not only our Salesforce CLIs, but also the Heroku CLI. Other companies like Twilio, Adobe, and Shopify also rely on oclif for their CLI applications.

Our Salesforce CLI project is open source and can be extended through plugins, and these plugins are created using oclif. If, as a Salesforce Developer, you need to create a custom plugin for your workflow, or an application or utility that relies on our open-source libraries, learning oclif is an excellent starting point.

Designing a CLI application

Before we build our CLI application, let’s see exactly what we will build. Let’s say that we want to build a command to create Salesforce Tasks. This command will store tasks locally and provide a sub-command to synchronize local tasks with remote tasks on Salesforce, which means pushing local not-synced tasks and pulling remote tasks.

Let’s design the commands we want to implement and their different arguments and flags.

Structure of an oclif command

oclif commands can either be added as a stand-alone commands or multiple commands grouped by topics. For our task application, we will create a tasks topic to group all the related commands. If you want to add other functionality to this application, think about the type of commands and topics you can create to organize your app better.

Create a new task

The new command creates a task in a local database, and the task consists of the following fields: Subject, Comment, Priority, and Status.

Note: A common pattern found in CLI documentation is that arguments enclosed in <> are required, and those enclosed in [] are optional.

List tasks

List the tasks that are stored in the local database.

Synchronize tasks

The sync command synchronizes with Salesforce, pushing local, not-synced tasks, and pulling remote tasks. If a task already exists, it will be updated.

Generating a CLI application

oclif comes with a project generator that will scaffold what’s needed to start building a CLI application. It will create a Node.js project with TypeScript support and a handy “hello world” command to show you how a command works.

Let’s generate a project by running: npx generate oclif tasks-cli

oclif project generator running on a terminal

This will create a tasks-cli folder with the source code needed to start building your oclif CLI. Let’s explore it before we start with our app.

Go to the directory by running cd tasks-cli and run the development executable: ./bin/dev

Note: On a *NIX operative system like Linux or macOS, you run local executables by using ./; on Windows, you do it by using .\, e.g., .\bin\dev.

tasks-cli showing the autogenerated help output

You’ll see the help that oclif generates for you — how cool is that?! Here you can see the available topics and commands that you can execute; if you want to learn more about a command, you can run ./bin/dev help [command].

tasks-cli showing the autogenerated help output for the hello command

Now, we can jump into the source code of our project; the topics and commands logic lives under the src/commands folder. A topic is just a subdirectory with command files. You can nest as many sub-commands as you want, but isn’t recommended to go more than one or two levels deep.

tasks-cli project structure showing the commands folder

For the tasks-cli project, we don’t need the hello topic; go ahead and delete it, but before, take a look at the commands/hello/index.ts source code and see what it does. If you don’t understand it, it’s ok, we will explore the structure of an oclif command in the next section.

Creating new commands

We want to create the following folder structure for the tasks-cli application, but don’t do it manually, oclif provides a generator for commands.

Note: Don’t worry about the lib/ folder, we are not covering its contents in this post. You can get the full source code from the julianduque/sf-tasks-cli GitHub repository. This contains a class with the required methods to store the data locally using sqlite3.

To generate a command, you can run the following: npx oclif generate command <command name>

oclif generating a new command
Now, we have two new files, our command class, and a test file. We will cover testing in a future post; for now, let’s focus on the command class structure.

We’re using here a slightly simplified version of the new command used in the final application. Let’s comment on the most important bits of this code.

  • an oclif command extends from the Command class that is imported from @oclif/core
  • the aliases property is useful for creating aliases for the command; those can be mapped to the root if no topic is specified
  • the description property is used to document the command; this text is shown in the help and documentation
  • the examples property defines different ways to use this command, which is useful for the help information and documentation
  • the args property defines the arguments for this command; we will expand on arguments in the following section
  • the flags property defines the flags for this command; we will expand on flags in the following section
  • the run() method is where the magic happens; here we write the command logic

For more information about commands, check the official oclif documentation.

Arguments and flags — how to interact with commands?

A command can receive both arguments and flags, but what are those? Let’s define them and see how we can set them up in an oclif application.

Arguments

Arguments are positional strings that can be passed to a command, for example task new Call "Follow up with the customer". In the previous example, both Call and Follow up with the customer are arguments. To define those in oclif, you define the args property with an array of arguments and its properties:

Note: Remember the positional part of the definition? Well, here, the position matters; the arguments order is specified in the args array.

To learn more about arguments and their properties, check the oclif documentation.

Flags

Flags, on the other hand, are non-positional; they can be passed in any order and receive either a string value or a boolean value if the flag is present. To define flags in oclif, you can use the Flag class that is imported from @oclif/core and set them in the flags property.

Note: If you want to create a boolean flag, you can do so by using Flags.boolean.

The char property is a shorthand version for that flag, so, instead of passing --priority Urgent you can pass -p Urgent.

To learn more about flags and their properties, check the oclif documentation.

Parsing arguments and flags

We’ve learned how to define arguments and flags, but how can we extract the values passed to our command? We do that in our run() method by running this.parse().

The arguments and flags values are set in the args and flags objects, and you can access them using the JavaScript dot notation, e.g., flags.priority or args.subject.

UX is everything!

oclif comes with handy tools to improve the experience of both our application users and the systems that will rely on it.

Sometimes, the users of our applications aren’t humans but other commands that rely on our output.
For that, oclif offers a handy way to return data as a JSON object. To achieve this, you’ll need to set the enableJsonFlag to true.

task-cli list command with --json flag showing the output as a JSON object
When interacting with humans, the CliUx class from @oclif/core offers utilities to prompt for information, render data as a table, and show a spinner while our application performs a task.

Note: You can find the full source code of the command/tasks/list.ts in the julianduque/sf-tasks-cli GitHub repository.

tasks-cli executing the list command showing a CliUx table

You can also rely on more advanced CLI UX components from the npm ecosystem, such as inquirer (see docs), which offers more options for prompting, like a select box.

Note: You can find the full source code of the command/tasks/new.ts in the julianduque/sf-tasks-cli GitHub repository.

task-cli showing an inquirer prompt with multiple choice options

Using plugins

One of the main advantages oclif has is its plugin capabilities. You’ll be able to not only write plugins for your applications, but also use plugins published by others. For our tasks application, we will use the @salesforce/plugin-auth to allow our application to authenticate into a Salesforce org; this plugin is the same one the sfdx CLI uses.

Let’s install the plugin by running ./bin/dev plugins install @salesforce/plugin-auth. This will install the plugin on our application, and add a new auth topic. Now, we can authenticate into a Salesforce org the same way we do with the sfdx CLI.

task-cli showing the output of the auth topic from @salesforce/plugin-auth

Connecting to Salesforce

Another useful open source project from the Salesforce CLI team is @salesforce/core (see GitHub repo). With this, we can both create an authentication object and create a connection to an org.

Note: You can find the full source code of the command/tasks/sync.ts in the julianduque/sf-tasks-cli GitHub repository.

The AuthInfo.create method requires a username to get the org information from the ones you have authenticated previously (for example, with the auth command we just added). To provide a good UX, we are asking for it as a --target-org flag.

Connection.create returns a JSforce Connection object, so that you can perform any operation available on that object.

Note: JSforce is a powerful JavaScript library to develop Salesforce applications.

Conclusion

oclif is a powerful framework for creating CLI applications. We haven’t discussed everything it can do. For example, you’ll be able to publish this application and make it available to the public, and even package it for different operative systems. If you want to learn more about it, you can read the Release section from the oclif documentation.

I’ve created a series of videos where I built this tasks-cli application live on YouTube. You can find a link in the Learning Resources section below.

What’s next?

In an upcoming post, you’ll learn how to create a custom plugin for the Salesforce CLI and publish it to the npm repository.

Learning resources

About the author

Julian Duque

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

Get the latest Salesforce Developer blog posts and podcast episodes via Slack or RSS.

Add to Slack Subscribe to RSS