We’ve been working on the Texeï SFDX Plugin migration to sf (V2), and just published a first version of it. If you’re unfamiliar with sf (V2), you can have a look at these two great blog post from the Salesforce Developers’ Blog:
Migrating, why ?
With the new Salesforce CLI, sf (V2) being GA, you may have noticed that some Salesforce commands started firing warnings about recent changes. A basic example would be that some flags, like --targetusername
, will be replaced by --target-org
. Behind the scenes, it’s also the commands architecture that changed.
So why do we care ? Well let’s be honest, we could have stayed with the exact same architecture, and everything would have worked perfectly. But once all commands have been migrated to use the new --target-org
flag, Texeï plugin would still use the --targetusername
flag, making it inconsistent with Salesforce commands.
Also, new commands would of course use the new architecture, meaning that whether you work on a new command or an old one, code would have been different. Not very convenient to onboard new developers, or to get pull requests from the community.
Lastly, some dependencies will bring new features but also remove some support, for instance SfdxError
is not exported anymore in the latest version of the @salesforce/core
package.
So we just rolled up our sleeves and started working on it.
Migrating, how ?
Knowing that all plugin authors would want to migrate their plugin, Salesforce CLI team created a wiki explaining all the steps needed to do it:
https://github.com/salesforcecli/cli/wiki/Migrate-Plugins-Built-for-sfdx
This remains the official documentation and best place to start, but we’ll give you our own feedback.
First thing to know is that migrating is pretty easy. What makes it difficult is that Texeï plugin has more than 20 commands, which means repeating all the migration steps for all these commands. Hopefully, Salesforce provides a set of ESLint additional rules that makes it easier (see note just below).
We’ll go through several examples about the things that we had to change in almost all commands, with a before and after code sample, but also note that you can go to the recap section at the end of this article to see a before/after helper table, or look at the same command written both the old way and the new way.
Note that you can use a set of ESLint additional rules that do most of the work for you. Thanks Shane McLaughlin for pointing this out.
Just setup these rules as explained here, and just run
yarn lint -- --fix
.
Update dependencies
The first step was to update the dependencies in package.json
. You can of course update them manually, but as some of them may be removed or new, we created a new plugin (using sf plugins install @salesforce/plugin-dev
and sf dev generate plugin
) just to have a look at the generated files and dependencies. Using a whole new setup will also bring you better linting than just migrating your command files.
Message files
Messages files are now different, basically they were JSON based previously, with key/value pairs of string, whereas now they are based on markdown. This allows you to bring formatting and variables to your messages.
Before:
After (raw content here):
These are pretty easy to migrate as there is a specific command for that:
sf dev convert messages --file-name messages/my-command.json
Beside that in your code itself, the expected property is now named summary, replacing the description flag:
Before:
After:
Code
Then you’ll have to update each of your commands. The main thing that is changing is that all classes were extending SfdxCommand
whereas now they do extend SfCommand
. Which comes with several changes regarding the way you write your commands.
Imports
The most important one will be to move the import from SfdxCommand
to SfCommand
:
will be now:
Some other changes will be to move SfdxError
to SfError
or SfdxProjectJson
to SfProjectJson
.
You’ll get warned by linting about other incorrect imports you may have.
Flags
Flags are imported almost the same way.
Before:
After:
In addition, flags should be parsed before used:
const { flags } = await this.parse(CommandName);
Note also that Flags.number
doesn’t exist anymore but can be replaced by Flags.integer.
requiresUsername, supportsDevhubUsername and requiresProject
Exported type
You’re now required to define the exported type. Whereas before you could use a random and undefined object:
Now you’ll define a type first, and return it from your command (here NewCommandResult
):
Connection
One you may use all the time, getting a connection will have to be rewritten from:
to:
Spinner and log
Another change needed even though pretty simple, moving:
to:
Some other change may be needed, like moving this.ux.log()
to this.log()
.
Breaking changes
One of the changes we had to handle is that the shortcut for --target-org
is now -o
, whereas it was -u
previously with --targetusername
. This conflicted with some existing commands using -o for another flag (like –objects). We just moved the flag shortcut to a new one to avoid the conflict, and documented it as a breaking change in our documentation.
ES Lint and TypeScript errors
When moving to the new linting, we encountered a lot of new warnings and errors. At first, we started to fix everything, but noticed that this would take A LOT of time. This was a big issue because this was slowing down our migration, and the longer we waited, the more conflicts we would have later will Pull Requests we receive. Not the best recommendation here, but we disabled the most painful warnings per file to be able to migrate quicker.
This doesn’t seem really clean, but the code is still the same as before, so what’s not perfect now wasn’t already. Basically not better code, but as good as before (and working). This is also easier to split the remaining cleaning work per file and developer.
Lastly, as these warnings are disabled per file, this will let us write new commands in a clean way.
Recap
Here is a basic recap of before/after code that we used very often:
Before | After |
import { flags, SfdxCommand } from ‘@salesforce/command’; | import { Flags, SfCommand } from ‘@salesforce/sf-plugins-core’; |
const messages = Messages.loadMessages(« texei-sfdx-plugin », « my-command »); | const messages = Messages.loadMessages(‘texei-sfdx-plugin’, ‘my.command’); |
SfdxError | SfError |
SfdxProjectJson | SfProjectJson |
public static description = messages.getMessage(‘commandDescription’); | public static readonly summary = messages.getMessage(‘summary’); |
wait: flags.number({ char: ‘w’, description: messages.getMessage(‘waitFlagDescription’), required: false }) | wait: Flags.integer({ char: ‘w’, summary: messages.getMessage(‘flags.wait.summary’), required: false }) |
this.ux.log() | this.log() |
this.ux.warn() | this.warn() |
this.startSpinner(‘Retrieving Profiles’, null, { stdout: true }); | this.spinner.start(‘Retrieving Profiles’, undefined, { stdout: true }); |
this.ux.stopSpinner(‘Done.’); | this.spinner.stop(‘Done.’); |
protected static requiresUsername = true; | requiredOrgFlagWithDeprecations |
protected static requiresDevhubUsername = false; | requiredHubFlagWithDeprecations |
protected static readonly requiresProject = true; | public static readonly requiresProject = true; |
const conn = await this.org.getConnection(); | const conn = flags[‘target-org’].getConnection(flags[‘api-version’]); |
Finally, you can have a look at the same command, both written the old way and the new way:
Old way:
New way:
Your turn now to move to sf (V2) ! 👍