-
Notifications
You must be signed in to change notification settings - Fork 25
Migrate Plugins Built for sfdx
You can install and use plugins originally built for sfdx
in sf
, although the general look-and-feel is different. We therefore recommend that you explicitly migrate your sfdx
plugin to sf
so its commands look and behave the same as other sf
commands. You can also then take advantage of the many improvements we've made to the code that supports sf
plugins.
If you haven't already, update @oclif
and the @salesforce/core
library to their current versions.
- Update to v2 of
@oclif/core
by following this document. - Update to v3 of
@salesforce/core
by following this document. We also discuss converting your messages files later in this document.
Salesforce provides a set of eslint rules for Salesforce CLI plugins.
When migrating an sfdx
plugin to sf
, we recommend you use a set of additional rules that do most of the work for you. See the setup instructions for more details.
Be aware that some of these eslint rules automatically change some of your code while other rules provide suggestions where you can choose which fix to apply.
Although v3 of @salesforce/core
works with your existing JSON message files, we recommend that you migrate them to Markdown. Message files contain the --help
content and error messages.
-
If you haven't already, install
plugin-dev
:sf plugins install dev
-
From the top-level directory of your plugin, run the
dev convert messages
command to convert an existing JSON file to a first-draft Markdown file to get you started. For example:sf dev convert messages --file-name messages/mycommand.json
In this case, the command generates the Markdown file
messages/mycommand.md
. -
Edit the resulting Markdown file to ensure your messages look good. See the next section for a tip to reduce maintenance, and then Write Useful Messages for general information about writing messages.
-
Run
sf dev audit messages
to find missing or unused messages, then update your code to add or remove references to the messages as needed. -
Remember to delete the old JSON files when you're finished!
Messages in v3 of @salesforce/core
support dynamic references to the command and Salesforce CLI binary names. Specifically, the placeholder <%= config.bin %>
refers to the binary name (sfdx
or sf
) and <%= command.id %>
refers to the command.
If your plugin supports both sfdx
and sf
, use these placeholders in message files rather than hardcoding the command and CLI executable name. This way you won't need to rewrite the messages when you change your command structure or name.
Here's an example from the Markdown message file for the org delete sandbox
command:
# examples
- Delete a sandbox with alias my-sandbox:
<%= config.bin %> <%= command.id %> --target-org=my-sandbox
We encourage you to follow sf
's styles in your command and flag names; see Design Your Plugin for details. If you need to maintain backward compatibility, you have some options.
If you rename a command, you can alias it back to its original name.
export class LimitsApiDisplayCommand extends SfCommand<ApiLimits> {
...
// this command is now `limits api display` but was previously 'force:limits:api:display'` so an alias keeps that old name operable
public static readonly aliases = ['force:limits:api:display', 'org:list:limits'];
// when someone uses the old name, display a warning encouraging them to use the new name.
// without this property, the old name works but no warning occurs.
public static deprecateAliases = true;
Similarly, if you rename a flag, you can alias it back to its previous name.
sobject: Flags.string({
char: 's',
required: true,
summary: messages.getMessage('flags.sobject'),
// the flag's previous name could be simplified to a single word
aliases: ['sobjecttype'],
// display a warning when someone uses the previous flag name
deprecateAliases: true,
}),
// sf uses the more readable hyphenated name
'use-tooling-api': Flags.boolean({
char: 't',
summary: messages.getMessage('flags.useToolingApi'),
aliases: ['usetoolingapi'],
deprecateAliases: true,
}),
// this flag was renamed and its short character was changed. both -f and -p work now, but -p provides a warning when used
'file': Flags.boolean({
char: 'f',
summary: messages.getMessage('flags.useToolingApi'),
aliases: ['pathtofile', 'p'],
deprecateAliases: true,
}),
We provide some helpers for compatibility within @salesforce/sf-plugins-core
. We've marked them as deprecated
because you should use these helpers only for migrating an existing sfdx
plugin, not for new plugin development.
public static flags = {
// allow but warn on --targetusername and -u
'target-org': requiredOrgFlagWithDeprecations,
// allow but warn on --apiversion
'api-version': orgApiVersionFlagWithDeprecations,
// loglevel is a no-op, but this flag is added to avoid breaking scripts and warn users who are using it
loglevel,
};
As described here, the eslint plugin handles most of the work of getting you to sfCommand
. But there are some differences you should be aware of.
Some properties like this.logger
don't automatically exist. You can create those (import Logger from sfdx-core)
other properties like requiresUsername
don't do anything (and are removed by the linter rules). You'll need to add a flag for the org.
SfCommand
has private ux
property, meaning you can't pass it to helper functions. Instead of
// SfdxCommand
myHelper(this.ux);
construct a new ux
instance and pass it to your help
import { ux, Flags, SfCommand } from '@salesforce/sf-plugins-core'
...
// myHelper can call any `ux` method and terminal output is suppressed if json enabled
myHelper(new ux({jsonEnabled: this.jsonEnabled()}))
SfdxCommand's requires|supports(devhub)username
properties no only created flag for the username but also an apiversion flag.
If your command needs to support an apiversion (or just keep it in place for compatibility), do the following
public static flags = {
'target-org': requiredOrgFlagWithDeprecations,
'api-version': orgApiVersionFlagWithDeprecations,
}
// when you get a Connection, pass the api-version flag to it
// that way, if the user specified an api version, the Connection is set
const conn = flags['target-org'].getConnection([flags.api-version]);
Once you yarn remove @salesforce/command
you may realize you were also using its test
library, based on fancy-test
.
sfdx-core
v3 includes better tools for mocking auths/orgs/projects/users/connections. See its docs
This example uses TestSetup to mock an org/connection and calls a command's run
method
It's helpful to leave existing tests to verify that you've properly aliases all commands and flags to avoid breaking changes.
© 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