@salesforce/core
Version:
Core libraries to interact with SFDX projects, orgs, and APIs.
413 lines • 15.3 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.Config = exports.SfProperty = exports.SFDX_ALLOWED_PROPERTIES = exports.SfdxPropertyKeys = void 0;
const path_1 = require("path");
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 fs_1 = require("../util/fs");
const configFile_1 = require("./configFile");
messages_1.Messages.importMessagesDirectory(__dirname);
const messages = messages_1.Messages.load('@salesforce/core', 'config', [
'invalidInstanceUrl',
'invalidApiVersion',
'invalidIsvDebuggerSid',
'invalidIsvDebuggerUrl',
'invalidBooleanConfigValue',
'invalidNumberConfigValue',
'invalidWrite',
'unknownConfigKey',
'invalidConfigValue',
]);
const log = logger_1.Logger.childFromRoot('core:config');
const SFDX_CONFIG_FILE_NAME = 'sfdx-config.json';
const CONFIG_FILE_NAME = 'config.json';
var SfdxPropertyKeys;
(function (SfdxPropertyKeys) {
/**
* Username associated with the default dev hub org.
*/
SfdxPropertyKeys["DEFAULT_DEV_HUB_USERNAME"] = "defaultdevhubusername";
/**
* Username associate with the default org.
*/
SfdxPropertyKeys["DEFAULT_USERNAME"] = "defaultusername";
/**
* The sid for the debugger configuration.
*/
SfdxPropertyKeys["ISV_DEBUGGER_SID"] = "isvDebuggerSid";
/**
* The url for the debugger configuration.
*/
SfdxPropertyKeys["ISV_DEBUGGER_URL"] = "isvDebuggerUrl";
/**
* The api version
*/
SfdxPropertyKeys["API_VERSION"] = "apiVersion";
/**
* Disables telemetry reporting
*/
SfdxPropertyKeys["DISABLE_TELEMETRY"] = "disableTelemetry";
/**
* allows users to override the 10,000 result query limit
*/
SfdxPropertyKeys["MAX_QUERY_LIMIT"] = "maxQueryLimit";
/** */
SfdxPropertyKeys["REST_DEPLOY"] = "restDeploy";
/** */
SfdxPropertyKeys["INSTANCE_URL"] = "instanceUrl";
})(SfdxPropertyKeys = exports.SfdxPropertyKeys || (exports.SfdxPropertyKeys = {}));
exports.SFDX_ALLOWED_PROPERTIES = [
{
key: SfdxPropertyKeys.INSTANCE_URL,
description: '',
input: {
// If a value is provided validate it otherwise no value is unset.
validator: (value) => value == null || (ts_types_1.isString(value) && sfdc_1.sfdc.isSalesforceDomain(value)),
failedMessage: messages.getMessage('invalidInstanceUrl'),
},
},
{
key: SfdxPropertyKeys.API_VERSION,
description: '',
hidden: true,
input: {
// If a value is provided validate it otherwise no value is unset.
validator: (value) => value == null || (ts_types_1.isString(value) && sfdc_1.sfdc.validateApiVersion(value)),
failedMessage: messages.getMessage('invalidApiVersion'),
},
},
{
key: SfdxPropertyKeys.DEFAULT_DEV_HUB_USERNAME,
description: '',
},
{
key: SfdxPropertyKeys.DEFAULT_USERNAME,
description: '',
},
{
key: SfdxPropertyKeys.ISV_DEBUGGER_SID,
description: '',
encrypted: true,
input: {
// If a value is provided validate it otherwise no value is unset.
validator: (value) => value == null || ts_types_1.isString(value),
failedMessage: messages.getMessage('invalidIsvDebuggerSid'),
},
},
{
key: SfdxPropertyKeys.ISV_DEBUGGER_URL,
description: '',
input: {
// If a value is provided validate it otherwise no value is unset.
validator: (value) => value == null || ts_types_1.isString(value),
failedMessage: messages.getMessage('invalidIsvDebuggerUrl'),
},
},
{
key: SfdxPropertyKeys.DISABLE_TELEMETRY,
description: '',
input: {
validator: (value) => value == null || ['true', 'false'].includes(value.toString()),
failedMessage: messages.getMessage('invalidBooleanConfigValue'),
},
},
// This should be brought in by a plugin, but there isn't a way to do that right now.
{
key: SfdxPropertyKeys.REST_DEPLOY,
description: '',
hidden: true,
input: {
validator: (value) => value != null && ['true', 'false'].includes(value.toString()),
failedMessage: messages.getMessage('invalidBooleanConfigValue'),
},
},
{
key: SfdxPropertyKeys.MAX_QUERY_LIMIT,
description: '',
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('defaultusername', '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();
}
/**
* 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());
metas.forEach((meta) => {
if (currentMetaKeys.includes(meta.key)) {
log.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 {
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 propertyConfigMap() {
return 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 sfdxConfig = global_1.Global.SFDX_INTEROPERABILITY ? this.readSfdxConfigSync() : {};
this.setContents(Object.assign(sfdxConfig, config));
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 sfdxConfig = global_1.Global.SFDX_INTEROPERABILITY ? this.readSfdxConfigSync() : {};
this.setContents(Object.assign(sfdxConfig, config));
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();
await this.writeSfdxConfig();
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 SfdxError}{ 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 SfdxError}{ name: 'UnknownConfigKeyError' }* An attempt to get a property that's not supported.
* **Throws** *{@link SfdxError}{ 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.input) {
if (property.input && property.input.validator(value)) {
super.set(property.key, value);
}
else {
let valueError = (value === null || value === void 0 ? void 0 : value.toString()) || '';
if (property.input.failedMessage) {
valueError = 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 SfdxError}{ 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]);
}
return super.unset(property.key);
}
/**
* 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();
}
readSfdxConfigSync() {
try {
return fs_1.fs.readJsonMapSync(this.getSfdxPath());
}
catch (error) {
/* Do nothing */
return {};
}
}
async writeSfdxConfig() {
try {
await fs_1.fs.mkdirp(path_1.dirname(this.getSfdxPath()));
this.logger.info(`Writing to config file: ${this.getPath()}`);
await fs_1.fs.writeJson(this.getSfdxPath(), this.toObject());
}
catch (error) {
/* Do nothing */
}
}
getSfdxPath() {
if (!this.sfdxPath) {
const stateFolder = global_1.Global.SFDX_STATE_FOLDER;
const fileName = SFDX_CONFIG_FILE_NAME;
const _isGlobal = ts_types_1.isBoolean(this.options.isGlobal) && this.options.isGlobal;
const _isState = 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 = path_1.join(configRootFolder, stateFolder);
}
this.sfdxPath = path_1.join(configRootFolder, fileName);
}
return this.sfdxPath;
}
/**
* Get an individual property config.
*
* **Throws** *{@link SfdxError}{ 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;
}
/**
* Encrypts and content properties that have a encryption attribute.
*
* @param encrypt `true` to encrypt.
*/
async cryptProperties(encrypt) {
const hasEncryptedProperties = this.entries().some(([key]) => {
var _a;
return !!((_a = Config.propertyConfigMap()[key]) === null || _a === void 0 ? void 0 : _a.encrypted);
});
if (hasEncryptedProperties) {
await this.initCrypto();
const crypto = ts_types_1.ensure(this.crypto);
this.forEach((key, value) => {
if (this.getPropertyConfig(key).encrypted && ts_types_1.isString(value)) {
this.set(key, ts_types_1.ensure(encrypt ? crypto.encrypt(value) : crypto.decrypt(value)));
}
});
}
}
}
exports.Config = Config;
Config.allowedProperties = [...exports.SFDX_ALLOWED_PROPERTIES];
//# sourceMappingURL=config.js.map