The goal of v3 is to support the multiple Salesforce clouds and the unification strategy.
There are several breaking changes from v2 to v3. This doc outlines what those the differences are between v2 and v3, why we did them, and how to transition to v3.
All files were stored in the directory .sfdx
at both the user’s home directory and in the project directory. The folder was named .sfdx
because of the Salesforce CLI executable. The executable is changing to sf
for several reasons so we are updating the directory to be called .sf
.
We don’t want to change the name of the folder just for the sake of changing it. However, there are several benefits.
- A clear distinction of ownership between the two CLIs, or any consumer using v3 of the library.
- The name “Salesforce DX” and “sfdx“ carries baggage. There has been an attempt to remove references to it, especially for unification.
- No conflicts with existing files.
Of course, there are some negatives to creating a new folder as well. The main one being this will cause more work for other plugins and tools that also store files in this directory. When they start consuming v3, they will have to explicitly point to .sfdx
or work on migrating their existing files to the new directory.
- Removed export for
AuthInfoConfig
- Removed export for
Aliases
- Removed
AuthInfo.listAllAuthFiles
- Added
StateAggregator
We wanted to introduce a single entry point for all configuration files. This makes it easier for consumers to access information without having to know the nuances of each underlying class.
v2:
await AuthInfo.listAllAuthFiles();
const aliases = await Aliases.create(Aliases.getDefaultOptions());
aliases.set('my-alias', 'my-username');
aliases.get('my-alias');
await aliases.write();
v3:
await AuthInfo.listAllAuthorizations();
OR
const stateAggregator = await StateAggregator.getInstance();
await stateAggregator.orgs.readAll();
stateAggregator.aliases.getAll();
stateAggregator.aliases.set('my-alias', 'my-username');
stateAggregator.aliases.resolveAlias('my-alias'); // returns 'my-alias'
stateAggregator.aliases.resolveAlias('my-username'); // returns 'my-alias'
stateAggregator.aliases.resolveUsername('my-alias'); // returns 'my-username'
stateAggregator.aliases.resolveUsername('my-username'); // returns 'my-username'
await stateAggregator.aliases.write();
- Moved
.sfdx/sfdx-config.json
to.sf/config.json
- Removed
Config.<config key name>
- Deprecated the following:
Deprecated Config Var | New Config Var |
---|---|
SfdxPropertyKeys.API_VERSION |
OrgConfigProperties.ORG_API_VERSION |
SfdxPropertyKeys.CUSTOM_ORG_METADATA_TEMPLATES |
OrgConfigProperties.ORG_CUSTOM_METADATA_TEMPLATES |
SfdxPropertyKeys.DEFAULT_DEV_HUB_USERNAME |
OrgConfigProperties.TARGET_DEV_HUB |
SfdxPropertyKeys.DEFAULT_USERNAME |
OrgConfigProperties.TARGET_ORG |
SfdxPropertyKeys.DISABLE_TELEMETRY |
SfConfigProperties.DISABLE_TELEMETRY |
SfdxPropertyKeys.INSTANCE_URL |
OrgConfigProperties.ORG_INSTANCE_URL |
SfdxPropertyKeys.ISV_DEBUGGER_SID |
OrgConfigProperties.ORG_ISV_DEBUGGER_SID |
SfdxPropertyKeys.ISV_DEBUGGER_URL |
OrgConfigProperties.ORG_ISV_DEBUGGER_URL |
SfdxPropertyKeys.MAX_QUERY_LIMIT |
OrgConfigProperties.ORG_MAX_QUERY_LIMIT |
SfdxPropertyKeys.REST_DEPLOY |
REMOVED |
ConfigAggregator
will not work with any of the deprecated keys. We strongly suggest that you move to the new keys as part of the migration process.- If you absolutely cannot migrate to the new config keys, then you can use
SfdxConfigAggregator
in the meantime.
Config keys need to be a lot more specific to support multiple clouds. For example, apiVersion
is ambiguous and does not specify which api it's referring to. We still have to support these keys moving forward so we don't break existing workflows. All old keys were moved to a SfdxPropertyKeys
to make it very clear that they are legacy key names.
v2
Config.update(true, Config.API_VERSION, '49.0');
const configAggregator = await ConfigAggregator.create();
configAggregator.getPropertyValue(Config.API_VERSION);
v3
Config.update(true, OrgConfigProperties.ORG_API_VERSION, '49.0');
const configAggregator = await ConfigAggregator.create();
configAggregator.getPropertyValue(OrgConfigProperties.ORG_API_VERSION);
OR
const configAggregator = await SfdxConfigAggregator.create();
configAggregator.getPropertyValue(SfdxPropertyKeys.API_VERSION);
- New: Encrypting capabilities on ConfigStore and ConfigFile.
- New: Improved typings on ConfigStore to understand the config data.
- Removed
AuthInfo.clearCache
. - Removed encrypt param on
AuthInfo.update
. - Changed: ConfigStore now extends
AsyncOptionalCreatable
.
Originally encryption happened within AuthInfo. Ideally the mechanism that reads and writes secure values also encrypt them. This helps ensure that decrypted values don't get saved to disk. If you use ConfigFile, you can now set encryption keys rather than dealing with Crypto directly.
Because of that change it no longer made sense to have a cache on AuthInfo. Instead, getting auth info fields should always be getting it from the true source, which is the config file. In this case, StateAggregator.
Most people won't have to worry about these changes unless doing some special test mocking or using the removed AuthInfo.clearCache
or the encrypt param on AuthInfo.update
. Everything updated will always be encrypted to prevent any accidents with storying decrypted data.
v2:
type MyConfig = {
option1: string;
option2: boolean;
};
class MyConfigFile extends ConfigFile<ConfigFile.Options> {
constructor(options: ConfigFile.Options) {
super(options);
// Some logic
}
public get(key: string): ConfigValue {
let value = super.get(key);
// Do custom crypto
return value;
}
public set(key: string, value: ConfigValue) {
// Do custom crypto
super.set(key, value);
}
}
const myConfig = await MyConfigFile.create({});
myConfig.getContents(); // ConfigContents
myConfig.get('option1'); // ConfigValue - forcing type narrowing.
myConfig.get('option2'); // ConfigValue - forcing type narrowing.
v3:
type MyConfig = {
option1: string;
option2: boolean;
};
class MyConfigFile extends ConfigFile<ConfigFile.Options, MyConfig> {
protected static encryptedKeys = ['option1'];
constructor(options?: ConfigFile.Options) {
super(options);
// Some logic
}
}
const myConfig = await MyConfigFile.create({});
myConfig.getContents(); // MyConfig
myConfig.get('option1'); // string - automatically decrypted
myConfig.get('option2'); // boolean
- Renamed:
SfdxError
toSfError
- Removed:
SfdxErrorConfig
- Removed:
SfdxError.create
v2:
throw SfdxError.create('<package>', '<bundle>', 'myKey', ['tokens'], 'actionKey', ['actionToken']);
v3:
Messages.importMessagesDirectory(__dirname);
const messages = Messages.load('<package>', '<bundle>', ['myKey', 'actionKey']);
// ...
const errName = 'myKey';
const errMessage = messages.getMessage(errName, ['tokens']);
const errActions = messages.getMessages('actionsKey', ['actionTokens']);
throw new SfError(errMessage, errName, errActions);
With the changes to messages we want developers to be explicit about the keys they are using. We also want to encourage messages are loaded at the top of the file. The SfdxError.create
method bypasses both of those objectives. Because of this we just completely removed it. Although it adds a little bit of convenience, it also simplifies the usage. For a similar convenience, see Messages.createError
.
While standardizing on our message files, we realized that there were a lot of inconsistencies with our thrown error names. To stay consistent with ecma we changed all error names to be CamelCase and end in Error
. Below is a table of all the changed error names. If you are catching one of these errors and checking the name, you will need to update that reference.
v2 Error Name | V3 Error Name |
---|---|
NoOrgFound | DefaultUsernameNotSetError |
SfdxSchemaValidationError | SchemaValidationError |
waitParamValidValueError | WaitParamValidValueError |
handshakeApiVersionError | HandshakeApiVersionError |
genericHandshakeTimeoutMessage | GenericHandshakeTimeoutError |
genericTimeoutMessage | GenericTimeoutError |
assignCommandPermissionSetNotFoundError | AssignCommandPermissionSetNotFoundError |
assignCommandPermissionSetNotFoundForNSError | AssignCommandPermissionSetNotFoundForNSError |
pollingTimeout | PollingTimeoutError |
invalidConfigValue | InvalidConfigValueError |
UnknownConfigKey | UnknownConfigKeyError |
InvalidProjectWorkspace | InvalidProjectWorkspaceError |
InvalidApiVersion | InvalidApiVersionError |
NotADevHub | NotADevHubError |
InvalidFormat | InvalidFormatError |
JWTAuthError | JwtAuthError |
NamedOrgNotFound | NamedOrgNotFoundError |
IncorrectAPIVersion | IncorrectAPIVersionError |
Domain Not Found | DomainNotFoundError |
NoInstanceUrl | NoInstanceUrlError |
RedundantRootLogger | RedundantRootLoggerError |
UnrecognizedLoggerLevelName | UnrecognizedLoggerLevelNameError |
NoResults | NoResultsError |
MissingAuthInfo | MissingAuthInfoError |
InvalidWrite | InvalidWriteError |
MissingGroupNameError | MissingGroupNameError |
ValidationSchemaFieldErrors | ValidationSchemaFieldError |
ValidationSchemaUnknown | ValidationSchemaUnknownError |
- Renamed
SfdxProject
toSfProject
- Renamed
SfdxProjectJson
toSfProjectJson
As we move to supporting the new sf
executable, we want the naming to be consistent.