Skip to content

Migrate Plugins Built for sfdx

Juliet Shackell edited this page Jan 30, 2023 · 16 revisions

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 sf plugins.

Prerequisites

If you haven't already, update oclif and the @salesforce/core library to their current versions.

  1. Update to oclif/core by following this document.
  2. Update to sfdx-core v3 by following this document. We discuss converting your messages files later in this document.

Plugin Migration Tooling

Eslint config

Salesforce provides a set of eslint rules for CLI plugins.

If you are migrating an sfdx plugin, there's a series of additional rules that do most of the work for you. Follow their setup instructions.

The rules will automatically change some code. For some rules, you'll see suggestions where you can choose which fix to apply.

Dynamic help messages

v3 Messages support dynamic references to command and CLI names. For example, if your plugin supports both sfdx and sf, instead of hardcoding the command and CLI name, you can use a placeholder for the bin (CLI) and command name

# examples

 - do something
 
  <%= config.bin %> <%= command.id %> -i 7500x000005BdFzXXX

This way, the help examples

  1. reference the CLI the user is using
  2. don't have to be rewritten when you change your command structure/name

Migrating messages to markdown

v3 messages work with your existing json message files, but we prefer markdown files.

If you haven't already, install plugin-dev: sf plugins install dev

Then run sf dev convert messages -f [some message file]. That converts json messages to markdown (delete the json ones when you're done)

You can also run sf dev audit messages to find missing or unused messages.

Backward compatibility

We encourage you to follow sf's styles in your names and flags. 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;

If you rename a flag, there are similar alias behaviors to support 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,
    }),

Additionally, there are some helpers for compatibility within sf-plugins-core. They're marked as deprecated so you don't use them on "new work".

  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

The eslint plugin should handle getting you to sfCommand. There are some differences to be aware of

Properties that don't exist

some properties like this.logger don't exist automatically. 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.

this.ux

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()}))

no automatic apiversion

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]);

Test Migration

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

Testing aliases

It's helpful to leave existing tests to verify that you've properly aliases all commands and flags to avoid breaking changes.

Clone this wiki locally