@salesforce/core
Version:
Core libraries to interact with SFDX projects, orgs, and APIs.
578 lines • 23.5 kB
JavaScript
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SfdxConfig = exports.Config = exports.SfProperty = exports.SFDX_ALLOWED_PROPERTIES = exports.SfdxPropertyKeys = exports.SF_ALLOWED_PROPERTIES = exports.SfConfigProperties = void 0;
const path_1 = require("path");
const fs = require("fs");
const kit_1 = require("@salesforce/kit");
const ts_types_1 = require("@salesforce/ts-types");
const global_1 = require("../global");
const logger_1 = require("../logger");
const messages_1 = require("../messages");
const sfdc_1 = require("../util/sfdc");
const sfdcUrl_1 = require("../util/sfdcUrl");
const orgConfigProperties_1 = require("../org/orgConfigProperties");
const configFile_1 = require("./configFile");
messages_1.Messages.importMessagesDirectory(__dirname);
const messages = messages_1.Messages.load('@salesforce/core', 'config', [
'deprecatedConfigKey',
'invalidApiVersion',
'invalidBooleanConfigValue',
'invalidConfigValue',
'invalidInstanceUrl',
'invalidIsvDebuggerSid',
'invalidIsvDebuggerUrl',
'invalidNumberConfigValue',
'invalidWrite',
'unknownConfigKey',
'defaultUsername',
'defaultDevHubUsername',
'isvDebuggerSid',
'isvDebuggerUrl',
'apiVersion',
'disableTelemetry',
'maxQueryLimit',
'restDeploy',
'instanceUrl',
'disable-telemetry',
'customOrgMetadataTemplates',
]);
const SFDX_CONFIG_FILE_NAME = 'sfdx-config.json';
const CONFIG_FILE_NAME = 'config.json';
var SfConfigProperties;
(function (SfConfigProperties) {
/**
* Disables telemetry reporting
*/
SfConfigProperties["DISABLE_TELEMETRY"] = "disable-telemetry";
})(SfConfigProperties = exports.SfConfigProperties || (exports.SfConfigProperties = {}));
exports.SF_ALLOWED_PROPERTIES = [
{
key: SfConfigProperties.DISABLE_TELEMETRY,
description: messages.getMessage(SfConfigProperties.DISABLE_TELEMETRY),
input: {
validator: (value) => value == null || ['true', 'false'].includes(value.toString()),
failedMessage: messages.getMessage('invalidBooleanConfigValue'),
},
},
];
var SfdxPropertyKeys;
(function (SfdxPropertyKeys) {
/**
* Username associated with the default dev hub org.
*
* @deprecated Replaced by OrgConfigProperties.TARGET_DEV_HUB in v3 {@link https://github.com/forcedotcom/sfdx-core/blob/v3/MIGRATING_V2-V3.md#config}
* will remain in v3 for the foreseeable future so that `sfdx-core` can map between `sf` and `sfdx` config values
*/
SfdxPropertyKeys["DEFAULT_DEV_HUB_USERNAME"] = "defaultdevhubusername";
/**
* Username associate with the default org.
*
* @deprecated Replaced by OrgConfigProperties.TARGET_ORG in v3 {@link https://github.com/forcedotcom/sfdx-core/blob/v3/MIGRATING_V2-V3.md#config}
* will remain in v3 for the foreseeable future so that `sfdx-core` can map between `sf` and `sfdx` config values
*/
SfdxPropertyKeys["DEFAULT_USERNAME"] = "defaultusername";
/**
* The sid for the debugger configuration.
*
* @deprecated Replaced by OrgConfigProperties.ORG_ISV_DEBUGGER_SID in v3 {@link https://github.com/forcedotcom/sfdx-core/blob/v3/MIGRATING_V2-V3.md#config}
*/
SfdxPropertyKeys["ISV_DEBUGGER_SID"] = "isvDebuggerSid";
/**
* The url for the debugger configuration.
*
* @deprecated Replaced by OrgConfigProperties.ORG_ISV_DEBUGGER_URL in v3 {@link https://github.com/forcedotcom/sfdx-core/blob/v3/MIGRATING_V2-V3.md#config}
*/
SfdxPropertyKeys["ISV_DEBUGGER_URL"] = "isvDebuggerUrl";
/**
* The api version
*
* @deprecated Replaced by OrgConfigProperties.ORG_API_VERSION in v3 {@link https://github.com/forcedotcom/sfdx-core/blob/v3/MIGRATING_V2-V3.md#config}
*/
SfdxPropertyKeys["API_VERSION"] = "apiVersion";
/**
* Disables telemetry reporting
*
* @deprecated Replaced by SfPropertyKeys.DISABLE_TELEMETRY in v3 {@link https://github.com/forcedotcom/sfdx-core/blob/v3/MIGRATING_V2-V3.md#config}
*/
SfdxPropertyKeys["DISABLE_TELEMETRY"] = "disableTelemetry";
/**
* Custom templates repo or local location.
*
* @deprecated Replaced by OrgConfigProperties.ORG_CUSTOM_METADATA_TEMPLATES in v3 {@link https://github.com/forcedotcom/sfdx-core/blob/v3/MIGRATING_V2-V3.md#config}
*/
SfdxPropertyKeys["CUSTOM_ORG_METADATA_TEMPLATES"] = "customOrgMetadataTemplates";
/**
* allows users to override the 10,000 result query limit
*
* @deprecated Replaced by OrgConfigProperties.ORG_MAX_QUERY_LIMIT in v3 {@link https://github.com/forcedotcom/sfdx-core/blob/v3/MIGRATING_V2-V3.md#config}
*/
SfdxPropertyKeys["MAX_QUERY_LIMIT"] = "maxQueryLimit";
/**
* @deprecated
*/
SfdxPropertyKeys["REST_DEPLOY"] = "restDeploy";
/**
* @deprecated Replaced by OrgConfigProperties.ORG_INSTANCE_URL in v3 {@link https://github.com/forcedotcom/sfdx-core/blob/v3/MIGRATING_V2-V3.md#config}
*/
SfdxPropertyKeys["INSTANCE_URL"] = "instanceUrl";
})(SfdxPropertyKeys = exports.SfdxPropertyKeys || (exports.SfdxPropertyKeys = {}));
exports.SFDX_ALLOWED_PROPERTIES = [
{
key: SfdxPropertyKeys.INSTANCE_URL,
description: messages.getMessage(SfdxPropertyKeys.INSTANCE_URL),
newKey: orgConfigProperties_1.OrgConfigProperties.ORG_INSTANCE_URL,
deprecated: true,
input: {
// If a value is provided validate it otherwise no value is unset.
validator: (value) => {
if (value == null)
return true;
// validate if the value is a string and is a valid url and is either a salesforce domain
// or an internal url.
return ((0, ts_types_1.isString)(value) &&
sfdcUrl_1.SfdcUrl.isValidUrl(value) &&
(new sfdcUrl_1.SfdcUrl(value).isSalesforceDomain() || new sfdcUrl_1.SfdcUrl(value).isInternalUrl()));
},
failedMessage: messages.getMessage('invalidInstanceUrl'),
},
},
{
key: SfdxPropertyKeys.API_VERSION,
newKey: orgConfigProperties_1.OrgConfigProperties.ORG_API_VERSION,
deprecated: true,
description: messages.getMessage(SfdxPropertyKeys.API_VERSION),
hidden: true,
input: {
// If a value is provided validate it otherwise no value is unset.
validator: (value) => value == null || ((0, ts_types_1.isString)(value) && sfdc_1.sfdc.validateApiVersion(value)),
failedMessage: messages.getMessage('invalidApiVersion'),
},
},
{
// will remain in v3 for the foreseeable future so that `sfdx-core` can map between `sf` and `sfdx` config values
key: SfdxPropertyKeys.DEFAULT_DEV_HUB_USERNAME,
newKey: orgConfigProperties_1.OrgConfigProperties.TARGET_DEV_HUB,
deprecated: true,
description: messages.getMessage('defaultDevHubUsername'),
},
{
// will remain in v3 for the foreseeable future so that `sfdx-core` can map between `sf` and `sfdx` config values
key: SfdxPropertyKeys.DEFAULT_USERNAME,
newKey: orgConfigProperties_1.OrgConfigProperties.TARGET_ORG,
deprecated: true,
description: messages.getMessage('defaultUsername'),
},
{
key: SfdxPropertyKeys.ISV_DEBUGGER_SID,
newKey: orgConfigProperties_1.OrgConfigProperties.ORG_ISV_DEBUGGER_SID,
deprecated: true,
description: messages.getMessage(SfdxPropertyKeys.ISV_DEBUGGER_SID),
encrypted: true,
input: {
// If a value is provided validate it otherwise no value is unset.
validator: (value) => value == null || (0, ts_types_1.isString)(value),
failedMessage: messages.getMessage('invalidIsvDebuggerSid'),
},
},
{
key: SfdxPropertyKeys.ISV_DEBUGGER_URL,
newKey: orgConfigProperties_1.OrgConfigProperties.ORG_ISV_DEBUGGER_URL,
deprecated: true,
description: messages.getMessage(SfdxPropertyKeys.ISV_DEBUGGER_URL),
input: {
// If a value is provided validate it otherwise no value is unset.
validator: (value) => value == null || (0, ts_types_1.isString)(value),
failedMessage: messages.getMessage('invalidIsvDebuggerUrl'),
},
},
{
key: SfdxPropertyKeys.DISABLE_TELEMETRY,
newKey: SfConfigProperties.DISABLE_TELEMETRY,
deprecated: true,
description: messages.getMessage(SfdxPropertyKeys.DISABLE_TELEMETRY),
input: {
validator: (value) => value == null || ['true', 'false'].includes(value.toString()),
failedMessage: messages.getMessage('invalidBooleanConfigValue'),
},
},
{
key: SfdxPropertyKeys.CUSTOM_ORG_METADATA_TEMPLATES,
newKey: orgConfigProperties_1.OrgConfigProperties.ORG_CUSTOM_METADATA_TEMPLATES,
deprecated: true,
description: messages.getMessage(SfdxPropertyKeys.CUSTOM_ORG_METADATA_TEMPLATES),
},
{
key: SfdxPropertyKeys.REST_DEPLOY,
description: messages.getMessage(SfdxPropertyKeys.REST_DEPLOY),
hidden: true,
newKey: 'org-metadata-rest-deploy',
deprecated: true,
input: {
validator: (value) => value != null && ['true', 'false'].includes(value.toString()),
failedMessage: messages.getMessage('invalidBooleanConfigValue'),
},
},
{
key: SfdxPropertyKeys.MAX_QUERY_LIMIT,
description: messages.getMessage(SfdxPropertyKeys.MAX_QUERY_LIMIT),
hidden: true,
newKey: orgConfigProperties_1.OrgConfigProperties.ORG_MAX_QUERY_LIMIT,
deprecated: true,
input: {
// the bit shift will remove the negative bit, and any decimal numbers
// then the parseFloat will handle converting it to a number from a string
validator: (value) => value >>> 0 === parseFloat(value) && value > 0,
failedMessage: messages.getMessage('invalidNumberConfigValue'),
},
},
];
// Generic global config properties. Specific properties can be loaded like orgConfigProperties.ts.
exports.SfProperty = {};
/**
* The files where sfdx config values are stored for projects and the global space.
*
* *Note:* It is not recommended to instantiate this object directly when resolving
* config values. Instead use {@link ConfigAggregator}
*
* ```
* const localConfig = await Config.create();
* localConfig.set('target-org', 'username@company.org');
* await localConfig.write();
* ```
* https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_cli_config_values.htm
*/
class Config extends configFile_1.ConfigFile {
constructor(options) {
super(Object.assign({
isGlobal: false,
}, options ?? {}, {
// Don't let consumers of config override this. If they really really want to,
// they can extend this class.
isState: true,
filename: Config.getFileName(),
stateFolder: global_1.Global.SF_STATE_FOLDER,
}));
// Resolve the config path on creation.
this.getPath();
this.sfdxConfig = new SfdxConfig(this.options, this);
}
/**
* Returns the default file name for a config file.
*
* **See** {@link CONFIG_FILE_NAME}
*/
static getFileName() {
return CONFIG_FILE_NAME;
}
/**
* Returns an array of objects representing the allowed config properties.
*/
static getAllowedProperties() {
return Config.allowedProperties;
}
/**
* Add an array of allowed config properties.
*
* @param metas Array of objects to set as the allowed config properties.
*/
static addAllowedProperties(metas) {
const currentMetaKeys = Object.keys(Config.propertyConfigMap());
// If logger is needed elsewhere in this file, do not move this outside of the Config class.
// It was causing issues with Bunyan log rotation. See https://github.com/forcedotcom/sfdx-core/pull/562
const logger = logger_1.Logger.childFromRoot('core:config');
metas.forEach((meta) => {
if (currentMetaKeys.includes(meta.key)) {
logger.info(`Key ${meta.key} already exists in allowedProperties, skipping.`);
return;
}
Config.allowedProperties.push(meta);
});
}
/**
* The value of a supported config property.
*
* @param isGlobal True for a global config. False for a local config.
* @param propertyName The name of the property to set.
* @param value The property value.
*/
static async update(isGlobal, propertyName, value) {
const config = await Config.create({ isGlobal });
const content = await config.read();
if (value == null) {
delete content[propertyName];
}
else {
(0, kit_1.set)(content, propertyName, value);
}
return config.write(content);
}
/**
* Clear all the configured properties both local and global.
*/
static async clear() {
const globalConfig = await Config.create({ isGlobal: true });
globalConfig.clear();
await globalConfig.write();
const localConfig = await Config.create();
localConfig.clear();
await localConfig.write();
}
static getPropertyConfigMeta(propertyName) {
const prop = Config.propertyConfigMap()[propertyName];
if (prop?.deprecated && prop?.newKey) {
return Config.propertyConfigMap()[prop.newKey];
}
return prop;
}
static propertyConfigMap() {
return (0, kit_1.keyBy)(Config.allowedProperties, 'key');
}
/**
* Read, assign, and return the config contents.
*/
async read(force = true) {
try {
const config = await super.read(false, force);
// Merge .sfdx/sfdx-config.json and .sf/config.json
const merged = this.sfdxConfig.merge(config);
this.setContents(merged);
await this.cryptProperties(false);
return this.getContents();
}
finally {
await this.clearCrypto();
}
}
readSync(force = true) {
const config = super.readSync(false, force);
// Merge .sfdx/sfdx-config.json and .sf/config.json
const merged = this.sfdxConfig.merge(config);
this.setContents(merged);
return this.getContents();
}
/**
* Writes Config properties taking into account encrypted properties.
*
* @param newContents The new Config value to persist.
*/
async write(newContents) {
if (newContents != null) {
this.setContents(newContents);
}
await this.cryptProperties(true);
await super.write();
if (global_1.Global.SFDX_INTEROPERABILITY)
await this.sfdxConfig.write();
await this.cryptProperties(false);
return this.getContents();
}
/**
* DO NOT CALL - The config file needs to encrypt values which can only be done asynchronously.
* Call {@link SfdxConfig.write} instead.
*
* **Throws** *{@link SfError}{ name: 'InvalidWriteError' }* Always.
*
* @param newContents Contents to write
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
writeSync(newContents) {
throw messages.createError('invalidWrite');
}
/**
* Sets a value for a property.
*
* **Throws** *{@link SfError}{ name: 'UnknownConfigKeyError' }* An attempt to get a property that's not supported.
* **Throws** *{@link SfError}{ name: 'InvalidConfigValueError' }* If the input validator fails.
*
* @param key The property to set.
* @param value The value of the property.
*/
set(key, value) {
const property = Config.allowedProperties.find((allowedProp) => allowedProp.key === key);
if (!property) {
throw messages.createError('unknownConfigKey', [key]);
}
if (property.deprecated && property.newKey) {
throw messages.createError('deprecatedConfigKey', [key, property.newKey]);
}
if (property.input) {
if (property.input && property.input.validator(value)) {
super.set(property.key, value);
}
else {
let valueError = value?.toString() || '';
if (property.input.failedMessage) {
valueError = (0, ts_types_1.isString)(property.input.failedMessage)
? property.input.failedMessage
: property.input.failedMessage(value);
}
throw messages.createError('invalidConfigValue', [valueError]);
}
}
else {
super.set(property.key, value);
}
return this.getContents();
}
/**
* Unsets a value for a property.
*
* **Throws** *{@link SfError}{ name: 'UnknownConfigKeyError' }* If the input validator fails.
*
* @param key The property to unset.
*/
unset(key) {
const property = Config.allowedProperties.find((allowedProp) => allowedProp.key === key);
if (!property) {
throw messages.createError('unknownConfigKey', [key]);
}
if (property.deprecated && property.newKey) {
throw messages.createError('deprecatedConfigKey', [key, property.newKey]);
}
return super.unset(property.key);
}
/**
* Get an individual property config.
*
* **Throws** *{@link SfError}{ name: 'UnknownConfigKeyError' }* An attempt to get a property that's not supported.
*
* @param propertyName The name of the property.
*/
getPropertyConfig(propertyName) {
const prop = Config.propertyConfigMap()[propertyName];
if (!prop) {
throw messages.createError('unknownConfigKey', [propertyName]);
}
return prop;
}
/**
* Initializer for supported config types.
*/
async init() {
// Super ConfigFile calls read, which has a dependency on crypto, which finally has a dependency on
// Config.propertyConfigMap being set. This is why init is called after the setup.
await super.init();
}
/**
* Encrypts and content properties that have a encryption attribute.
*
* @param encrypt `true` to encrypt.
*/
async cryptProperties(encrypt) {
const hasEncryptedProperties = this.entries().some(([key]) => {
return !!Config.propertyConfigMap()[key]?.encrypted;
});
if (hasEncryptedProperties) {
await this.initCrypto();
const crypto = (0, ts_types_1.ensure)(this.crypto);
this.forEach((key, value) => {
if (this.getPropertyConfig(key).encrypted && (0, ts_types_1.isString)(value)) {
this.set(key, (0, ts_types_1.ensure)(encrypt ? crypto.encrypt(value) : crypto.decrypt(value)));
}
});
}
}
}
exports.Config = Config;
Config.allowedProperties = [
...exports.SFDX_ALLOWED_PROPERTIES,
...exports.SF_ALLOWED_PROPERTIES,
...orgConfigProperties_1.ORG_CONFIG_ALLOWED_PROPERTIES,
];
class SfdxConfig {
constructor(options = {}, config) {
this.options = options;
this.config = config;
this.sfdxPath = this.getSfdxPath();
}
/**
* If Global.SFDX_INTEROPERABILITY is enabled, merge the sfdx config into the sf config
*/
merge(config) {
if (!global_1.Global.SFDX_INTEROPERABILITY)
return config;
const sfdxConfig = this.readSync();
const sfdxPropKeys = Object.values(SfdxPropertyKeys);
// Get a list of config keys that are NOT provided by SfdxPropertyKeys
const nonSfdxPropKeys = Config.getAllowedProperties()
.filter((p) => !sfdxPropKeys.includes(p.key))
.map((p) => p.key);
// Remove any config from .sf that isn't also in .sfdx
// This handles the scenario where a config has been deleted
// from .sfdx and we want to mirror that change in .sf
for (const key of nonSfdxPropKeys) {
if (!sfdxConfig[key])
delete config[key];
}
return Object.assign(config, sfdxConfig);
}
async write(config = this.config.toObject()) {
try {
const translated = this.translate(config, 'toOld');
const sfdxPath = this.getSfdxPath();
await fs.promises.mkdir((0, path_1.dirname)(sfdxPath), { recursive: true });
await fs.promises.writeFile(sfdxPath, JSON.stringify(translated, null, 2));
}
catch (error) {
/* Do nothing */
}
}
readSync() {
try {
const contents = (0, kit_1.parseJsonMap)(fs.readFileSync(this.getSfdxPath(), 'utf8'));
return this.translate(contents, 'toNew');
}
catch (error) {
/* Do nothing */
return {};
}
}
getSfdxPath() {
if (!this.sfdxPath) {
const stateFolder = global_1.Global.SFDX_STATE_FOLDER;
const fileName = SFDX_CONFIG_FILE_NAME;
const _isGlobal = (0, ts_types_1.isBoolean)(this.options.isGlobal) && this.options.isGlobal;
const _isState = (0, ts_types_1.isBoolean)(this.options.isState) && this.options.isState;
// Don't let users store config files in homedir without being in the state folder.
let configRootFolder = this.options.rootFolder
? this.options.rootFolder
: configFile_1.ConfigFile.resolveRootFolderSync(!!this.options.isGlobal);
if (_isGlobal || _isState) {
configRootFolder = (0, path_1.join)(configRootFolder, stateFolder);
}
this.sfdxPath = (0, path_1.join)(configRootFolder, fileName);
}
return this.sfdxPath;
}
/**
* If toNew is specified: migrate all deprecated configs with a newKey to the newKey.
* - For example, defaultusername will be renamed to target-org.
*
* If toOld is specified: migrate all deprecated configs back to their original key.
* - For example, target-org will be renamed to defaultusername.
*/
translate(contents, direction) {
const translated = {};
for (const [key, value] of Object.entries(contents)) {
const propConfig = direction === 'toNew'
? this.config.getPropertyConfig(key)
: Config.getAllowedProperties().find((c) => c.newKey === key) ?? {};
if (propConfig.deprecated && propConfig.newKey) {
const normalizedKey = direction === 'toNew' ? propConfig.newKey : propConfig.key;
translated[normalizedKey] = value;
}
else {
translated[key] = value;
}
}
return translated;
}
}
exports.SfdxConfig = SfdxConfig;
//# sourceMappingURL=config.js.map
;