-
Notifications
You must be signed in to change notification settings - Fork 25
Code Your Plugin
This section provides reference information for customizing your plugin.
We provide a number of commands in the plugin-dev plugin to generate your own plugin, commands, flags, and more. See the Get Started section for examples.
First install the plugin:
sf plugins install @salesforce/plugin-dev
Here's the full list of commands:
-
sf dev generate plugin
: Generate the files for a plugin. Includes a samplehello world
command. -
sf dev generate command
: Generate the initial files for a new command. -
sf dev generate flag
: Update existing command files with code for a new flag. -
sf dev generate library
: Generate initial files for a library that multiple CLI commands can call.
Run a command with --help
to see more information.
If you use sf dev generate plugin
to generate your initial plugin, we include a number of yarn
scripts that help with developing and releasing your plugin.
The templates and the CLI team's plugins use wireit by default. It simplifies running scripts in parallel, understandings script dependencies, and uses cached results based on file changes.
If you want to use different scripts, or add to the wireit configuration, you'll want to modify sfdevrc.json
and not package.json; otherwise yarn install
scripts will keep changing it back to the default. The properties under wireit
in sfdevrc.json
will overwrite the matching property in package.json#wireit
during yarn install
. Example
We encourage you to use wireit
dependencies
over npm-style hooks (ex: pretest
) for better performance.
You can always yarn clean-all
to delete all the cache materials if you suspect wrong results due to caching.
Script | Description |
---|---|
yarn / yarn install
|
Install the plugin's dependencies. |
yarn clean |
Delete transient directories and files (such as docs/ , tmp/ , *.log ). |
yarn clean-all |
Run yarn clean and remove node_modules . |
yarn clean:lib |
Delete the compiled source code (lib/ ). |
yarn compile |
Compile source code into lib/ . |
yarn docs |
Generate documentation for your plug-in. Requires that you add a typedoc.json configuration which isn't included in the generated plugin. |
yarn format |
Prettify your source code. This script runs automatically in the husky pre-commit hook. |
yarn lint |
Lint your source code. |
yarn build |
Run yarn clean , yarn compile and yarn lint . |
yarn postpack |
Delete the oclif.manifest.json . |
yarn prepack |
Runs yarn build and generates oclif.manifest.json file. |
yarn test:only |
Run unit tests (files that match *.test.ts pattern). |
yarn test |
Unit tests, test compile/lint, and several checks to prevent breaking changes and documentation bugs |
yarn test:nuts |
Run NUT tests (files that match *.nut.ts pattern). |
yarn version |
Update README with latest commands. |
We encourage plugin developers to use existing npm libraries that already have the functionality you need; there's no reason to reinvent the wheel.
Salesforce owns and maintains these npm libraries to implement common and useful functionality in your plugin.
The @salesforce/core
library provides client-side management of Salesforce DX projects, org authentication, connections to Salesforce APIs, and other utilities. Much of the core functionality that powers the Salesforce CLI plugins comes from this library. You can use this functionality in your plugins too.
-
AuthInfo
,Org
, andConnection
classes to interact with Salesforce orgs. -
Messages
class to work with messages in Markdown, JSON, or JavaScript. -
ConfigFile
class to work with configuration files. -
SfError
class to throw errors from your plugin. -
SfProject
class to work with Salesforce project directories. -
testSetup
utility to write unit tests. - See API docs for details.
-
SfCommand
class, the base class for everysf
command. - Salesforce specific command flags, such as
salesforceId
,requiredOrg
,requiredHub
, andoptionalOrg
. - See API Docs for details.
- A collection of commonly needed utilities. It includes high level support for parsing and working with JSON data, interacting with environment variables, a minimal lodash replacement, and support for common design patterns.
- See API docs for details.
@salesforce/source-deploy-retrieve
- Functionality for working with Salesforce metadata.
- See API docs for details.
- A collection of commonly used types and type-narrowing convenience functions for writing concise type guards.
- See API docs for details.
@salesforce/cli-plugins-testkit
- Testing library that provides utilities to write NUTs (non-unit-tests), such as integration, smoke, and e2e style testing. For example, you could write tests to ensure your plugin commands execute properly using an isolated Salesforce project, scratch org, and different Salesforce CLI executables
- See docs and examples for details.
- Library for creating test stubs with sinon.
- The underlying framework of the entire Salesforce CLI and all its plugins.
- You don’t need to know much about this library in order to develop your plugin. But in case you're curious, here are the docs.
We encourage you to use libraries written by other developers in the npm community. Be sure, however, that you do your due diligence to ensure that you're using libraries that are well-maintained by trustworthy developers.
Here are a few libraries that we recommend:
- got to make HTTP requests.
- graceful-fs for resilient file system operations.
- chalk to colorize output.
- open to open URLs in a web browser.
- change-case to convert strings between different cases, such as camelCase to TitleCase.
- sinon, mocha, and chai to test your plugin.
The following sections describe common coding patterns that you'll likely use in your plugin, along with a code sample. Where possible, we also provide a link to one of our plugins as an additional example.
To throw an error from your command, first add the error message to your messages Markdown file. Use a H1 header for the error name. We suggest you follow the Salesforce CLI style convention of prefacing error names with error
and warnings with warning
. For example:
# error.InvalidUsername
Invalid Username: %s.
Load the message into your command with the Messages.loadMessages
method and throw it using message.createError
. This code example builds on the sample hello world
command.
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-awesome', 'hello.world');
export type HelloWorldResult = {
name: string;
time: string;
};
export default class World extends SfCommand<HelloWorldResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static flags = {
username: Flags.string({
char: 'u',
description: messages.getMessage('flags.username.summary'),
default: 'World',
}),
};
public async run(): Promise<HelloWorldResult> {
const { flags } = await this.parse(World);
// throw an error that's created using the error message defined in the messages file and provide the username to insert into the message.
throw messages.createError('error.InvalidUsername', [flags.username]);
}
}
When a user runs the command and runs into the error, it's automatically prefaced with Error:
, such as:
Error: Invalid Username: doesnt@work.org
The Messages
class also contains the createWarning
and createInfo
methods for warnings and informational output.
When a CLI command encounters an error, it usually returns an exit code of 1. If you don't include any error handling code in your command, oclif handles the error and its default exit code is 1. Similarly, a successful command execution returns a 0 exit code by default.
You can use a different exit code if you want. You must, however, use only those codes that aren't currently being used by Salesforce CLI, or are reserved for its future use. This table shows these error codes.
Error Code | Description |
---|---|
0 | The command executed successfully. |
1 | The command didn't execute successfully. |
2 | oclif detected errors, typically issues with flags. |
3 - 9 | Reserved for future use by Salesforce CLI. |
10 | TypeErrors, which are typically problems in client code. |
11 - 19 | Reserved for future use by Salesforce CLI. |
20 | GACKs, which are problems in Salesforce server code. |
21 - 29 | Reserved for future use by Salesforce CLI. |
68 | Partial success, such as deploying only some requested metadata. |
69 | Request still in progress, or the request timed out. |
130 | The command received a termination signal, such as the user pressing Ctrl+C. |
SfCommand
contains a prompt
method that encapsulates the inquirer library. See the inquirer's documentation for different types of questions you can construct.
This code example show how to change the run()
method in the sample hello world
command to ask the user a question and change the output based on the answer.
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-awesome', 'hello.world');
export type HelloWorldResult = {
name: string;
};
export default class World extends SfCommand<HelloWorldResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static flags = {
name: Flags.string({
char: 'n',
description: messages.getMessage('flags.name.summary'),
default: 'World',
}),
};
public async run(): Promise<HelloWorldResult> {
const { flags } = await this.parse(World);
const answers = await this.prompt<{ confirm: boolean }>({
type: 'confirm',
name: 'confirm',
message: `Hello ${flags.name}! Is that your real name?`,
});
const message = answers.confirm ? `Hello ${flags.name}` : 'Hello ???';
this.log(message);
return { name: flags.name };
}
}
SfCommand
exposes a spinner
class that you can use to put spinners on the terminal if your command takes a while to complete. These spinners are automatically suppressed if the --json
flag is present.
This code example show how to change the run()
method in the sample hello world
command to sleep for a short time, but display the word Loading...
and a spinner while it sleeps.
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import { sleep } from '@salesforce/kit';
Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-awesome', 'hello.world');
export type HelloWorldResult = {
name: string;
};
export default class World extends SfCommand<HelloWorldResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static flags = {
name: Flags.string({
char: 'n',
description: messages.getMessage('flags.name.summary'),
default: 'World',
}),
};
public async run(): Promise<HelloWorldResult> {
const { flags } = await this.parse(World);
this.spinner.start('Loading...');
await sleep(5000);
this.spinner.stop();
return { name: flags.name };
}
}
SfCommand
exposes a progress
class that you can use to put progress bars on the terminal. These progress bars are automatically suppressed if the --json
flag is present.
This code example show how to change the run()
method in the sample hello world
command to sleep for a short time, but display the words Hello World Progress
and a progress bar while it sleeps.
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import { sleep } from '@salesforce/kit';
Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-awesome', 'hello.world');
export type HelloWorldResult = {
name: string;
};
export default class World extends SfCommand<HelloWorldResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static flags = {
name: Flags.string({
char: 'n',
description: messages.getMessage('flags.name.summary'),
default: 'World',
}),
};
public async run(): Promise<HelloWorldResult> {
const { flags } = await this.parse(World);
this.progress.start(0, {}, { title: 'Hello World Progress' });
this.progress.setTotal(100);
for (let i = 0; i < 100; i++) {
await sleep(10);
this.progress.update(i);
}
this.progress.finish();
return { name: flags.name };
}
}
You can easily create and use configuration files using the ConfigFile class from @salesforce/core
. The configuration file is located in the global .sfdx
directory if isGlobal
equals true
. Otherwise it's located in your local project directory.
This code sample shows how to create a global configuration file called myConfigFilename.json
, located in the global .sfdx
directory. The example then shows how to set a key called myKey
to the value myvalue
.
import { ConfigFile } from '@salesforce/core';
class MyConfig extends ConfigFile {
public static getFileName(): string {
return 'myConfigFilename.json';
}
}
const myConfig = await MyConfig.create({
isGlobal: true
});
myConfig.set('mykey', 'myvalue');
await myConfig.write();
sf
uses configuration variables to set CLI defaults, such as your default org (target-org
) or the API version you want the CLI to use (org-api-version
). You set and get configuration variables with the sf config set|get
commands. You can define your own custom configuration variable that is also managed by the sf config
commands.
This example adds a Boolean configuration variable called do-awesome-things
. First create a file in your plugin called configMeta.ts
with code similar to this:
import { ConfigValue } from '@salesforce/core';
export enum ConfigVars {
DO_AWESOME_THINGS = 'do-awesome-things',
}
export default [
{
key: ConfigVars.DO_AWESOME_THINGS,
description: 'do awesome things',
input: {
validator: (value: ConfigValue): boolean => value != null && ['true', 'false'].includes(value.toString()),
failedMessage: 'Must provide a boolean value.',
},
},
];
Then update the package.json
file in the top-level directory of your plugin and add a configMeta
property to the oclif
section, like this:
{
"oclif": {
"configMeta": "./lib/configMeta",
}
}
You can then set the new configuration variable like this:
sf config set do-awesome-things=true
Linters help you write awesome code by flagging programming errors, bugs, stylistic errors and suspicious constructs. If you generate a plugin using our plugin generator (sf dev generate plugin
), you'll also get our recommended linter rules to help you develop the best possible commands for your users.
The rules are open-source and the list is maintained here along with setup instructions
We recommend that you install the eslint plugin in VS Code so that you get feedback in real time as you're developing.
If you extend the base SfCommand
class, you get lots of out-of-the-box functionality to develop highly usable commands. Our command generator (sf dev generate command
) creates a Typescript class for your new command that automatically extends SfCommand
.
Here's a brief overview of the various class properties you can set to alter the behavior of your command. Check out SfCommand
class and the oclif docs to dig a little deeper. To see many of these properties in action, take a look at the DeployMetadata
class, which implements the sf deploy metadata
core Salesforce CLI command.
-
summary
- String that briefly describes the purpose of the command. Displayed with the--help | -h
flags. -
description
- String that provides a more in-depth explanation of the command. Displayed with the--help
flag. -
examples
- Array of strings that provide examples of how to use the command. Displayed with the--help
flag. Pro tip: Rather than "hard code" the CLI command name in the corresponding messages Markdown file, such assf hello world
, use the string<%= config.bin %> <%= command.id %>
instead. The CLI framework automatically inserts the command. -
aliases
- Array of strings that describe the different names that this command can be invoked by. -
state
- Set tobeta
if you want your command to warn users that it's still in development. -
hidden
- Set totrue
if you want to hide your command from users. -
configurationVariablesSection
-HelpSection
that describes the configuration variables that can be used with this command. Only used for displaying help with the--help
flag. -
envVariablesSection
-HelpSection
that describes the environment variables that can be used with this command. Only used for displaying help with the--help
flag. -
errorCodes
-HelpSection
that describes the error codes that could be returned by the command. Only used for displaying help with the--help
flag. -
requiresProject
- Set totrue
if you want the command to throw an error if the user isn't running the command from inside a Salesforce project directory. -
enableJsonFlag
- Set tofalse
to disable the--json
flag for your command.
Add flags to your commands to allow user input to modify the behavior of the command.
To get started quickly with your new flag, run this interactive command in your plugin directory:
sf dev generate flag
All commands automatically have the --help
and -h
flags for displaying long and short command help, respectively.
If your command extends the base SfCommand
class, it also has these flags by default:
-
--json
: Format output as JSON. -
--flags-dir
: Import flag values from a file.
If you pick one of these flag types when you run dev generate flag
, you don't need to write any code at all to make it work!
-
optionalHub
(corresponds to the standard--target-dev-hub
flag) -
requiredlHub
(corresponds to the standard--target-dev-hub
flag) -
optionalOrg
(corresponds to the standard--target-org
flag) -
requiredOrg
(corresponds to the standard--target-org
flag) -
orgApiVersion
(corresponds to the standard--api-version
flag)
Instead, you can simply use the existing Salesforce CLI code to make the flag work the same way as it does in other CLI commands, and even use the standard definition (flag long and short name, description, default value). The dev generate flag
command prompts you for all the information.
Below are the most common properties when defining a new flag. See oclif docs for the full list and @salesforce/sf-plugins-core for Specialty Flags.
-
summary
- Brief overview of the flag's purpose. Displayed with the--help | -h
flags. -
description
- More in-depth explanation of the flag. Displayed only with the--help
flag. -
char
- Short character that the user can use instead of the full flag name, such as-p
instead of--package-name
. -
multiple
- Set totrue
if the user can provide the flag multiple times in the same command execution, e.g.sf do awesome things -i 1 -i 2 -i 3
-
parse
- A function to modify or validate input. The value returned from this function will be the new flag value. -
dependsOn
- Flags that must be passed in order to use this flag. -
exclusive
- This flag cannot be specified alongside these other flags. -
exactlyOne
- Exactly one of these flags must be provided. -
required
- Set totrue
if the user is required to provide this flag on every command execution. -
default
- Provide the default value for a flag if it is not provided by the user. -
hidden
- Set totrue
if you want to hide the flag from the user.
Choose a flag type based on the behavior you want that flag to cause. Using a specific flag type helps you validate the format of the flag value that your user supplies.
This section lists the types you can choose for your new flag. Each section includes a code snippet that shows an example of declaring the flag in your command Typescript file; see the summary
for a description of the type.
See DeployMetadata
class for the flags defined for the sf deploy metadata
core Salesforce CLI command.
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-boolean-flag': Flags.boolean({
summary: 'a flag that expects a true/false value',
}),
}
}
The flag doesn't accept an actual value; simply specifying it at the command line sets it to true
. Sample user input:
--my-boolean-flag
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-dir-flag': Flags.directory({
summary: 'a flag that expects a string that points to a directory',
exists: true, // optionally require the directory to exist
}),
}
}
Sample user input:
--my-dir-flag /Users/romeo/sfdx-projects
This flag takes the input value and converts it to a Duration.
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-duration-flag': Flags.duration({
summary: 'a flag that expects a string that can be converted to a Duration',
unit: 'minutes',
}),
}
}
Sample user input:
--my-duration-flag 33
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
enum MyEnum {
'A' = 'A',
'B' = 'B',
'C' = 'C',
}
class MyCommand extends SfCommand {
public static flags = {
'my-enum-flag': Flags.enum<MyEnum>({
summary: 'a flag that expects a specific value defined by an enum',
options: Object.values(MyEnum),
}),
}
}
Sample user input:
--my-enum-flag B
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-file-flag': Flags.file({
summary: 'a flag that expects a string that points to a file',
exists: true, // optionally require the file to exist
}),
}
}
Sample user input:
--my-file-flag /Users/romeo/sfdx-projects/list.json
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-integer-flag': Flags.integer({
summary: 'a flag that expects a number',
min: 0, // optionally set the minimum acceptable number
max: 100 // optionally set the maximum acceptable number
}),
}
}
Sample user input:
--my-integer-flag 42
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-string-flag': Flags.string({
summary: 'a flag that expects a string value',
}),
}
}
Sample user input:
--my-string-flag "awesome string value"
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-apiversion-flag': Flags.orgApiVersion({
summary: 'a flag that expects a valid Salesforce API version',
}),
}
}
Sample user input:
--my-apiversion-flag 56.0
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-devhub-flag': Flags.requiredOrg({
summary: 'a flag that expects a username of a devhub org that you have authorized',
}),
}
}
Sample user input:
--my-devhub-flag devhub@example.com
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-username-flag': Flags.requiredOrg({
summary: 'a flag that expects a username of an org that you have authorized',
}),
}
}
Sample user input:
--my-username-flag test-wvkpnfm5z113@example.com
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-optional-username-flag': Flags.requiredOrg({
summary: 'a flag that expects a username of an org that you may or may not have authorized',
}),
}
}
Sample user input:
--my-optional-username-flag test-wvkpnfm5z113@example.com
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-sfid-flag': Flags.salesforceId({
summary: 'a flag that expects a Salesforce ID',
length: 18, // optionally set the length of the id
startsWith: '00D', // optionally set the string that the id must start with
}),
}
}
Sample user input:
--my-sfid-flag 04t001122334455ABC
This flag takes the input value and converts it to a URL
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
class MyCommand extends SfCommand {
public static flags = {
'my-url-flag': Flags.url({
summary: 'a flag that expects a url that can be parsed by node's URL class',
}),
}
}
Sample user input:
--my-url-flag https://developer.salesforce.com/docs
Add logging to your plugin with the Logger
class from @salesforce/core
.
Let's show how to add logging to the initial hello world
command that was generated by sf dev generate plugin
.
-
Open
src/commands/hello/world.ts
and update the import from@salesforce/core
to include theLogger
class:import { Messages, Logger } from '@salesforce/core';
-
Now create child logger instance. The
Logger
class creates a root logger with some defaults. Other code, like a command or library, then creates a child logger using theLogger.child()
method. To try it out, replace therun
method with this code:public async run(): Promise<HelloWorldResult> { const log = await Logger.child(this.ctor.name); const { flags } = await this.parse(World); const time = new Date().toDateString(); const message = `Hello ${flags.name} at ${time}`; log.debug(`Time: ${time} | Name: ${flags.name}`); this.log(message); return { name: flags.name, time, }; }
The example shows how you pass the name of the child logger as the first argument of
Logger.child()
. In the example it's set it tothis.ctor.name
which points to the name of the command class (World
in our case), but it can be anything you want. -
Let's now make the logger print records to the terminal. First set the
DEBUG
environment variable, either to*
to see all log records orsf:_LoggerName_
to filter records by logger.For now, let's print logs for only the
hello world
command:For bash/zsh:
export DEBUG='sf:World'
For PowerShell:
$Env:DEBUG = 'sf:World'
-
Run the
hello world
command usingbin/dev
as usual:./bin/dev hello world --name Astro
You should see log and command output similar to this:
sf:World DEBUG Time: Wed Sep 7 2022 | Name: Astro +0ms Hello Astro at Wed Sep 7 2022
-
Remember to unset the
DEBUG
environment variable when you're done:For bash/zsh:
unset DEBUG
For PowerShell:
$Env:DEBUG = ''
The Logger
class wraps pino
, which already sets log levels. See logger.levels for more information.
The CLI saves logs to a file in the global .sf
folder; you can disable this behavior by setting the SFDX_DISABLE_LOG_FILE
environment variable to true
. A new log file is started for each day, formatted like sf-YYYY-MM-DD.log
. They'll be cleaned up eventually if more than 7 days old.
Log records are saved to a log file only if the log level method used is the same level or higher than the level set in the logger instance.
Let's see how this works. By default, the root logger sets the log level to warn
. This example shows calls to debug
, warn
, and error
methods, but only the warn
and error
records are saved to the log file:
log.debug('test1'); // log level: 20, below 40 so don't save it.
log.warn('test2'); // log level: 40, same level as the logger instance so record goes to file.
log.error('test3'); // log level: 50, above `warn` so it's saved to a file too.
The user can set the log level via environment variables (SF_LOG_LEVEL=debug
or SF_LOG_LEVEL=trace
) so it's best not to rely on code to manage your child Logger. Let the user control the log level.
A hook is a piece of code that runs at a specific lifecycle event of a CLI command. Think of a hook as a pause in the CLI command execution. The command executes as usual until it encounters a hook. It pauses while the hook code executes, and then picks up again when the hook code execution is completed. Salesforce CLI supports all the Open CLI Framework (oclif) hooks.
For example, let's say you've configured the oclif hook init
in your plugin. When you run any command in the plugin, the init
hook fires after the CLI is initialized but before the command is found. If you've also configured a prerun
hook, then that one fires right after the init
hook but before the command itself is run. If you've configured a postrun
hook ... You get the idea.
Create a hook by adding TypeScript or JavaScript code and configuration information to your custom Salesforce CLI plugin. You can create a plugin that contains only hooks, or add the hook code and configuration to a plugin that contains all your business logic; it's up to you!
When a user installs the plugin that contains the hooks into their CLI, the hook fires at the appropriate lifecycle event. The hook continues to fire until the user explicitly uninstalls the custom plugin. If you want the hooks to fire in a continuous integration (CI) job, install the custom plugin before you run any Salesforce CLI commands.
The best way to show how to create a hook in your custom plugin is to show how Salesforce CLI itself uses hooks. We'll use the the top-level Salesforce CLI npm package @salesforce/cli as an example.
-
Code the hook in TypeScript or JavaScript. For example, this code is for a
prerun
hook that Salesforce CLI uses to check the version of the plugin that contains the command that the user is executing. The hook alerts the user if their installed plugin version is different from the version bundled with the CLI. -
Update your plugin's
package.json
file and add ahooks
object inside theoclif
object. Thehooks
object specifies the type of oclif hook, such asprerun
, and the location of your compiled source. For example:{ ... "oclif": { "commands": "./lib/commands", "bin": "sf", "hooks": { "prerun": "./lib/hooks/prerun" } ... }
See the @salesforce/cli package.json for another example.
-
After you release your plugin, users install it in their CLI as usual.
sf plugins install my-plugin-with-hooks
From this point on, or until the user uninstalls the plugin, the configured hooks fire when the user runs any Salesforce CLI command.
© Copyright 2024 Salesforce.com, inc. All rights reserved. Various trademarks held by their respective owners.
- Quick Intro to Developing sf Plugins
- Get Started: Create Your First Plugin
- Design Guidelines
- Code Your Plugin
- Debug Your Plugin
- Write Useful Messages
- Test Your Plugin
- Maintain Your Plugin
- Integrate Your Plugin With the Doctor Command
- Migrate Plugins Built for sfdx
- Conceptual Overview of Salesforce CLI