UNPKG

@salesforce/core

Version:

Core libraries to interact with SFDX projects, orgs, and APIs.

578 lines 23.5 kB
"use strict"; /* * 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