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.
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.
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
new command creates a task in a local database, and the task consists of the following fields:
Note: A common pattern found in CLI documentation is that arguments enclosed in
<> are required, and those enclosed in
 are optional.
List the tasks that are stored in the local database.
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
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:
Note: On a *NIX operative system like Linux or macOS, you run local executables by using
./; on Windows, you do it by using
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].
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, 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
To generate a command, you can run the following:
npx oclif generate command <command name>
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
Commandclass that is imported from
aliasesproperty is useful for creating aliases for the command; those can be mapped to the root if no topic is specified
descriptionproperty is used to document the command; this text is shown in the help and documentation
examplesproperty defines different ways to use this command, which is useful for the help information and documentation
argsproperty defines the arguments for this command; we will expand on arguments in the following section
flagsproperty defines the flags for this command; we will expand on flags in the following section
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 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
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
To learn more about arguments and their properties, check the oclif documentation.
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
Note: If you want to create a boolean flag, you can do so by using
char property is a shorthand version for that flag, so, instead of passing
--priority Urgent you can pass
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
The arguments and flags values are set in the
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
Note: You can find the full source code of the
command/tasks/list.ts in the julianduque/sf-tasks-cli GitHub repository.
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.
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
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.
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
Connection.create returns a JSforce Connection object, so that you can perform any operation available on that object.
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.
In an upcoming post, you’ll learn how to create a custom plugin for the Salesforce CLI and publish it to the npm repository.
- Getting Started with oclif
- codeLive: Building a CLI Application with oclif (4 videos)
- How to Build a Simple CLI with oclif
- tasks-cli GitHub repository
About the author
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.