As of April 2023, the sfdx version of Salesforce CLI is deprecated and Salesforce no longer supports it. The latest and supported Salesforce CLI version is sf. However, many customers created their own plugins using the sfdx framework and libraries. If you're such a customer, read on.
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.
Prerequisites
Update @oclif and the @salesforce/core library to the latest versions.
- Update to v3 (or latest) of
@oclif/coreas described by this document. - Update to v7 (or latest) of
@salesforce/coreas described this document. The root of the repository contains otherMIGRATING_VX_VY.MDdocuments that detail every major change and upgrade tips.
Tools and Tips
These sections provide useful information for migrating your plugins from sfdx to sf.
ESLint Configuration
Salesforce provides a set of eslint rules for Salesforce CLI plugins.
While 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 set up instructions for more details.
Some of these eslint rules automatically change some of your code while other rules provide suggestions where you can select which fix to apply.
Migrate Messages to Markdown
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.
-
From the top-level directory of your plugin, run the
dev convert messagescommand 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.jsonIn this case, the command generates the Markdown file
messages/mycommand.md. -
Edit the generated Markdown file to make sure your messages look good. See the next section for information about reducing maintenance and Write Useful Messages for general information about writing messages.
-
Run
sf dev audit messagesto find missing or unused messages, then update your code to add or remove references to the messages as needed. -
Delete the old JSON files after you're finished.
Dynamic Help and Error Messages
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 itself.
If your plugin supports both sfdx and sf, use these placeholders in message files rather than hard coding the command and CLI executable name. As a result, you aren't required to rewrite the messages after 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
Backward Compatibility
We encourage you to follow sf's styles in your command and flag names. See Design Guidelines for details. If you must 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 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,
};
Key Differences Between "sfdxCommand" and "sfCommand"
As described in this section, the eslint plugin handles most of the work of getting you to sfCommand. But there are some differences.
Properties That Don't Exist
Some properties like this.logger don't automatically exist. You can create the property by importing Logger from sfdx-core. Other properties like requiresUsername don't do anything and are removed by the linter rules. You must add a flag for the org.
this.ux
SfCommand has a private ux property, which means you can't pass it to helper functions. Instead of this code:
// SfdxCommand
myHelper(this.ux);
Use this code to 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()}))
No Automatic apiVersion
SfdxCommand's requires|supports(devhub)username properties created a flag for both the username an apiversion flag. If your command must support an apiversion, or you want to keep it in place for compatibility, do this:
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]);
Test the Migration
After you run yarn remove @salesforce/command you sometimes see that 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 and a connection and calls a command's run method.
Testing Aliases
To avoid breaking changes, we recommend that you leave existing tests which verify that you've correctly aliased all commands and flags.